重试、退避与整体 deadline
重试不是“失败就再发一次”。先按错误是否可恢复分类,再计算剩余 deadline、尝试次数和副作用风险。参数错误、认证失败、权限拒绝和业务校验失败通常应立即返回;限流、短暂过载和网络抖动才可能重试。写操作即使收到超时,也可能已经在服务端成功,不能仅凭客户端没有响应就自动重放。
第一个可运行实验:预算内退避
使用 Python 3.12 保存 deadline.py,运行 python deadline.py。
python
RETRYABLE = {429, 500, 502, 503, 504}
def next_delay(status, attempt, elapsed, deadline, retry_after=None):
if status not in RETRYABLE or attempt >= 3:
return None
delay = retry_after if retry_after is not None else min(2 ** attempt, 8)
if delay < 0:
raise ValueError('negative delay')
return delay if elapsed + delay < deadline else None
assert next_delay(400, 0, 0, 30) is None
assert next_delay(503, 1, 2, 10) == 2
assert next_delay(429, 0, 7, 10, 4) is None
assert next_delay(503, 3, 0, 100) is None
print('deadline policy passed')Retry-After 需要先解析成持续时间;不要直接把任意响应头转换成整数。实际系统还要给下一次请求预留连接和读取时间,并加入抖动,避免大量客户端同时恢复。每次尝试都记录 request ID、attempt、错误分类和剩余 deadline。
变体实验
把比较条件改成 <=,说明为什么剩余时间恰好等于等待时间时仍不应重试:请求本身没有连接、读取和清理余量。再加入一个 side_effect 标记,要求有副作用且状态未知的调用返回人工查询状态,而不是自动重放。
练习
- 把连接超时、读取超时和整体 deadline 作为三个独立字段记录。
- 设计指数退避加随机抖动的公式,并设置最大等待上限。
- 为未知执行状态定义
query_before_retry分支。