开始前,会这些就够了
- 认识请求与响应;能运行一个 Python 文件
遇到不熟悉的词,先看这 2 个解释
- 重试
- 遇到可能只是暂时性的失败后,按预定规则再次尝试同一项操作;规则通常限制次数、等待间隔和总耗时,也要考虑重复执行会不会多做一次业务。
- 退避与抖动
- 退避是失败后先等一段时间再试,通常还会逐步增加等待上限。抖动让每次等待带一点随机差异,减少所有客户端按同一张时间表一起回来的机会。
假设八个客户端一起遇到临时故障,都约好一百毫秒后重试。它们确实没有立即再发,但一百毫秒后又一起回来。再把间隔翻倍,如果大家继续使用同一张时间表,就可能只是把拥挤搬到了更晚的时刻。
上一篇重试文章关注是否会重复下单。这里假设业务已允许重试,继续问另一个问题:这些重试会在什么时候到达,会不会让正在恢复的服务更难恢复?
想进一步把等待放进整次请求的时间账,可以下载下面的离线练习包。只改总预算,再观察哪一次尝试被挡住;所有时刻都是程序计算的虚拟时间。
把例子带到自己的电脑上
下载完整练习包
需要 Python 3.10 或更新版本;制作时实测 Python 3.14.0,无第三方依赖。使用标准库,不需要安装第三方包。下载 ZIP 后选择“全部提取”,进入能看到 README.md 的文件夹。
下载 ZIP · 5 个文件包里有什么
- demo.py:两种等待策略与总预算的虚拟实验
- verify.py:19 组验证,含 800 种参数组合
- README.md:中文步骤、观察任务与模型边界
- demo.expected.txt:默认完整输出对照
- SHA256SUMS.txt:前四个文件的内容摘要
在这个文件夹打开 PowerShell,逐行运行
python --version
python -B demo.py
python -B demo.py --budget 800
python -B verify.py解压后在含 demo.py 的文件夹打开终端。先看默认时间表,再只改总预算;记录本轮为什么继续或停止。次数包括首试,等待和预定调用耗时都计入同一预算。程序只做虚拟时间加法,不联网、不睡眠,也不代表真实服务压测。
找不到命令或文件时,先按 README 检查环境和所在目录。文中标出的阅读时间只估算文章篇幅,安装环境和动手练习可按自己的节奏进行。
每层都“再试一下”,最里面会收到多少次?
人为设定所有尝试都失败,上一层每重试一次,下一层就重新用满自己的次数。这里只算这种最坏构造。
“3 次尝试”包括第一次,因此最多再重试 2 次。
- 第 1 层3 次1 × 3
- 第 2 层9 次3 × 3
- 第 3 层27 次9 × 3
最内层最多收到 27 次尝试,来自同一个最外层请求。
真实请求可能提前成功、碰到不应重试的错误,或先耗尽时间预算,实际次数会不同。减少重试层数、限制总预算与打散重试时间是不同措施;这个计数实验没有模拟并发、抖动或服务器吞吐量。
把间隔拉长,还要检查是否仍然挤在一起
退避就是失败后先等一段时间;指数退避常让等待上限逐步变大,再设一个封顶值。本例第一次重试的等待上限是一百,第二次二百,第三次四百。单位是虚拟毫秒,不是推荐线上配置。
如果八个客户端每次都等待相同上限,并暂时忽略执行耗时,它们分别在第 100、300、700 毫秒再次出发。这里的数字是累计时刻:第二次等待二百,不代表从最开始算到二百就发送。
给每次等待留一点各自选择的空间
抖动就是让等待时间带有随机变化。本例采用 full jitter:每个客户端每次在零到本轮上限之间,独立选择一个整数等待值。上限仍增长,但它们不再被一张固定时间表强制绑在一起。
随机不保证每次都错开,也可能抽到零或相同值。这个程序固定种子,只为让学习者复现输出;真实客户端若全复用相同种子和相同抽取顺序,反而可能同步。本文算法也不等同于 gRPC 文档中的正负百分之二十抖动,不应把不同策略当成同一公式。
先看时间表,不冒充压力测试
程序安排八个客户端,每个最多四次尝试:第一次加三次重试。它只输出假设所有尝试均失败时的重试时刻,没有发送请求、处理队列或测量吞吐量。对照两张时间表,可以讨论时间分散,却不能宣布“服务器性能提高了多少”。
同一列代表同一个客户端,下一行是在它上次尝试时刻上再加等待。代码用一个固定种子的随机数生成器,依次模拟八个客户端的抽取;这不表示真实客户端应共用种子。程序把每次尝试耗时设为零,真实调用花掉的时间也必须计入整次请求预算。
from random import Random
clients, max_attempts = 8, 4 # Initial attempt plus three retries.
for jitter in (False, True):
rng = Random(7)
times = [0] * clients
print("full jitter" if jitter else "fixed backoff")
for retry in range(1, max_attempts):
ceiling = min(400, 100 * 2 ** (retry - 1))
for client in range(clients):
wait = rng.randrange(ceiling + 1) if jitter else ceiling
times[client] += wait
print("retry", retry, times)
print("three layers, three attempts each, all fail:", 3 ** 3)
怎样运行:Python 3.10+ 标准库;保存为 retry_schedule.py,运行 python retry_schedule.py。
运行后对照结果
fixed backoff
retry 1 [100, 100, 100, 100, 100, 100, 100, 100]
retry 2 [300, 300, 300, 300, 300, 300, 300, 300]
retry 3 [700, 700, 700, 700, 700, 700, 700, 700]
full jitter
retry 1 [41, 19, 50, 83, 6, 9, 68, 12]
retry 2 [134, 168, 64, 212, 60, 18, 90, 123]
retry 3 [348, 203, 187, 258, 342, 235, 120, 412]
three layers, three attempts each, all fail: 27
上面重试一次,下面可能又忙一轮
设入口、服务和存储客户端共三层,每层一次调用最多尝试三次。如果每次下层调用都耗尽尝试后失败,最底层最多会被尝试 3×3×3,也就是二十七次。这里算的是最坏的逐层失败构造,不是每次用户请求都会放大二十七倍。
工程上要先查清客户端库、代理与业务代码谁已经在重试,再给整条调用链定总量和停止条件。单层限制看着不大,叠在一起仍可能超过预算。参数错误通常需要修正参数;结果不确定的业务操作还需要能安全识别重复,随机等一等不会解决这些问题。
恢复需要空间,重试也需要出口
限制最多尝试次数、总等待时间和同时进行的尝试数量,分别控制不同成本。等待超过业务期限后仍继续调用,即使这次成功也可能无人接收结果。应在每轮开始前重新判断是否还有必要、是否还有预算。
观测时同时记原始业务请求数和实际尝试数,才看得见重试增加的工作。某次随机安排比较均匀,只是本次安排的事实;是否改善服务恢复,需要带真实容量与失败模式的测试,本例没有做这类测量。
记住这三点
- 指数退避拉长等待,抖动让各客户端的等待不必完全相同。
- 最多四次尝试包含初次;最多四次重试则不包含,配置时要分清。
- 错开时间不能代替幂等、次数限制和总时间预算。
别把例子用过头
- 随机抽样仍可能碰撞,full jitter 不是所有客户端库共用的算法。
- 多层尝试数相乘描述指定失败条件下的上界构造,不是实测流量。
- 固定种子的学习结果不能作为线上随机策略或容量结论。
自己试一小步
如果所有客户端都恢复为同一个随机种子,并在同样的时间失败,抖动还会带来什么变化?
- 为每个客户端分别创建随机数生成器,先预测种子相同与不同的两组结果。
- 保持尝试次数、上限和初始失败时刻一致,只改变种子安排。
- 把每次尝试的耗时也加入虚拟时间,说明哪些输出变化属于模型的新假设。
做到这些,就算完成
- 区分单轮等待值与从零开始的累计发送时刻。
- 没有用一次分散结果证明之后必然不碰撞。
- 记录总尝试数,并明确初次是否计入。
查原始资料
想确认一个细节,先看这份资料对应的部分,不必从头读完所有文档。
- Exponential Backoff And Jitter (新标签页)↗
Adding Backoff、Adding Jitter:固定退避仍可形成成组调用,加入随机等待使调度分散。
- gRPC:Retry (新标签页)↗
Retry configuration:
maxAttempts、指数退避、可重试状态和该实现的抖动范围。 - gRFC A6:Client Retries (新标签页)↗
Maximum
Numberof Retries:maxAttempts包含最初请求,调用截止线覆盖全部尝试。
找一份资料继续练
先选一项,按下面的范围继续学;不必从头读完整门课程。
Dubbo 中文:一次调用能等多久、失败后重试几次 (新标签页)↗
从超时页的同级目录打开集群容错,读 retries 参数,把配置换算成首次调用加重试的总次数,再回看多层调用怎样放大尝试量。
查看学习准备与访问说明 →AWS:重试请求怎样避免重复办事 (新标签页)↗
在“何时重试”之外补上“重复到达后怎么办”,说明请求编号与业务写入如何一起记录。
查看学习准备与访问说明 →