先说结论

超时限制单次等待,重试处理短暂失败,熔断在持续失败时快速阻止调用,降级提供可接受的替代结果。正确顺序是先定义端到端预算,每跳超时小于剩余预算;只对幂等且可能恢复的错误有限重试;熔断后快速失败并进入降级。

四者必须配合隔离和限流,否则线程、连接和队列仍可能被慢依赖耗尽。

重试策略

使用指数退避加随机抖动,限制次数和总耗时,并尽量只在一层重试,避免调用链每层三次形成指数放大。写操作携带幂等键,明确超时后“结果未知”的查询或补偿路径。

端到端超时预算

假设用户请求总预算 800 ms,入口解析和本地处理预留 100 ms,调用库存和价格各需要并行访问,则每个依赖不能都设置 800 ms。应为每跳分配剩余预算、网络传输和重试空间,例如依赖单次 250 ms、最多一次快速重试,剩余时间用于聚合与响应。

总预算 800ms
  -> 服务自身 100ms
  -> 依赖并行窗口 500ms
  -> 降级/序列化/网络余量 200ms

超时设置应沿调用链传播 deadline,而不是每层重新从零开始计算。否则内层请求已经超时,外层还在等待;线程和连接会在用户已放弃后继续占用。

重试放大模型

若入口一次请求调用 3 个服务,每层都重试 3 次,最坏情况下下游可能承受 27 倍请求。正确策略是只在最靠近可恢复错误的边界有限重试,并传播 attempt、deadline 和幂等键。连接超时、连接被拒绝、429、5xx 和业务 4xx 的重试语义应分别配置。

指数退避要加随机抖动,避免同一批请求同时再次冲击依赖。重试队列要有容量与死信,后台重试不能绕过入口限流。

熔断状态机

CLOSED --失败率/慢调用超阈值--> OPEN
OPEN --冷却时间到--> HALF_OPEN
HALF_OPEN --探测成功--> CLOSED
HALF_OPEN --探测失败--> OPEN

小样本下不能立即熔断,需设置最小请求数和时间窗口;探测请求应少量、可控且优先幂等。统计维度要区分超时、业务拒绝、客户端参数错误和下游真正故障,否则正常 4xx 会把熔断器误触发。

熔断与降级

熔断器根据时间窗口内失败率、慢调用率和最小请求数从关闭转为打开,冷却后半开少量探测。降级可以返回缓存、默认值、排队受理或关闭非核心功能,但不能把错误伪装成真实成功。

容易踩坑的地方

超时设得越长不代表越可靠,只会让资源更久被占用。熔断阈值过敏会在小样本下抖动,过迟又无法阻止级联故障,需要压测和故障演练校准。

降级不是返回假成功

可接受降级包括读取上一版缓存、隐藏推荐模块、返回排队受理、使用静态配置或只保留核心字段。不能把扣款未知、库存未知伪装成成功;结果不确定时应返回操作号和查询入口。降级逻辑本身也要测试、监控和设置恢复条件。

隔离与舱壁

按依赖拆线程池、连接池、信号量和队列,避免一个慢合作方占满所有请求线程。隔离容量要与下游真实配额匹配;池太大只会把拥塞转到数据库或网络。对非核心任务使用低优先级和独立队列,故障时先丢弃或暂停。

故障演练指标

演练 DNS 失败、连接超时、响应慢、部分 5xx、返回脏数据和恢复抖动,记录错误率、P99、重试次数、熔断状态、线程/连接池、队列、下游压力和恢复时间。没有演练的“熔断已配置”无法证明级联故障真的被控制。

常见问题

追问:什么时候不应该重试?

参数错误、权限失败、明确业务拒绝、非幂等且无幂等键的操作,以及剩余预算不足时都不应自动重试。

追问:重试和超时哪个先设计?

先定义端到端超时预算,再决定在剩余预算内是否允许有限重试;没有预算的重试等于无限等待和资源泄漏。

追问:熔断打开后所有请求都失败吗?

通常快速失败非核心依赖,并允许少量半开探测;核心业务可走缓存、排队或备用路径。策略必须明确优先级,不能一刀切让整个系统不可用。

追问:限流和熔断可以只选一个吗?

限流保护容量,熔断处理持续失败;下游正常但流量过大需要限流,下游已故障则需要熔断,通常应配合使用。