幂等请求也需要重试预算
目标
构建一个执行器,同时遵守状态码、请求安全性、最大次数和总时间限制。
为什么重要
一次正常请求成功,并不能保证边界值和故障恢复。本实验先以小规模方式实现或测试每个函数的契约,再把它们接入真实运行。评分不只看代码是否存在或报告里的措辞,而是检查结果、异常和存储状态。保留前面步骤的代码,再继续下一步。
步骤
- 在
/root/work/idem-retry-budget-lab/service.py中,让 retryable(status) 只对 429、502、503、504 返回 True,其余状态返回 False。这个列表是本实验的策略,并不是所有服务的标准答案。先用下面的命令做准备。
mkdir -p /root/work/idem-retry-budget-lab
cp /opt/fixtures/practice_depth/idem-retry-budget-lab/* /root/work/idem-retry-budget-lab/
cd /root/work/idem-retry-budget-lab
- 在
/root/work/idem-retry-budget-lab/service.py中,让 replay_safe(method, key=None) 仅对不区分大小写的 GET、HEAD、PUT、DELETE,或带有非空 key 的 POST 返回 True。按该契约,PATCH 不被允许。 - 在
/root/work/idem-retry-budget-lab/service.py中,让 remaining(deadline, now) 返回 deadline-now 与 0 中较大的那个值。参数是单调时钟下以秒为单位的浮点数。 - 在
/root/work/idem-retry-budget-lab/service.py中,让 backoff(attempt, base=0.5, cap=4.0) 返回 min(cap, base*2**attempt)。attempt 的范围是 0–30,base 和 cap 不能为负数,违反时抛出 ValueError。 - 在
/root/work/idem-retry-budget-lab/service.py中,让 jitter(delay, fraction) 返回 delay*fraction。必须满足 delay>=0 且 0<=fraction<=1,违反时抛出 ValueError。 - 在
/root/work/idem-retry-budget-lab/service.py中,让 wait_delay(local, retry_after) 返回 local 与 Retry-After 整数秒中较大的那个值。None、负数、非整数、日期等不支持的值按 0 秒处理。 - 在
/root/work/idem-retry-budget-lab/service.py中,can_attempt(attempts, maximum, wait, budget) 仅在同时满足 attempts < maximum、wait>=0、budget>wait 时返回 True。attempts 是已经执行过的调用总数。 - 在
/root/work/idem-retry-budget-lab/service.py中实现 execute(send, clock, sleep, method='GET', key=None, timeout=5.0, maximum=3, random_fraction=lambda:0.5)。send(剩余秒数) 返回 {status: int, retry_after: 可选值}。一开始就固定 deadline,按上述策略重试,无法再重试时返回最后一次响应。timeout<=0 或 maximum<1 时抛出 ValueError,调用前预算已耗尽时抛出 TimeoutError。
参考
- 软件包已安装在镜像中,不需要联网,也不需要 pip install。
- 准备阶段的复制只做一次。再次复制会把工作文件重置。
- 评分会导入你的 service.py,并使用不同的 id 和临时存储来运行。不要把结果写成常量。
- 每个步骤的评分都在 45 秒内运行。不要写出无限循环,也不要制造真实的等待。
收窄需要重试的响应
在 /root/work/idem-retry-budget-lab/service.py 中,让 retryable(status) 只对 429、502、503、504 返回 True,其余状态返回 False。这个列表是本实验的策略,并不是所有服务的标准答案。先用下面的命令做准备。
mkdir -p /root/work/idem-retry-budget-lab
cp /opt/fixtures/practice_depth/idem-retry-budget-lab/* /root/work/idem-retry-budget-lab/
cd /root/work/idem-retry-budget-lab
对 400、401、409 这类错误重复发送请求,输入、权限、冲突问题并不会自行消失。
可以用 bash /opt/lab/checks/idem-retry-budget-lab/01-contract.sh 手动复现评分。保存文件后重新运行。
判断请求能否再次发送
在 /root/work/idem-retry-budget-lab/service.py 中,让 replay_safe(method, key=None) 仅对不区分大小写的 GET、HEAD、PUT、DELETE,或带有非空 key 的 POST 返回 True。按该契约,PATCH 不被允许。
要区分“服务器提供键的去重能力”这个前提。仅仅在请求头里放一个字符串,并不会让请求变得安全。
可以用 bash /opt/lab/checks/idem-retry-budget-lab/02-contract.sh 手动复现评分。保存文件后重新运行。
共用同一个截止时间
在 /root/work/idem-retry-budget-lab/service.py 中,让 remaining(deadline, now) 返回 deadline-now 与 0 中较大的那个值。参数是单调时钟下以秒为单位的浮点数。
如果每次尝试都重新发放一个新的 timeout,总时间限制就会被放大。
可以用 bash /opt/lab/checks/idem-retry-budget-lab/03-contract.sh 手动复现评分。保存文件后重新运行。
给退避设置上限
在 /root/work/idem-retry-budget-lab/service.py 中,让 backoff(attempt, base=0.5, cap=4.0) 返回 min(cap, base*2**attempt)。attempt 的范围是 0–30,base 和 cap 不能为负数,违反时抛出 ValueError。
attempt=0 表示第一次失败之后的等待。要检查取值范围,避免指数计算无限增大。
可以用 bash /opt/lab/checks/idem-retry-budget-lab/04-contract.sh 手动复现评分。保存文件后重新运行。
让抖动可以确定性地测试
在 /root/work/idem-retry-budget-lab/service.py 中,让 jitter(delay, fraction) 返回 delay*fraction。必须满足 delay>=0 且 0<=fraction<=1,违反时抛出 ValueError。
不要把随机数的生成藏在函数内部,而是注入 fraction,这样就能重现两个端点和中间值。
可以用 bash /opt/lab/checks/idem-retry-budget-lab/05-contract.sh 手动复现评分。保存文件后重新运行。
不早于服务器要求的等待时间重试
在 /root/work/idem-retry-budget-lab/service.py 中,让 wait_delay(local, retry_after) 返回 local 与 Retry-After 整数秒中较大的那个值。None、负数、非整数、日期等不支持的值按 0 秒处理。
如果服务器要求 7 秒,即使本地退避只有 0.5 秒,也要等 7 秒。剩余预算不足时,在下一步放弃。
可以用 bash /opt/lab/checks/idem-retry-budget-lab/06-contract.sh 手动复现评分。保存文件后重新运行。
同时检查尝试次数与等待预算
在 /root/work/idem-retry-budget-lab/service.py 中,can_attempt(attempts, maximum, wait, budget) 仅在同时满足 attempts < maximum、wait>=0、budget>wait 时返回 True。attempts 是已经执行过的调用总数。
如果等待之后剩余时间恰好为 0,就不能再发起新请求。
可以用 bash /opt/lab/checks/idem-retry-budget-lab/07-contract.sh 手动复现评分。保存文件后重新运行。
用模拟时钟验证整个执行器
在 /root/work/idem-retry-budget-lab/service.py 中实现 execute(send, clock, sleep, method='GET', key=None, timeout=5.0, maximum=3, random_fraction=lambda:0.5)。send(剩余秒数) 返回 {status: int, retry_after: 可选值}。一开始就固定 deadline,按上述策略重试,无法再重试时返回最后一次响应。timeout<=0 或 maximum<1 时抛出 ValueError,调用前预算已耗尽时抛出 TimeoutError。
响应的耗时也要计入时钟。第一次失败对应 backoff(0)。sleep 之后要再次检查时间,并缩短传给 send 的 timeout。
可以用 bash /opt/lab/checks/idem-retry-budget-lab/08-contract.sh 手动复现评分。保存文件后重新运行。