3、超时机制概述:超时与重试、熔断的关系
聊超时机制之前,我想先问大家一个问题:你遇到过服务调用卡死的情况吗?
我在项目中遇到过好几次。有一次线上告警,某个接口的响应时间从 50ms 突然飙到 30 秒,整个线程池被打满,后续请求全部排队。最后发现是下游服务挂了,但我们的客户端没设超时,一直在傻等。
嗯,这就是典型的没有超时保护的后果。
超时机制,说白了就是给每个请求设定一个“等待上限”。超过这个时间,不管结果如何,先放弃再说。但超时不是孤立存在的,它和重试、熔断是铁三角关系。今天我们就来拆解这三者是怎么配合的。
3.1 超时的本质:给不确定性设一个边界
分布式系统里,网络延迟、服务负载、GC 停顿……这些不确定性太多了。你没法保证每次调用都能在 100ms 内返回。但你可以决定:我等多久就不再等了。
我个人习惯把超时分为两类:
- 连接超时:建立 TCP 连接的最长等待时间。一般设 500ms~1s。
- 读取超时:连接建立后,等待响应数据的最长时间。这个要根据业务来,我通常设 2s~5s。
核心原则:超时时间 = 正常 P99 延迟 × 1.5 ~ 2 倍。别拍脑袋设个 30 秒,那等于没设。
3.2 超时与重试:一对相爱相杀的兄弟
超时之后怎么办?很多人第一反应就是重试。但重试不是万能的,用不好反而会雪上加霜。
我曾经犯过一个错误:某个核心服务设了 3 次重试,每次超时 2 秒。结果下游服务 GC 抖动,所有请求都超时,然后重试又发起新请求,直接把下游打挂了。这就是典型的重试风暴。
所以,超时和重试必须搭配使用,规则如下:
| 场景 | 是否重试 | 说明 |
|---|---|---|
| 连接超时 | 建议重试 | 可能是网络抖动,重试成功率较高 |
| 读取超时 | 谨慎重试 | 可能是服务端处理慢,重试可能加重负载 |
| 业务异常(如参数错误) | 不重试 | 重试一万次也是同样的结果 |
| 幂等性不保证 | 不重试 | 重试可能导致数据重复 |
我的经验:重试一定要加退避策略。比如第一次失败后等 100ms,第二次等 200ms,第三次等 400ms。别一股脑全扎进去。
3.3 超时与熔断:从“等待”到“主动拒绝”
重试解决不了所有问题。如果下游服务已经挂了,你重试再多次也没用。这时候就需要熔断机制出场了。
熔断的核心思想很简单:连续失败达到阈值,直接短路,不再发起请求。
你想想看,如果某个接口连续超时 10 次,说明什么?说明下游大概率已经不可用了。这时候再发请求,除了浪费资源,没有任何意义。
超时和熔断的关系是这样的:
- 超时是熔断的输入信号:每次超时都算一次失败,累计到阈值触发熔断。
- 熔断是超时的保护机制:熔断开启后,请求直接返回失败,不再等待超时。
- 半开状态:熔断一段时间后,放少量请求试探,如果成功则关闭熔断,如果超时则继续熔断。
注意:熔断的阈值不能设得太低。我曾经设了 3 次失败就熔断,结果一次 GC 抖动就熔断了半小时,影响面很大。建议设 10~20 次,配合滑动窗口统计。
3.4 三者的协作流程
为了让你更直观地理解,我画了一张流程图:
这张图展示了完整的流程:
- 发起请求后,先等待响应。
- 如果没超时,正常返回结果。
- 如果超时了,检查熔断器状态。
- 熔断器关闭或半开,尝试重试(带退避)。
- 熔断器开启,直接快速失败,不重试。
- 重试次数用完,返回超时异常,并上报给熔断器。
3.5 实际项目中的配置建议
说了这么多理论,最后给点干货。这是我目前在用的配置模板:
# 超时配置
rpc:
timeout:
connect: 500ms # 连接超时
read: 3000ms # 读取超时
total: 10000ms # 整体超时(含重试)
# 重试配置
retry:
maxAttempts: 3 # 最大重试次数
backoff:
type: exponential # 指数退避
initialDelay: 100ms # 初始延迟
multiplier: 2 # 倍数
maxDelay: 2000ms # 最大延迟
# 熔断配置
circuitBreaker:
failureThreshold: 10 # 失败阈值
successThreshold: 5 # 成功阈值(半开状态)
windowSize: 60s # 滑动窗口大小
openDuration: 30s # 熔断持续时间
避坑指南:我曾经把重试退避的倍数设成 5,结果第一次等 100ms,第二次等 500ms,第三次等 2500ms……总耗时远超预期。建议倍数控制在 1.5~2 之间。
总结一下:超时是底线,重试是补偿,熔断是保护。三者缺一不可,但也不能滥用。设好参数,做好监控,才能让系统在故障面前稳如泰山。
公众号:蓝海资料掘金营,微信deep3321