重试放大
假设 10% 的请求失败,而每个请求重试两次;失败的 10% 就变成了额外 20% 的流量,而且恰好发生在依赖最无力处理的时候。如果依赖失败率上升,重试可能把已经退化的服务推向崩溃。重试预算把额外流量限制为成功流量的受控比例。
一个简单预算
在滚动窗口内记录成功与重试,只有当 retries < successes * ratio 时才允许重试。10% 的比例、每分钟 1,000 次成功,意味着每分钟最多 100 次重试。
class RetryBudget {
constructor(ratio = 0.1, windowMs = 60_000, now = Date.now) {
this.ratio = ratio;
this.windowMs = windowMs;
this.now = now;
this.successes = [];
this.retries = [];
}
recordSuccess() {
this.prune();
this.successes.push(this.now());
}
tryRetry() {
this.prune();
if (this.retries.length >= this.successes.length * this.ratio) {
return false;
}
this.retries.push(this.now());
return true;
}
prune() {
const cutoff = this.now() - this.windowMs;
this.successes = this.successes.filter((t) => t >= cutoff);
this.retries = this.retries.filter((t) => t >= cutoff);
}
}
退避与截止时间
预算控制的是流量,不是时机。应配合指数退避与抖动,并设置总截止时间,避免重试无限延长请求。带截止时间的重试预算示例 组合了这些关注点。
何时使用
在调用共享依赖的客户端库和服务网格中使用重试预算。与每请求固定重试次数相比,预算能适应整体流量。与熔断器相比,预算减少重试负载但仍允许调用;熔断器在确认故障后停止调用。
陷阱
预算需要共享状态或按实例计数;按实例预算只是近似全局限制,在扇出场景仍可能放大。没有幂等键时绝不重试非幂等操作。同时测量重试次数和最终成功率;一个通过掩盖持续故障来提高成功率的预算并没有起作用。