连载中 16/20

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

2026-07-15 · 2033 阅读 · 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)

相关推荐

连载中 12/20

排查四件套:jstack、jmap、jstat、jcmd 的实战分工

jstack 看线程在干什么,jmap 看堆里装了什么,jstat 看运行时在变什么,jcmd 是统一入口。四把刀各管一段,配合着用没有查不动的现场。

#jstack#jmap#jstat#jcmd#排查工具
2026-09-16 · 5 阅读 · 0 评论 · 0 赞
连载中 11/20

GC 日志:把回收过程翻译成人话

一行 GC 日志里塞着七种信息:谁触发的、收了哪、停了多久、活了哪些。加上 -Xlog 配置,再加上日志分析工具,GC 不再是只能盯监控曲线的黑盒。

#GC日志#Xlog#日志分析#GC监控#Full GC排查
2026-09-16 · 13 阅读 · 0 评论 · 0 赞
连载中 10/20

ZGC:亚毫秒停顿是怎么炼成的

百 G 大堆停顿不到一毫秒,靠的是把搬家全部挪到并发阶段——着色指针让引用自带状态,读屏障让搬运中的对象依然可访问。代价是吞吐与内存,收益是停顿与堆大小解耦。

#ZGC#着色指针#读屏障#亚毫秒停顿#分代ZGC
2026-09-15 · 8 阅读 · 0 评论 · 0 赞