RPC 调用如何处理超时、重试和幂等?
简化版
RPC 是跨网络调用,必须处理三件事:① 超时——远程调用可能很慢或永不返回,必须设超时时间,超时就中断,避免调用方线程被无限占用、级联拖垮;② 重试——对读/幂等操作,调用失败可自动重试其他节点提高成功率(如 Dubbo Failover),但要限制重试次数、加退避;③ 幂等——重试的前提是操作幂等(执行多次结果一样),否则写操作重试会导致数据重复(如重复下单、重复扣款)。核心原则:超时必须设,读可重试,写要么不重试(Failfast)要么保证幂等。
详细版
① 超时(Timeout):
- RPC 必须设超时,否则慢调用/无响应会拖垮调用方(线程/连接耗尽 → 级联故障)。
- Dubbo 用
timeout配置(默认 1000ms)。 - 超时后调用方抛异常、释放资源,配合熔断降级。
② 重试(Retry):
- 失败后重试其他节点(Dubbo Failover 默认 retries=2,共 3 次)。
- 只对幂等操作重试;重试要有次数上限,避免重试风暴。
- 最好加退避(backoff),避免瞬间重试冲击。
③ 幂等(Idempotent):
- 重试的前提是幂等——否则写操作重试会重复执行(重复下单)。
- 实现:唯一 ID 去重、数据库唯一约束、乐观锁、状态机(同「消息幂等」思路)。
关键组合:读操作 = 超时 + Failover 重试(幂等天然满足);写操作 = 超时 + Failfast(不重试)或 保证幂等后再重试。
完整版教学
一、为什么 RPC 必须处理这三件事
RPC 是跨网络调用,网络是不可靠的——会慢、会丢、会断。这带来三个必须面对的问题:
- 远程调用可能很慢或永不返回 → 需要超时控制。
- 调用可能失败(网络抖动、节点故障)→ 可以重试提高成功率。
- 但重试可能重复执行操作 → 需要幂等保证安全。
这三者紧密关联:超时是基础(必须有)、重试是提高成功率的手段(但有风险)、幂等是重试安全的前提。理解它们的关系是关键。
二、超时:RPC 的必备防线
超时是 RPC 最重要、必须设置的一环。 设想:调用方调用一个远程服务,如果这个服务卡住了、或响应极慢,而调用方没设超时,那么调用方的这个线程就会一直阻塞等待。
后果是级联故障:大量请求都卡在等待这个慢服务,调用方的线程池被占满、连接耗尽,导致调用方自己也无法处理其他请求、跟着挂掉;再上游的服务调用它也超时……故障像多米诺骨牌一样蔓延,最终整个系统雪崩。
所以RPC 必须设置合理的超时时间(Dubbo timeout 默认 1000ms)。超时后立即中断调用、抛出异常、释放线程和连接资源,把「慢」的影响限制住。超时通常还配合熔断降级(连续超时就熔断,不再调用问题服务)。
三、重试:提高成功率,但有前提
调用失败(或超时)后,一个自然的想法是重试——换个节点再试一次,可能就成功了(原节点可能只是临时抖动)。Dubbo 的默认 Failover 策略就是失败自动重试其他 Provider(retries 默认 2)。
重试的好处:屏蔽个别节点的临时故障,提高整体调用成功率和可用性。
但重试有风险和讲究:
- 必须限制重试次数:无限重试会在故障时形成重试风暴——本来就慢/挂的服务,被大量重试请求进一步压垮。所以要设次数上限。
- 最好加退避(backoff):不要失败后立即重试,而是等待递增的时间再重试(如 100ms、200ms、400ms),避免瞬间的重试洪峰。
- 最关键——只对幂等操作重试:见下文。
四、幂等:重试安全的前提(核心)
重试最大的坑:非幂等操作重试会导致重复执行。
考虑「创建订单」这个写操作:调用方发起「创建订单」,服务端其实成功创建了,但返回响应时网络超时了。调用方以为失败,重试——于是又发一次「创建订单」,结果创建了两个订单!同理「扣款」重试会扣两次款。这就是非幂等操作重试的灾难。
所以重试的前提是操作幂等——执行一次和执行多次结果相同。保证幂等的手段(同「消息幂等消费」):
- 唯一 ID + 去重:请求带全局唯一 ID,服务端处理前查重,重复的直接返回上次结果、不重复执行。
- 数据库唯一约束:如订单号唯一索引,重复创建冲突失败。
- 乐观锁 / 状态机:
update ... where version=?或校验前置状态,重复更新不生效。
五、读写分开处理的核心原则
综合超时、重试、幂等,实践中的核心原则是按读写区别对待:
读操作(查询):
- 天然幂等(查多少次结果一样)。
- 用 超时 + Failover 重试——失败重试其他节点,安全地提高成功率。
写操作(新增、修改、删除中的非幂等操作):
- 默认不能重试(重试会重复)。
- 两种做法:① 用 Failfast(快速失败,不重试),失败交给上层处理;② 如果一定要重试提高可靠性,先保证操作幂等(唯一 ID/唯一约束/状态机),再重试才安全。
千万不要对非幂等的写操作用默认的 Failover 重试——这是 RPC 里最经典的坑,会导致重复下单、重复扣款等严重问题。
六、常见误区与追问
这道题面试时最容易丢分的地方,是把「RPC 超时、重试与幂等」答成一段泛泛的组件介绍。更稳的答法是先给结论,再沿着 远程调用链路 拆清楚流程,最后补上异常场景、数字边界和选型取舍。
| 回答层次 | 要讲清的内容 | 容易漏掉的边界 |
|---|---|---|
| 核心定义 | 超时控制等待上限,重试提高成功率,幂等负责兜住重复请求带来的副作用 | 不要停在名词解释 |
| 流程机制 | 设置单次超时 -> 调用失败或超时 -> 判断是否可重试 -> 重试其他节点 -> 业务侧用幂等键去重 -> 超过次数返回失败 | 说明谁触发、谁存储、谁通知、谁兜底 |
| 工程取舍 | 单次超时 200ms、重试 2 次时,最坏耗时可能接近 600ms,还会给下游放大 3 倍请求量 | RPC 让调用写法像本地方法,但失败语义、网络延迟和版本兼容必须按远程系统处理 |
RPC 超时、重试与幂等 面试拆解:
1. 设置单次超时
2. 调用失败或超时
3. 判断是否可重试
4. 重试其他节点
5. 业务侧用幂等键去重
6. 超过次数返回失败
记忆钩子:先拆代理、序列化、传输、寻址、容错,再说明超时、重试、幂等这些工程边界;回答时一定要落到题目中的「RPC 超时、重试与幂等」,不要把相邻中间件的能力混着讲。
- 误区:超时后服务端一定没有执行。 客户端超时只代表没收到响应,服务端可能已经执行成功。
- 误区:重试能无成本提高可用性。 重试会放大流量和延迟,故障时可能压垮下游。
- 误区:只有支付接口需要幂等。 下单、发券、发消息、库存扣减等写操作都要考虑重复请求。
- 追问:哪些接口适合自动重试? 查询类、幂等写或明确可去重的操作更适合。
- 追问:幂等键怎么设计? 用业务唯一号、请求号或去重表,保证同一业务请求只生效一次。
- 追问:如何避免重试风暴? 限制重试次数、退避、熔断、隔离,并区分错误类型。
七、加强记忆
RPC 跨网络必须处理三件事:① 超时(必设)——远程调用可能慢/不返回,不设超时会导致调用方线程/连接耗尽、级联雪崩,超时后中断释放资源(Dubbo timeout 默认 1s)+ 配合熔断;② 重试——失败换节点重试提高成功率(Dubbo Failover),但要限次数 + 加退避防重试风暴,且只对幂等操作重试;③ 幂等——重试的前提,否则写操作重试会重复执行(重复下单/扣款),靠唯一 ID 去重 / 唯一约束 / 乐观锁 / 状态机保证。核心原则:读操作用「超时 + Failover 重试」(天然幂等);写操作用「超时 + Failfast 不重试」或「保证幂等后再重试」。最大的坑:非幂等写操作绝不能用默认重试。