先说结论
超时限制单次等待,重试处理短暂失败,熔断在持续失败时快速阻止调用,降级提供可接受的替代结果。正确顺序是先定义端到端预算,每跳超时小于剩余预算;只对幂等且可能恢复的错误有限重试;熔断后快速失败并进入降级。
四者必须配合隔离和限流,否则线程、连接和队列仍可能被慢依赖耗尽。
重试策略
使用指数退避加随机抖动,限制次数和总耗时,并尽量只在一层重试,避免调用链每层三次形成指数放大。写操作携带幂等键,明确超时后“结果未知”的查询或补偿路径。
端到端超时预算
假设用户请求总预算 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、重试次数、熔断状态、线程/连接池、队列、下游压力和恢复时间。没有演练的“熔断已配置”无法证明级联故障真的被控制。
常见问题
追问:什么时候不应该重试?
参数错误、权限失败、明确业务拒绝、非幂等且无幂等键的操作,以及剩余预算不足时都不应自动重试。
追问:重试和超时哪个先设计?
先定义端到端超时预算,再决定在剩余预算内是否允许有限重试;没有预算的重试等于无限等待和资源泄漏。
追问:熔断打开后所有请求都失败吗?
通常快速失败非核心依赖,并允许少量半开探测;核心业务可走缓存、排队或备用路径。策略必须明确优先级,不能一刀切让整个系统不可用。
追问:限流和熔断可以只选一个吗?
限流保护容量,熔断处理持续失败;下游正常但流量过大需要限流,下游已故障则需要熔断,通常应配合使用。