一次 RPC 调用要经过哪些环节?超时与重试要注意什么?
结论先行:一次 RPC 调用从客户端发起到拿到结果,要经过动态代理封装、序列化、服务发现与负载均衡、网络传输、服务端反序列化与反射调用、结果返回等环节。工程上最容易出问题的是超时与重试:超时要分层设置,重试只对幂等接口安全,且要有限次、带退避,否则一次故障会演变成雪崩。
一、调用链路各环节
- 客户端通过动态代理生成接口的远程调用 stub;
- 组装请求:方法、参数序列化(JSON/Protobuf/Hessian),附加调用元数据;
- 服务发现:从注册中心拿到可用服务实例列表,负载均衡选一台(随机/轮询/一致性哈希);
- 连接管理:从连接池取连接发送请求,同步等待或异步回调;
- 服务端接收:反序列化请求,按服务名与方法路由到实现类反射执行;
- 返回结果:结果序列化回传,客户端反序列化后返回给调用方或抛出异常。
- 补充:服务端处理完成但响应在网络中丢失时,客户端同样表现为超时,此时查询业务结果比盲目重放更安全。
二、超时与重试的注意点
| 关注点 | 说明 |
|---|---|
| 超时分两类 | 连接超时(建连阶段)与读超时(等待响应),要分别配置 |
| 超时阈值 | 客户端超时要覆盖服务端正常处理时间,过短会误杀慢请求 |
| 重试前提 | 只有幂等接口才能安全重试,写接口必须带幂等键 |
| 重试策略 | 限次数 + 指数退避 + 随机抖动,避免重试风暴 |
| 与熔断配合 | 下游故障时优先快速失败与熔断,而不是无限重试放大压力 |
| 超时传递 | 全链路超时要收敛:调用方超时大于等于被调方超时,避免层层叠加 |
| 结果语义 | 超时不等于失败,需用幂等键查业务状态后再决定是否重试 |
三、配置示意
# RPC 客户端常见配置
timeout: 3000 # 读超时 3 秒
connect-timeout: 1000 # 连接超时 1 秒
retries: 2 # 最多重试 2 次,且仅幂等接口开启
backoff: 200 # 重试间隔基数,建议指数退避并加随机抖动
常见追问 / 记忆点
- 追问:接口不幂等时重试怎么办?答:调用方生成请求幂等键透传,服务端按幂等键去重,重试才会安全。
- 追问:为什么重试要加抖动?答:同时失败的服务会同时重试,形成毛刺流量,加随机抖动让重试错开。
- 追问:下游一直超时,重试还是熔断?答:短时抖动可重试,持续故障应熔断并降级,避免雪崩。
- 记忆点:代理、序列化、发现、传输、反射五环节;超时分层、重试限次、幂等才重试。