@freeCodeCamp:现代分布式系统需要服务之间快速、可靠的通信方式。在这本手册中,@devseyi 解释了如…
摘要
一本全面的手册,讲解 RPC、Protocol Buffers 和 gRPC,用于构建现代分布式系统,并包含使用 Dart 和 Flutter 的动手实践。
查看缓存全文
缓存时间: 2026/07/27 13:53
现代分布式系统需要快速、可靠的服务间通信方式。在这本手册中,@devseyi 解释了 RPC、Protocol Buffers 和 gRPC 如何协同工作。你将探索 gRPC 的通信模式,使用 Dart 和 Flutter 构建完整系统,并学习何时使用 gRPC 而非 REST 或 WebSockets。https://freecodecamp.org/news/remote-procedure-calls-protocol-buffers-and-modern-distributed-systems-communication/… — # 从RPC到gRPC:理解远程过程调用、Protocol Buffers和现代分布式系统通信 来源:https://www.freecodecamp.org/news/remote-procedure-calls-protocol-buffers-and-modern-distributed-systems-communication/ 从RPC到gRPC:理解远程过程调用、Protocol Buffers和现代分布式系统通信
每个应用程序,在某个时刻,都需要与另一个系统通信。一个移动应用与后端通信。一个后端服务与支付网关通信。一个认证服务与用户服务通信。一个数据管道与存储层通信。问题从来不是系统是否需要通信。问题始终是如何通信。多年来,基于HTTP的REST与JSON一直是默认答案。它有效,简单,并且工具随处可见。但随着系统规模的增长(服务间通信数量、数据交换量以及实时通信需求),REST开始显露其局限性。这时,远程过程调用(RPC)、Protocol Buffers 和 gRPC 登场了。
在本手册中,你将学习什么是RPC以及它旨在解决的问题。你还将理解Protocol Buffers:它们是什么,为什么存在,以及如何工作。然后你会看到Google如何将这些想法结合到gRPC中——现代分布式系统中最强大的通信框架之一。你将学习所有四种gRPC通信模式,看到从单个契约文件生成多语言代码的过程,并通过一个完整的端到端Flutter实现(包含生产级关注点:认证、错误处理和超时)进行实践。到最后,你不仅会知道gRPC是什么,还会理解何时使用它、何时不使用它,以及如何以系统工程师的视角思考服务通信。
目录
- 什么是远程过程调用 (https://www.freecodecamp.org/news/remote-procedure-calls-protocol-buffers-and-modern-distributed-systems-communication/#heading-what-is-a-remote-procedure-call)?
- RPC解决的问题 (https://www.freecodecamp.org/news/remote-procedure-calls-protocol-buffers-and-modern-distributed-systems-communication/#heading-the-problem-rpc-solves)
- 为什么选择gRPC而非REST:真实场景 (https://www.freecodecamp.org/news/remote-procedure-calls-protocol-buffers-and-modern-distributed-systems-communication/#heading-why-grpc-over-rest-the-real-case)
- Protocol Buffers:一种用于数据的新语言 (https://www.freecodecamp.org/news/remote-procedure-calls-protocol-buffers-and-modern-distributed-systems-communication/#heading-protocol-buffers-a-new-language-for-data)
- Proto文件 (https://www.freecodecamp.org/news/remote-procedure-calls-protocol-buffers-and-modern-distributed-systems-communication/#heading-the-proto-file)
- JSON vs Protocol Buffers (https://www.freecodecamp.org/news/remote-procedure-calls-protocol-buffers-and-modern-distributed-systems-communication/#heading-json-vs-protocol-buffers)
- Protoc编译器与代码生成 (https://www.freecodecamp.org/news/remote-procedure-calls-protocol-buffers-and-modern-distributed-systems-communication/#heading-the-protoc-compiler-and-code-generation)
- 什么是gRPC (https://www.freecodecamp.org/news/remote-procedure-calls-protocol-buffers-and-modern-distributed-systems-communication/#heading-what-is-grpc)?
- 为什么HTTP/2对gRPC至关重要 (https://www.freecodecamp.org/news/remote-procedure-calls-protocol-buffers-and-modern-distributed-systems-communication/#heading-why-http2-matters-for-grpc)
- 四种gRPC通信模式 (https://www.freecodecamp.org/news/remote-procedure-calls-protocol-buffers-and-modern-distributed-systems-communication/#heading-the-four-grpc-communication-patterns)
- Protobuf仓库:组织最佳实践 (https://www.freecodecamp.org/news/remote-procedure-calls-protocol-buffers-and-modern-distributed-systems-communication/#heading-the-protobuf-repository-organizational-best-practice)
- 使用Dart和Flutter构建完整的gRPC系统 (https://www.freecodecamp.org/news/remote-procedure-calls-protocol-buffers-and-modern-distributed-systems-communication/#heading-building-a-complete-grpc-system-with-dart-and-flutter)
- 生产关注点 (https://www.freecodecamp.org/news/remote-procedure-calls-protocol-buffers-and-modern-distributed-systems-communication/#heading-production-concerns)
- gRPC vs REST vs WebSockets:何时使用什么 (https://www.freecodecamp.org/news/remote-procedure-calls-protocol-buffers-and-modern-distributed-systems-communication/#heading-grpc-vs-rest-vs-websockets-when-to-use-what)
- 混合架构 (https://www.freecodecamp.org/news/remote-procedure-calls-protocol-buffers-and-modern-distributed-systems-communication/#heading-the-hybrid-architecture)
- 结论 (https://www.freecodecamp.org/news/remote-procedure-calls-protocol-buffers-and-modern-distributed-systems-communication/#heading-conclusion)
什么是远程过程调用?
要理解RPC,首先需要理解什么是过程调用。过程调用意味着调用一个过程或函数,以便执行其代码。例如,在Dart中:
double calculateTax(double amount) {
return amount * 0.075;
}
final tax = calculateTax(50000); // 本地过程调用
你调用 calculateTax,传入参数,得到结果。该函数位于同一台机器、同一个进程、同一块内存空间中。这是一个本地过程调用。
远程过程调用(RPC)将这个相同的想法扩展到网络中。你调用的函数位于另一台机器、另一个进程中,甚至可能在另一个国家。但从调用者的角度来看,它感觉就像调用本地函数一样。
// 这看起来像本地函数调用
final tax = await taxService.calculateTax(amount: 50000);
// 但在底层,这:
// 1. 将参数序列化为二进制格式
// 2. 通过网络连接发送到远程服务器
// 3. 服务器用你的参数执行 calculateTax
// 4. 序列化结果
// 5. 通过网络发送回来
// 6. 反序列化为Dart对象
// 7. 像本地调用一样返回给你
网络复杂性被完全隐藏。你调用一个函数,得到一个结果。中间的一切都由RPC框架处理。这就是RPC的基本思想:让远程函数调用感觉像本地调用一样自然。
RPC解决的问题
要理解RPC为何重要,你需要看看没有RPC时的替代方案是什么样子。没有RPC时,调用远程服务看起来像这样:
Future<double> calculateTax(double amount) async {
final response = await http.post(
Uri.parse('https://tax-service.internal/api/v1/calculate'),
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer $token',
},
body: jsonEncode({'amount': amount}),
);
if (response.statusCode != 200) {
throw Exception('税率计算失败: ${response.statusCode}');
}
final data = jsonDecode(response.body);
return (data['tax'] as num).toDouble();
}
每次服务调用都需要你:
- 知道并硬编码端点URL
- 知道正确的HTTP方法
- 手动序列化请求到JSON
- 自己处理HTTP状态码
- 手动从JSON反序列化响应
- 将动态类型转换为你实际期望的类型
- 希望响应中的字段名与你认为的一致
现在将这种情况乘以应用中每一次服务调用。一个认证服务、用户服务、支付服务、通知服务、交易服务……每一个都需要同样的手动模板代码。每一个都可能引入字段名拼写错误、状态码假设错误,或者只在运行时才出现的JSON反序列化失败。
使用RPC:
// 感觉就像本地函数调用
final tax = await taxService.calculateTax(
TaxRequest(amount: 50000),
);
// tax 已经是强类型的 TaxResponse 对象
// 没有URL,没有HTTP方法,没有JSON解析,没有类型转换。
框架处理所有事情。函数签名定义在契约文件中,客户端和服务器均使用该文件。类型在编译时被强制执行。如果服务器更改了响应形状,客户端在进入生产环境之前就会编译失败。这就是RPC解决的问题:它消除了网络通信的偶然复杂性,让你专注于真正想做的事情。
为什么选择gRPC而非REST:真实场景
在深入探讨Protocol Buffers和gRPC的技术细节之前,有必要明确说明为何在特定场景下你会选择gRPC而非REST。这并不是说gRPC总是更好。而是清晰地看看它在哪些方面真正胜出。
大型负载被多个内部系统调用
考虑一家大型电信公司的内部企业API。一个计划注册或激活流程的单个请求和响应负载可能包含超过一千个字段。该端点被数十个内部应用调用:计费系统、CRM平台、面向客户的移动应用、内部仪表盘和合作伙伴门户。
使用REST和JSON时,这些应用中的每一个都以文本形式发送和接收那个包含上千字段的负载。像 subscription_activation_status、rate_plan_identifier 和 network_provisioning_reference 这样的字段名,每次请求都作为字符串在线上传输。每个负载的很大一部分不是数据本身,而是数据的标签。
使用Protocol Buffers时,字段名永远不会出现在负载中。只有字段号和值在线上传输。那个包含上千字段的负载会显著缩小。对于每天被数十个系统调用数百万次的端点来说,带宽节省是巨大的,并直接转化为基础设施成本的降低。
除了大小,生成的客户端保证也同样重要。使用REST时,那数十个应用各自阅读API文档,并构建自己对该契约的理解。当后端更改字段名或类型时,并非所有应用都能立即发现。有些应用直到在生产环境中出问题时才知道。使用共享的 .proto 文件,每个应用从同一源生成自己的强类型客户端。契约变更意味着每个应用重新生成。编译器会立即报告哪些破坏性变更影响了每个代码库。没有任何东西以损坏状态进入生产环境。
低带宽和远程网络条件
这是gRPC在网络质量差异显著的市场上最被低估的优势之一。在许多地区,相当一部分移动用户仍在使用2G或3G连接。在2G连接上,带宽可能低至每秒50到100千比特。一个80KB的REST JSON响应在2G连接上需要超过6秒才能下载。而等效的protobuf二进制文件(可以小3到10倍)仅需不到2秒。这种差异不仅仅是技术脚注。它是在相当一部分用户眼中,应用感觉可用还是感觉崩溃的分界线。对于任何在可变网络条件下运营的产品,protobuf的二进制效率都是直接的竞争优势。
除了大小,gRPC运行在HTTP/2之上,它维护单个持久连接,而不是为每个请求打开新连接。在慢速网络上,连接建立(TCP握手和TLS协商)本身可能需要几百毫秒,跨多个调用重用单一连接可节省大量时间。
微服务间的通信
当两个内部服务需要通信时,你有多种选择。像Kafka或RabbitMQ这样的消息总线非常适合不需要立即响应、操作可以异步进行,以及向多个消费者广播已发生事件的场景。但许多服务间调用本质上是同步的。认证服务需要在请求继续之前立即验证令牌。欺诈检测服务需要在支付被授权之前立即评估交易。定价服务需要在报价生成之前立即计算费率。这些操作无法发布一个事件然后等待。
对于高频的同步服务间调用,配合protobuf编码的gRPC over HTTP/2比REST高效得多。持久的多路复用连接意味着每次调用没有连接建立开销。二进制编码意味着每次跳转没有JSON序列化和反序列化开销。生成的客户端意味着两个服务都针对同一契约进行编译。在大规模场景下,当两个服务每秒相互调用数千次时,这些效率差异会累积成真实性能和成本差异。
跨多个团队管理API契约
在大型工程组织中,多个团队构建其他团队依赖的服务。REST API契约存在于文档中。文档会过时。后端团队更改了字段名。移动团队直到用户报告崩溃时才发觉。数据团队直到凌晨2点他们的管道抛出错误才发现。gRPC的protobuf仓库方法将契约管理从文档问题转变为代码问题。契约变更通过拉取请求进行。每个依赖团队审查变更。破坏性变更在编译时就被捕获。没有人会在生产环境中感到意外。这种治理优势随着团队规模的增长而扩大。组织越大,其价值就越高。
实时通信
REST是请求-响应模式。客户端提问,服务器回答。对话结束。对于实时特性,你要么轮询(浪费资源),要么在REST API旁边单独搭建一个WebSocket服务器(需要维护两个不同的系统)。gRPC的流模式在与常规调用相同的框架中原生处理实时通信。实现余额更新、实时交易通知或双向聊天会话,都使用与常规一元调用相同的生成客户端、相同的连接和相同的protobuf编码。一个框架涵盖所有通信模式,无需额外基础设施。
Protocol Buffers:一种用于数据的新语言
RPC是一个概念。要实现它,你需要两样东西:一种在客户端和服务器之间定义契约的方法,以及一种高效序列化数据以便通过网络传输的方法。这就是Protocol Buffers登场的地方。
Protocol Buffers,通常称为 protobuf,是一种语言无关、平台无关、可扩展的结构化数据序列化机制。它于2001年在Google开发,内部使用多年,并于2008年开源。
大规模下的JSON问题
JSON是Web API的主流数据格式。它人类可读、灵活且得到普遍支持。对于许多用例,它是正确的选择。但JSON存在结构上的低效性,在大规模场景下会变得痛苦。
考虑一个用户档案响应:
{
"id": "usr_001",
"first_name": "John",
"last_name": "Smith",
"email": "[email protected]",
"phone_number": "+2348012345678",
"account_type": "savings",
"balance": 500000.00,
"currency": "NGN",
"is_verified": true,
"is_active": true,
"kyc_level": 3,
"created_at": "2024
相似文章
使用Go开发移动应用
一位开发者分享了使用Go Mobile为Flutter应用构建后端的经验,详细介绍了通过protobuf和平台通道进行通信的方式。
@freeCodeCamp: 当一个事件需要触发多个反应时,紧耦合的代码会很快变得难以维护。在这本手册…
一本介绍观察者设计模式、其在Dart中的实现,以及如何在Flutter应用中与事件驱动架构和领域驱动设计集成的手册。
@freeCodeCamp:当数据库写入成功但事件发布失败时,分布式系统可能会崩溃。在本教程中,@pliutau 教…
本教程介绍如何在 Go 和 PostgreSQL 中实现 Outbox 模式,以确保分布式系统中可靠的事件发布,包括构建中继服务和处理至少一次投递。
ByteByteGoHq/system-design-101
一个GitHub仓库,提供复杂系统设计概念的视觉化和简单解释,涵盖API、负载均衡、HTTP和网络等主题。
@freeCodeCamp: 很多RAG教程在本地运行没问题,但一旦尝试部署就会出问题。在这本手册中,@dannwaneri 教你…
这本手册教开发者如何构建一个生产级RAG系统,使用Cloudflare Workers、Vectorize和Workers AI,专注于成本效益和可靠性。