重试三次,可能把故障放大九次
接口超时,重试一下。这句话听上去很合理,问题是调用链里的每一层可能都这样想。
设想 A 调用 B,B 调用 C。A 对一次操作最多尝试三次,B 对每次来自 A 的调用也最多向 C 尝试三次。如果每次都失败,一次操作最多会给 C 带来九次尝试。这里“三次”包含第一次调用;如果配置写的是“额外重试三次”,数字还会变成十六。
AWS 对超时与重试的讨论指出,多层独立重试会放大底层负载。在设计里,次数的含义和重试的层级都需要明确。
超时留下的是一个问号
客户端没收到结果,不等于服务端没执行。一个创建订单请求可能已经写入数据库,只是返回途中断了连接。换一个新请求再创建,可能得到第二笔订单。
对有副作用的操作,首先要定义重复请求的语义。AWS 的幂等 API 文章讨论了用调用方提供的标识表达同一次请求意图。
在假设的订单接口里,可以让同一次操作的所有尝试携带相同的幂等标识。服务端需要把标识与请求参数、执行状态或结果关联起来,处理并发重复请求,并规定记录的保留时间。只在缓存里随手放一个“处理过”的布尔值,还不足以解释崩溃和超时后的行为。
等待不能挤出总预算
重试策略不仅是次数,也包括每次调用的等待时间和两次尝试之间的间隔。
假设一次操作只允许等待两秒,而每次下游调用都能等待两秒,那么串行尝试三次显然无法守住这个约束。应从总截止时间分配预算,并确认客户端的超时设置到底覆盖连接、请求还是完整操作。
退避可以拉开尝试间隔,随机抖动可以减少许多客户端同时再次出发的情况,但两者都不能凭空增加下游容量。它们仍需受总时间和总尝试次数约束。
别把所有失败都送回队列
参数不合法、权限不足,重复提交相同请求通常不会改变答案。限流或暂时不可用可能值得稍后再试,也应遵守接口约定的等待提示与截止时间。
具体错误是否可以重试,需要由接口契约定义。不要只靠“不是成功”这个条件,也不要让 SDK、业务服务和任务调度器分别无限补救。
可以记录原始操作数量与实际尝试数量的比值,再看重试带来的成功增量。如果失败增加时,尝试数量迅速上升,成功数量却没有改善,就该检查重试是否正在拖慢恢复。
一个完整的重试设计,要能说明重复执行是否安全、总共愿意花多少时间、由哪一层负责,以及什么时候停止。