14.2 截止时间、重试、断路器与过载保护
远程调用一定会遇到慢、断开、部分成功和结果未知。韧性模式的目的不是掩盖所有失败,而是限制失败持续时间、资源消耗和传播范围。
设计时先确定整个操作的截止时间,再决定哪些失败值得重试,以及断路器、隔舱和负载削减各自保护哪些资源。这是推理顺序,具体装饰器并不存在通用的唯一排列。
从端到端截止时间开始
单跳超时只限制某一次调用;端到端 deadline 表示整个用户操作还剩多少时间。
假设 A、B 串行调用,整个操作的入口预算为 800 ms:
入口处理 80 ms
下游 A 预算 250 ms
下游 B 预算 300 ms
序列化、排队和安全余量 170 ms这里四项相加为 800 ms;若 A、B 并行,等待预算应按关键路径计算。下游不能每层重新获得 800 ms,否则调用链越深,总等待越长。应传播剩余预算。本地可用单调时钟计时,但不能把其原始时间戳发给另一台机器;协议必须处理时钟差异,例如 gRPC 会传播扣除已用时间后的超时值。
客户端取消后应尽量停止无价值工作,但取消需要服务端配合,也不能撤销已经提交的写入。检查所用客户端的超时是否覆盖连接、DNS、TLS 和响应读取,不能只给其中一个阶段计时,却称它为总预算。
超时值来自延迟分布和业务预算,不来自随手选择的整数。过短会制造重试流量,过长会占满线程、连接和队列。
重试必须满足安全条件
适合自动重试的通常是:
- 幂等读取;
- 带幂等键的写入;
- 明确返回瞬态错误且服务端确认未提交的操作。
不应因为“收到超时”就重试非幂等写入。超时意味着结果未知,原操作可能已经成功。
退避可采用指数增长并加入随机抖动:
delay = random(0, min(cap, base × 2^attempt))这里 attempt = 0 表示第一次重试,random 在给定区间内均匀取值,base 和 cap 使用相同时间单位。服务端给出 Retry-After 时,应结合它和剩余截止时间决定是否再试;若等待与下一次尝试已没有预算,就停止。鉴权失败、参数错误、断路器拒绝和本地过载不应被统一当作瞬态网络错误重试。
还需要重试预算:限制额外请求占正常流量的比例。否则依赖容量下降时,重试会把一次故障放大成多倍流量。
重试应尽量放在最了解操作语义的一层。假设网关、服务 SDK 和业务代码三层都允许最多 3 次总尝试(含首次),最深处最坏会收到 3 × 3 × 3 = 27 次调用。若配置的是每层额外重试 3 次,则是 4 × 4 × 4 = 64 次。配置名里的 retries 与 maxAttempts,差的可能不只一个单词。
断路器保护的是调用资源
断路器根据一段时间内的失败率或慢调用率切换状态:
CLOSED ──失败达到阈值──> OPEN
OPEN ──等待期结束────> HALF_OPEN
HALF_OPEN ──探测满足成功条件──> CLOSED
HALF_OPEN ──探测失败──────> OPENCLOSED:正常调用并统计结果;OPEN:新调用快速失败,不再为它们发起远程请求;已经在途的调用不会因此自动取消;HALF_OPEN:允许少量探测请求判断是否恢复。
断路器不是健康检查,也不保证业务降级正确。它只决定“现在是否值得继续尝试”。窗口、最小样本数、慢调用阈值和半开探测量都需要结合流量校准。
隔舱、并发限制与负载削减
如果所有依赖共用一个线程池或连接池,一个慢依赖可以拖死整个服务。隔舱为关键依赖或请求类别分配独立资源上限。
队列不是免费容量。请求到达速率长期高于处理速率时,无界排队只会把失败变成更晚的超时。例如持续到达 120 个请求/秒、只能完成 100 个/秒,在这个忽略波动的模型中,100 个位置的空队列仅需 5 秒就会填满;队列没有增加吞吐量。系统应在资源耗尽前:
- 限制并发和队列长度;
- 对低优先级请求快速拒绝;
- 返回可识别的过载信号;
- 保留健康检查、控制面和关键流量的资源。
负载削减比接受所有请求后一起超时更可恢复。
降级必须保持业务诚实
有效降级示例:
- 推荐不可用时返回“暂无推荐”;
- 排行榜读缓存并标注更新时间;
- 非关键通知进入待发送队列。
危险降级示例:
- 支付校验失败时默认通过;
- 权限服务超时时默认授权;
- 结算结果未知时返回“成功”。
降级是业务政策,应由产品和领域共同定义,不能由基础设施库自动猜测。
组合次序与观测
下面展示一种按单次尝试隔离资源的嵌套方式:
整个操作的截止时间
└─ 有预算的重试循环
├─ 每次尝试:检查断路器 → 获取并发许可 → 限时调用 → 释放许可
└─ 确认可以重试且预算足够 → 退避等待
→ 结果 / 明确的降级这里退避期间不占下游并发许可;若把许可包在重试循环外,限制的则是整个逻辑调用,等待也会占位。两者控制的资源不同。还应避免把本地许可不足算作下游故障;若先取得半开探测资格却未能真正调用,要按库的机制归还资格。具体库的装饰顺序会影响统计和资源释放,必须用故障测试确认。至少观测:
- 每个下游的调用量、失败率和延迟;
- 超时、重试次数和重试成功率;
- 断路器状态与拒绝数;
- 并发、连接池、队列和负载削减数;
- 降级结果在总响应中的比例。
平均延迟会掩盖长尾,应关注 p95/p99 和超出 deadline 的比例。
参考资料
- AWS, Retry with backoff pattern
- AWS, Exponential Backoff and Jitter
- Microsoft, Circuit Breaker pattern
- Google Cloud, Retry strategy