客户端超时 2s + 重试 2 次,下游 P99 从 1.8s 飙到 6s:重试放大系数到底怎么算、超时预算该切几段?
该问题的正文、回答与讨论。
背景 我维护一个订单查询网关(Java 17,JDK HttpClient,Tomcat),下游是库存服务(Go,gRPC over HTTP2)。生产环境晚高峰网关到下游的并发连接稳定在 3000 左右。网关侧的调用配置是:单次超时 2s,失败后自动重试 2 次(即同一业务请求最多打 3 次),重试间隔用固定 200ms。下游库存服务平时 P50=120ms、P99=1.8s,CPU 在 4C 容器里平时 40% 上下。
现象 某个晚高峰,下游库存服务 P99 突然从 1.8s 涨到 6s,CPU 从 40% 直接顶到 95%+,同时网关侧开始大量 504。我抓了下下游的 QPS:平时约 8000,出问题时飙到 1.9 万——几乎是平时的 2.4 倍。我直觉是「重试把流量放大了」,但算不清楚到底放大多少,也不知道该把超时和重试怎么配才能既保成功率又不把下游打崩。
已尝试的排查与结果 1)把网关单次超时从 2s 调到 3s:下游 P99 还是 6s,没改善,反而网关自己开始堆积请求; 2)把固定 200ms 间隔重试改成指数退避(200ms/400ms)+ 抖动:放大系数似乎小了一点,但 CPU 还是能到 85%; 3)在网关侧加了个信号量把到下游的并发限制到 1500:下游 CPU 稳住了,但网关侧排队时间暴涨,P99 反而更难看; 4)查了下没有做 deadline propagation——每次重试都是全新的 2s 超时,三段加起来实际最长能占 2s3+200ms2=6.4s。
期望得到的答案形态 1)一个能套数字的「重试放大系数」模型:设入口并发 C、单次超时 T、重试 k 次、重试间隔 d,下游实际承受的 QPS / 并发放大系数怎么估?我现在的 2.4 倍是不是就是 3 次请求 × 某个失败率的结果? 2)超时预算(deadline budget)到底该怎么切:从网关 2s 总预算里,下游调用、重试、退避等待各该留多少,才不会出现「总耗时 6.4s 但用户只等了 2s」的矛盾? 3)退避策略选型:高并发场景下 fixed / exponential / full-jitter 各自的适用边界,以及「重试只对幂等 + 可重试错误(5xx/超时)发起」这条怎么落地才不放大非幂等写操作? 4)有没有一个最小可落地的组合(超时值 + 重试次数 + 退避 + 并发信号量)能同时压住下游 CPU 又不牺牲网关 P99?最好给到具体数字和一段能直接抄的配置。
参考
- 热榜文章 id=23《超时与重试:高并发后端最容易翻车、却最少被认真讨论的两件事》,里面讲的重试风暴和我的现象很像,但没给到我这种「3000 并发 + 2.4 倍放大」的量化算式;
- 热榜文章 id=18《连接池不是越大越好:一次 P99 抖动的排查与调参》,连接池上限和下游 CPU 的关系给了我一点线索。