RPC 核心概念 01:什么是 RPC?为什么需要 RPC?
RPC 核心概念 01:什么是 RPC?为什么需要 RPC?
一、从函数调用说起
我们写代码的最基本动作就是调用函数:
result:=add(1,2)这种调用发生在同一个进程内,CPU 直接跳转到函数地址执行。但当业务越来越大,单个进程容纳不下所有功能,我们就要把不同模块拆到不同进程、不同机器上。问题来了:A 机器上的代码如何调用 B 机器上的函数?
这就是 RPC(Remote Procedure Call,远程过程调用)要解决的核心问题——让远程函数调用看起来像本地函数调用一样简单。
二、RPC 的定义
RPC:一种让程序透明地调用另一个地址空间(通常是远程服务器)函数的协议与技术。
调用方(Client)的代码长这样:
result,err:=userClient.GetUser(ctx,&GetUserReq{ID:1})被调方(Server)的代码长这样:
func(s*UserService)GetUser(ctx context.Context,req*GetUserReq)(*GetUserResp,error){return&GetUserResp{Name:"Tom"},nil}中间发生的网络通信、序列化、连接管理等细节全部被框架隐藏。
三、为什么需要 RPC?
3.1 微服务架构的必然需求
随着业务复杂度提升,单体应用面临:
- 编译部署慢;
- 任意模块出 bug 影响全局;
- 团队协作冲突;
- 难以按模块独立扩缩容。
拆分成多个微服务后,服务间通信就是头等大事。HTTP+JSON 当然可以,但有诸多缺点(后述),RPC 框架是更合适的方案。
3.2 性能与开发效率的平衡
| 维度 | HTTP/JSON | RPC |
|---|---|---|
| 协议开销 | 较大(HTTP 头、JSON 文本) | 小(二进制协议) |
| 序列化效率 | 慢(文本) | 快(Protobuf 等) |
| 接口契约 | 弱(依赖文档) | 强(IDL 强约束) |
| 开发体验 | 手写客户端 | 代码自动生成 |
| 服务治理 | 自行实现 | 框架内置 |
四、RPC 的本质:本地调用 + 网络通信
一次 RPC 调用的完整流程:
Client Server │ │ │ 1. 调用 stub (生成的客户端代码) │ │ │ │ 2. 参数序列化 (Marshal) │ │ │ │ 3. 通过网络发送 (TCP / HTTP/2 / QUIC) │ ├─────────────────────────────────────────────────────►│ │ │ 4. 接收并反序列化 │ │ │ │ 5. 路由到具体方法 │ │ │ │ 6. 执行业务逻辑 │ │ │ │ 7. 序列化结果 │◄─────────────────────────────────────────────────────┤ │ 8. 反序列化得到返回值 │可以发现 RPC 框架要解决的核心问题:
- 接口约定(IDL)
- 序列化协议(如 Protobuf)
- 传输协议(TCP、HTTP/2 等)
- 服务发现与负载均衡
- 错误处理与超时
- 可观测性(日志、监控、链路追踪)
五、RPC 的发展历程
| 年代 | 代表技术 | 特点 |
|---|---|---|
| 1980s | Sun RPC、CORBA | 早期跨平台尝试,复杂笨重 |
| 2000s | XML-RPC、SOAP | 文本协议,性能差但跨语言好 |
| 2010s | Thrift(Facebook)、Dubbo(阿里) | 二进制协议,性能跃升 |
| 2015+ | gRPC(Google) | HTTP/2 + Protobuf,事实标准 |
| 国内 | tRPC(腾讯)、Kitex(字节) | 业务定制 + 多协议互通 |
六、与其他通信方式的对比
6.1 RPC vs REST
REST 是资源导向的设计风格,基于 HTTP 语义;RPC 是动作导向的远程调用。
- 对外暴露的开放 API:建议 REST(生态、调试方便);
- 内部微服务通信:建议 RPC(性能、强契约)。
6.2 RPC vs 消息队列(MQ)
- RPC:同步、点对点;
- MQ:异步、解耦、广播。
复杂业务往往两者结合:核心链路同步 RPC,旁路通知用 MQ。
6.3 RPC vs GraphQL
- RPC:客户端直接调用具体方法;
- GraphQL:客户端按需查询字段。
GraphQL 适合面向多端 BFF 场景,RPC 适合内部微服务。
七、一个最小 RPC 示例(伪代码)
服务端
typeCalculatorstruct{}func(c*Calculator)Add(req AddReq)AddResp{returnAddResp{Sum:req.A+req.B}}server:=rpc.NewServer()server.Register(&Calculator{})server.Listen(":8000")客户端
client:=rpc.Dial("127.0.0.1:8000")deferclient.Close()resp,err:=client.Call("Calculator.Add",AddReq{A:1,B:2})fmt.Println(resp.Sum)// 3看起来就像本地调用,但底层经历了序列化、网络、反序列化等一系列步骤。
八、RPC 不是银弹
引入 RPC 的代价:
- 网络不可靠:超时、抖动、断连必须处理;
- 延迟变大:本地调用 ns 级,RPC 至少 ms 级;
- 可观测性复杂:跨进程调用要做链路追踪;
- 版本兼容:服务升级要考虑客户端旧版本。
经典理论:Fallacies of Distributed Computing(分布式计算谬误)告诫我们网络不是 100% 可靠。
九、什么时候不该用 RPC?
- 单进程内:直接函数调用;
- 极高吞吐 + 异步处理:用 MQ;
- 数据流式传输:考虑 gRPC streaming 或专用协议;
- 与浏览器直连:用 REST/WebSocket 更兼容。
十、小结
- RPC 让远程调用像本地调用一样简单;
- RPC 框架解决:IDL、序列化、传输、治理、观测;
- 内部微服务首选 RPC,对外开放接口可用 REST;
- 享受便利的同时要尊重网络不可靠的事实。
下一篇我们将深入 RPC 的"骨架":IDL 与 Protobuf。
