什么是 RPC?一次 RPC 调用的完整流程是怎样的?
简化版
RPC(Remote Procedure Call,远程过程调用)是一种让程序像调用本地方法一样调用远程服务器上的方法的技术——你写 userService.getUser(id),感觉是本地调用,实际上请求被发到了另一台机器上执行、再把结果拿回来,中间的网络通信、序列化都被框架隐藏了。一次 RPC 的核心流程:客户端调用本地代理(Stub)→ 代理把方法名和参数序列化 → 通过网络发送到服务端 → 服务端反序列化、找到真实方法执行 → 结果序列化返回 → 客户端反序列化拿到结果。RPC 的价值就是让远程调用对开发者透明。
详细版
RPC 的核心组件:
- 动态代理(Stub/Proxy):客户端拿到的是接口的代理对象,调用它就触发远程调用。
- 序列化/反序列化:把方法参数、返回值在对象和字节流之间转换(用于网络传输)。
- 网络传输:通过 TCP(或 HTTP/2)把数据发到服务端。
- 服务端骨架(Skeleton):接收请求,反序列化,反射调用真实方法。
- 注册中心:服务发现,客户端找到服务端地址。
完整调用流程:
客户端 服务端
①调用代理方法 userService.getUser(1)
②代理拦截 → 封装请求(接口/方法/参数)
③序列化请求为字节流
④通过网络(TCP)发送 ─────────────────────→ ⑤接收字节流
⑥反序列化请求
⑦反射调用真实方法 getUser(1)
⑧序列化返回结果
⑩反序列化结果 ←───────────────────────── ⑨通过网络返回
⑪返回给调用方
完整版教学
一、RPC 要解决什么:让远程调用像本地一样
在单体应用里,一个模块调用另一个模块,就是一次本地方法调用——orderService.create(),简单直接。但拆成微服务后,订单服务和用户服务部署在不同机器上,订单服务要调用用户服务的方法,就得跨网络通信。
如果让开发者自己处理网络通信(建立连接、拼数据包、发送、接收、解析),既繁琐又容易出错。RPC 的目标就是把这一切封装起来,让开发者「像调用本地方法一样调用远程方法」——你只管写 userService.getUser(id),至于「这个方法在另一台机器上、要走网络、要序列化」,全部由 RPC 框架透明处理。这就是 RPC 的核心价值:远程调用的透明化。
二、核心组件:代理、序列化、网络、反射
要实现「本地调用的假象 + 真实的远程执行」,RPC 框架需要几个关键组件:
- 动态代理(Stub):客户端调用的
userService其实是一个代理对象(框架用动态代理生成的)。你调它的方法,代理会拦截下来,转成一次远程调用,而不是真的在本地执行。这是「本地调用假象」的关键。 - 序列化/反序列化:方法的参数是 Java 对象,网络只能传字节。所以要把参数序列化成字节流发出去,服务端再反序列化还原成对象;返回值同理。
- 网络传输:用 TCP(Dubbo 默认)或 HTTP/2(gRPC)把字节流从客户端传到服务端。
- 服务端反射调用:服务端收到请求,反序列化出「要调哪个接口的哪个方法、什么参数」,然后通过反射找到真实的实现方法并执行。
- 注册中心(配套):客户端要知道服务端在哪,通过注册中心做服务发现。
三、完整调用流程(分步拆解)
一次完整的 RPC 调用,从客户端发起到拿到结果:
客户端侧:
- 客户端调用代理对象的方法:
userService.getUser(1)。 - 代理拦截这次调用,封装成一个请求对象(包含:接口名、方法名、参数类型、参数值)。
- 把请求对象序列化成字节流。
- 通过网络(TCP)把字节流发送给服务端(发到哪台机器,由注册中心的服务发现 + 负载均衡决定)。
服务端侧:
5. 服务端接收字节流。
6. 反序列化还原出请求对象。
7. 根据接口名、方法名,通过反射找到真实的服务实现,调用真实方法 getUser(1),得到结果。
8. 把返回结果序列化成字节流。
9. 通过网络返回给客户端。
客户端侧:
10. 客户端接收返回的字节流,反序列化成结果对象。
11. 把结果返回给最初的调用方——getUser(1) 的返回值。
对调用方来说,它只感知到第 1 步和第 11 步(调用、拿结果),中间的序列化、网络、反射全部被框架隐藏了——这就是「像本地调用一样」。
四、RPC 框架还要处理的问题
一个成熟的 RPC 框架,除了上面的核心流程,还要解决很多工程问题:
- 服务发现与负载均衡:客户端如何找到服务端(注册中心)、多个实例如何选一个(负载均衡)。
- 容错:调用失败怎么办(重试、快速失败、降级)。
- 超时控制:远程调用可能很慢或不返回,要设超时。
- 序列化协议选择:用什么序列化(Protobuf、Hessian、JSON)影响性能和兼容性。
- 连接管理:连接池、长连接复用、心跳保活。
- 异步调用、泛化调用、SPI 扩展等高级特性。
这些让 RPC 框架(Dubbo、gRPC)比「裸的远程调用」复杂得多。
五、RPC 的典型代表
- Dubbo:阿里开源,Java 生态,默认 Dubbo 协议 + Hessian 序列化,功能丰富(负载均衡、容错、SPI),国内广泛使用。
- gRPC:Google 开源,基于 HTTP/2 + Protobuf,跨语言,性能高,云原生生态标配。
- Thrift:Facebook 开源,跨语言,有自己的 IDL。
- 早期:Java RMI、Hessian、WebService(SOAP)。
六、常见误区与追问
这道题面试时最容易丢分的地方,是把「RPC 调用完整流程」答成一段泛泛的组件介绍。更稳的答法是先给结论,再沿着 远程调用链路 拆清楚流程,最后补上异常场景、数字边界和选型取舍。
| 回答层次 | 要讲清的内容 | 容易漏掉的边界 |
|---|---|---|
| 核心定义 | RPC 让调用方像调本地方法一样发起远程调用,但底层仍是代理、序列化、网络传输和服务端反射执行 | 不要停在名词解释 |
| 流程机制 | 调用客户端代理 -> 封装接口方法和参数 -> 序列化为字节流 -> 网络发送到服务端 -> 反序列化并执行真实方法 -> 序列化响应返回 | 说明谁触发、谁存储、谁通知、谁兜底 |
| 工程取舍 | 一次 getUser(1) 调用通常会经过请求编码、网络 RTT、服务执行、响应解码,超时应按几十到几百毫秒级配置 | RPC 让调用写法像本地方法,但失败语义、网络延迟和版本兼容必须按远程系统处理 |
RPC 调用完整流程 面试拆解:
1. 调用客户端代理
2. 封装接口方法和参数
3. 序列化为字节流
4. 网络发送到服务端
5. 反序列化并执行真实方法
6. 序列化响应返回
记忆钩子:先拆代理、序列化、传输、寻址、容错,再说明超时、重试、幂等这些工程边界;回答时一定要落到题目中的「RPC 调用完整流程」,不要把相邻中间件的能力混着讲。
- 误区:RPC 调用和本地方法一样可靠。 写法像本地,但网络超时、连接断开、重复请求都必须按远程调用处理。
- 误区:RPC 框架只负责发请求。 成熟框架还负责服务发现、负载均衡、连接池、超时、重试、熔断和序列化。
- 误区:序列化方式无所谓。 序列化影响性能、包大小、跨语言能力和版本兼容。
- 追问:为什么需要客户端代理? 代理拦截本地方法调用,把方法名、参数等封装成远程请求。
- 追问:服务端如何找到真实方法? 根据接口名、方法名和参数类型定位实现类,再反射或生成调用器执行。
- 追问:RPC 最容易踩的工程坑是什么? 超时重试导致重复执行,所以写接口时要考虑幂等。
七、加强记忆
RPC(远程过程调用)= 让你像调用本地方法一样调用远程服务器上的方法,把网络通信、序列化全部隐藏,实现远程调用透明化。核心组件:动态代理(Stub,制造本地调用假象)、序列化/反序列化(对象↔字节流)、网络传输(TCP/HTTP2)、服务端反射调用、注册中心(服务发现)。完整流程:客户端调代理 → 封装请求并序列化 → 网络发送 → 服务端反序列化 → 反射调用真实方法 → 结果序列化返回 → 客户端反序列化拿到结果。成熟框架还要处理服务发现、负载均衡、容错、超时。代表:Dubbo(Java)、gRPC(HTTP2+Protobuf 跨语言)、Thrift。口诀:代理制造本地假象、序列化跨网络、反射执行真方法。