14.2 截止时间、重试、断路器与过载保护
远程调用一定会遇到慢、断开、部分成功和结果未知。韧性模式的目的不是掩盖所有失败,而是限制失败持续时间、资源消耗和传播范围。
正确的组合顺序通常是:截止时间先限制等待,重试只处理少量瞬态失败,断路器避免持续攻击已退化依赖,隔舱与负载削减保护自身资源。
从端到端截止时间开始
单跳超时只限制某一次调用;端到端 deadline 表示整个用户操作还剩多少时间。
假设入口预算为 800 ms:
入口处理 80 ms
下游 A 预算 250 ms
下游 B 预算 300 ms
序列化、排队和安全余量 170 ms下游不能每层重新获得 800 ms,否则调用链越深,总等待越长。应把剩余 deadline 继续传播,客户端取消后也应尽量停止无价值工作。
超时值来自延迟分布和业务预算,不来自随手选择的整数。过短会制造重试流量,过长会占满线程、连接和队列。
重试必须满足安全条件
适合自动重试的通常是:
- 幂等读取;
- 带幂等键的写入;
- 明确返回瞬态错误且服务端确认未提交的操作。
不应因为“收到超时”就重试非幂等写入。超时意味着结果未知,原操作可能已经成功。
退避可采用指数增长并加入随机抖动:
delay = random(0, min(cap, base × 2^attempt))还需要重试预算:限制额外请求占正常流量的比例。否则依赖容量下降时,重试会把一次故障放大成多倍流量。
重试应尽量放在最了解操作语义的一层。网关、SDK、服务网格和业务代码同时重试,三层各重试 3 次,最坏可能产生 27 次尝试。
断路器保护的是调用资源
断路器根据一段时间内的失败率或慢调用率切换状态:
CLOSED ──失败达到阈值──> OPEN
▲ │
└──探测恢复成功── HALF_OPENCLOSED:正常调用并统计结果;OPEN:快速失败,不继续消耗连接和线程;HALF_OPEN:允许少量探测请求判断是否恢复。
断路器不是健康检查,也不保证业务降级正确。它只决定“现在是否值得继续尝试”。窗口、最小样本数、慢调用阈值和半开探测量都需要结合流量校准。
隔舱、并发限制与负载削减
如果所有依赖共用一个线程池或连接池,一个慢依赖可以拖死整个服务。隔舱为关键依赖或请求类别分配独立资源上限。
队列不是免费容量。请求到达速率长期高于处理速率时,无界排队只会把失败变成更晚的超时。系统应在资源耗尽前:
- 限制并发和队列长度;
- 对低优先级请求快速拒绝;
- 返回可识别的过载信号;
- 保留健康检查、控制面和关键流量的资源。
负载削减比接受所有请求后一起超时更可恢复。
降级必须保持业务诚实
有效降级示例:
- 推荐不可用时返回“暂无推荐”;
- 排行榜读缓存并标注更新时间;
- 非关键通知进入待发送队列。
危险降级示例:
- 支付校验失败时默认通过;
- 权限服务超时时默认授权;
- 结算结果未知时返回“成功”。
降级是业务政策,应由产品和领域共同定义,不能由基础设施库自动猜测。
组合次序与观测
一次调用可以概念化为:
并发许可 → 断路器 → 单次尝试超时 → 有预算的重试 → 结果/降级具体库的装饰顺序会影响统计和资源释放,必须用故障测试确认。至少观测:
- 每个下游的调用量、失败率和延迟;
- 超时、重试次数和重试成功率;
- 断路器状态与拒绝数;
- 并发、连接池、队列和负载削减数;
- 降级结果在总响应中的比例。
平均延迟会掩盖长尾,应关注 p95/p99 和超出 deadline 的比例。
参考资料
- AWS Builders' Library, Timeouts, retries, and backoff with jitter
- Microsoft, Circuit Breaker pattern
- Google Cloud, Retry strategy