每个客户端只重试几次,为什么服务还是被拖垮了? 假设一次网络波动让不少请求超时。客户端没有立刻报错而是自动再试单看一台手机这个设计很体贴。可网络恢复以后服务并没有马上恢复。新请求还在等旧请求陆续超时客户端继续重试。原本只需要处理一轮的工作现在来了好几轮。“每个客户端最多重试两次应该不多吧”两次对于一个用户确实不多。但服务端同时面对很多用户还可能面对网关和其他服务发起的重试。局部看合理的动作合在一起就不一定了。多试一次要乘上正在受影响的用户数看一个纯粹用于说明数量的例子一万个客户端各发起一次操作都没有在等待期限内得到结果策略允许每个客户端最多追加两次尝试。如果全部耗尽尝试次数客户端总共就会发起三万次请求其中两万次属于重试。实际数量可能更少有些客户端提前成功并停止有些请求也没能送达服务端。但这个上限说明了潜在流量的来源。需要分开统计两个数用户原本想做多少件事系统为了这些事实际尝试了多少次。更麻烦的是时间分布。同样两万次额外请求分散到很长一段时间与集中在几秒内到达对服务的压力不同。只看整分钟平均请求量可能看不见短时间的尖峰。当然不是每次移动网络波动都会拖垮服务。要看受影响的范围、请求是否到达、服务剩余容量以及重试怎样安排。这里讨论的是可能形成的放大过程不是把重试本身判为错误。客户端不等了服务端可能还在忙超时表示调用方已经等够了不自动意味着另一端的工作已经停止。服务端可能已经收到请求正在查询数据或等待其他依赖也可能已经处理完只是响应没及时回到客户端。第二次尝试到来时第一次工作仍可能占着资源。如果请求持续变慢它们就会在系统里停留更久。以一个稳定流量的简化估算为例每秒进入 100 个请求平均停留时间从 0.1 秒增加到 2 秒同时在途的请求量就可能从约 10 个增加到约 200 个。真实故障过程未必稳定但资源占用为什么会增加就容易理解了。这时再增加请求有可能继续拉长等待。等待变长又触发更多超时重试于是变成了故障持续的一部分。原来的网络问题解除额外负载却未必立刻消失。盲目把超时调大也不是答案。用户会等得更久在途工作可能继续积累。等待期限需要与业务时效和资源承受能力一起考虑。每一层都重试次数可能是乘出来的设想一条简化链路客户端请求网关网关请求业务服务业务服务再请求下游。如果这三个调用点各允许最多三次尝试包含第一次而且每次都走到下游、每层都耗尽尝试次数那么一次用户操作最多可能触发 3 × 3 × 3也就是 27 次下游调用。这是特定假设下的上限示例不是每条链路都会出现的固定倍数。提前成功、取消和总期限都会改变实际数量。但它能解释一个常见盲点每个模块都只加了一点容错最后承压最重的依赖却收到了成倍工作。AWS 的工程说明也专门讨论了多层重试的乘法放大。所以设计时要知道哪些层已经在重试包括网络库或 SDK 的默认行为。不同层负责不同故障可以有道理但次数和期限需要协调不能各自加上一个看起来保险的数字。大家都等几秒还是可能一起回来立即重试容易产生冲击。那就统一等五秒行不行如果一批客户端几乎同时失败又都等五秒它们很可能在下一轮再次集中出现。每次失败后把间隔拉长可以降低尝试频率但所有客户端遵循相同节奏时仍可能形成一波波请求。随机错开重试时间就是为了减少这种同步。有人早一点有人晚一点让后续尝试更分散。这里的随机抖动是主动安排时间与网络延迟忽高忽低是两回事。错峰不能凭空增加服务容量。如果持续进入的原始业务已经超过处理能力即使所有重试都分布得很均匀系统仍可能过载。它也不意味着越晚越好。一个需要及时反馈的操作与可以延后完成的后台同步不该使用完全相同的等待策略。给重试一个总预算“每次最多等多久”和“整个操作最多折腾多久”是两个不同的约束。如果每次重试都重新得到完整等待时间用户实际等到的总时长可能远超页面原本承诺的范围。总期限应该把尝试之间的等待也算进去剩余时间不够时就不再开启一轮注定难以完成的工作。次数上限之外还可以限制一段时间内允许产生多少额外尝试。单个客户端的预算有助于控制自身行为但不能代替服务端的整体容量保护。错误类型也要区分。暂时连接失败可能值得再试参数本身不合法原样重发通常没有意义。服务端明确给出限流或稍后再试的信息时应按接口约定处理不能把所有非成功响应都当作立即重发的理由。还有一条边界操作能安全重复不代表重复没有成本。即便业务通过幂等机制避免了重复创建入口鉴权、查找已有结果和返回响应仍可能消耗资源。幂等保护业务结果负载控制还得另外做。一台真机能验证到哪一步单设备弱网测试很适合观察客户端行为失败后有没有重试间隔是否合理取消后是否停止以及总等待期限有没有被一轮轮刷新。但一台手机表现正常不能证明大量客户端一起恢复时也正常。这需要在授权、隔离的测试环境里配合受控负载逐步验证而不是把线上服务当试验场。可以围绕几种不同情况安排对照条件重点观察请求未到达服务端就失败客户端尝试次数以及真正到达服务端的请求数服务端已处理但响应延迟或丢失重试是否带来重复工作业务结果是否正确服务端处理变慢在途请求与重试流量是否持续增长一批客户端集中恢复连接恢复瞬间的请求峰值以及多久回到正常水平这些条件不能互相替代。只在客户端阻断所有出站请求服务端可能根本没有收到负载不能据此验证服务端能否承受重试风暴。观察请求放大倍数时也要说明统计在哪一层。客户端发起次数、网关收到次数和下游执行次数可能不同。可以用同一批逻辑操作关联记录再分别计算避免拿不相干的分母得出一个漂亮数字。恢复测试要看一段时间网络重新可用只是恢复的开始。积压请求、已经安排好的重试以及新进入的正常业务还会继续争用资源。因此测试不能停在第一条请求成功的时刻。要继续观察重试是否逐渐减少、积压能否消退以及新操作是否恢复到可接受的等待时间。评价方案时也别只奖励“重试少”。如果把重试全部关掉额外流量当然会下降但原本可以恢复的任务可能全失败了。需要同时看成功完成的业务数量、用户等待和服务负载。一份有用的报告应该能回答这次波动影响了多少原始操作额外产生了多少尝试哪些尝试帮助恢复哪些只是重复消耗。下次再讨论“最多重试两次够不够”可以把调用链和受影响的客户端数量一起摆出来。服务端面对的从来不只是那一台手机的两次尝试。