连载中 16/20

重试风暴:好心如何办坏事

2026-07-15 · 2036 阅读 · 0 评论 · 0 赞

恢复的瞬间,最危险

下游故障恢复的那一刻,往往是最危险的时刻。积压的请求、堆积的重试、等急了不停刷新页面的用户,全在门口排着队——门一开,洪水同时灌进来,刚缓过来的下游二次躺平。重试本是好意,但在下游眼里,无节制的重试就是一次 DDoS。

三重放大

算一笔账,看重试怎么把流量放大。第一重积压放大:10 秒故障期间积压的请求,在恢复瞬间集中涌入,瞬时流量数倍于常态。第二重重试放大:上游失败重试 3 次,同一笔业务最多打出 4 个请求,放大 4 倍。第三重层级放大:调用链三层、每层都重试 3 次,最坏 4×4×4 = 64 个请求,只为同一笔业务。

再叠加用户行为:页面超时,用户手动刷新,又来一轮。10 倍常态流量被放大成上百倍瞬时洪峰——多少「恢复即雪崩」的事故,都是这笔账算出来的。

退避与抖动

第一招是指数退避:第 1 次等 1s、第 2 次等 2s、第 3 次等 4s,重试越来越稀疏,给下游喘息。但光退避还不够——同一时刻失败的所有请求,退避后的重试点完全相同,部队齐步过桥,桥照样塌。所以要再加随机抖动,把重试时间点打散:

long backoff = (long) (baseMs * Math.pow(2, attempt));   // 1s, 2s, 4s...
long jitter  = ThreadLocalRandom.current().nextLong(backoff / 2, backoff);
Thread.sleep(jitter);   // 指数退避 + 随机抖动,别让所有人同一瞬间重试

只重试「值得重试」的

重试之前的前提审查,比重试本身更重要:幂等的才重试——写操作没做幂等保护(分布式事务篇讲过的唯一索引、去重表),重试就是重复下单;可重试的错才重试——超时、503、连接拒绝可以再试,参数错误、余额不足试一万次也是失败;次数封顶——2~3 次足够,重试是补救不是主力,彻底失败还有降级兜底。

重试预算与熔断联动

更精细的做法是重试预算(gRPC 的思路):给整个服务设一个重试额度,比如「重试请求数不超过活跃请求的 10%」——正常时段偶发失败随便重试;下游大面积失败时额度瞬间耗尽,后续请求直连直返,重试自动熄火。再和熔断联动:熔断器 OPEN 期间禁止重试(都快速失败了还试什么),半开态的探测请求走独立通道,不占重试预算。

顺带一提对冲请求(hedging):不等失败就提前发第二个请求,谁先返回用谁——专治长尾延迟,代价是双倍流量,只配给核心接口用。

退避、抖动、预算、联动,纪律都立好了。但回头看看,所有阈值都是人拍的——流量涨了阈值偏小,扩容了阈值偏大。有没有让系统按实时负载自己调阈值的办法?下篇聊自适应保护。

☕
503

10 年全栈工程师 · 503咖啡馆主理人

#重试风暴#指数退避#随机抖动#重试预算#幂等#对冲请求

评论 (0)

热门推荐

连载中 11/22

主从搭建实操:从零配出一主两从

光讲原理不过瘾?手把手搭一主两从:my.cnf 六个参数、复制账号、GTID、CHANGE REPLICATION SOURCE TO、SHOW REPLICA STATUS 验收,附翻车排查清单。

#MySQL#主从复制#GTID#主从搭建#高可用
2026-05-07 · 10101 阅读 · 0 评论 · 0 赞
连载中 16/22

连接池:HikariCP 参数与连接风暴

连接池不是越大越好:8 核机器配 1000 连接反而更慢的数学原理,HikariCP 四个必调参数,maxLifetime 与 wait_timeout 的隐形陷阱。

#MySQL#连接池#HikariCP#maxLifetime#连接风暴
2026-05-10 · 9873 阅读 · 0 评论 · 0 赞
连载中 4/16

缓存穿透:恶意 ID 打穿 MySQL 的四道防线

请求的数据在缓存和数据库里都不存在时,缓存形同虚设。聊聊参数校验、空值缓存、布隆过滤器、限流熔断四道防线的原理与组合打法。

#Redis#缓存穿透#布隆过滤器#高可用
2026-05-16 · 9294 阅读 · 21 评论 · 287 赞