本文目录
重试不是失败后的默认动作,而是一笔有上限的容量预算
先为一次用户操作设置端到端截止时间,再从中分配连接、单次调用和退避等待预算;只有预计能够恢复、剩余时间足够且操作具备幂等语义时才重试。网关、服务、SDK 和任务层若各自重试,会把一次请求成倍放大,应选定一个负责层并限制总尝试次数、总耗时和并发重试量。对下单、支付、发券、创建任务等写操作,使用与业务意图绑定的幂等标识保存执行状态与结果;超时并不等于失败,客户端应查询同一业务结果,不能换新标识盲目再提交。验收要同时证明正常成功率、依赖故障时的请求放大上限、重复副作用防护和恢复后的对账能力。
先按错误性质与业务副作用决定是否重试
| 场景 | 处理条件 | 错误做法的后果 |
|---|---|---|
| 连接建立失败 | 确认请求尚未到达业务处理,并且剩余截止时间允许,可由选定层有限重试 | 各层同时补发,依赖恢复前先形成新一轮洪峰 |
| 读请求暂时失败 | 读取语义幂等,错误被判定为瞬时故障,使用有上限的退避与抖动 | 对权限、参数或资源不存在等持久错误反复占用容量 |
| 写请求响应丢失 | 复用同一幂等标识查询或重放同一业务意图,返回已保存的确定结果 | 每次重试生成新标识,造成重复订单、扣款或消息 |
| 依赖过载拒绝 | 尊重服务端恢复提示,限制重试预算;必要时快速失败、排队或降级 | 把限流响应当作立即重试信号,使有效请求更难完成 |
适用边界:先区分瞬时故障、持久错误与结果未知
本文适用于 API 调用链、异步任务和移动端网络波动下的有限重试治理。参数错误、权限拒绝、业务规则冲突和确定的资源不存在,通常不会因立即重试而改变;数据库事务重试、消息重新投递与批处理补偿还需遵守各自产品和业务规则。
超时只表示调用方在等待期限内没有得到确定响应,不证明服务端没有执行。连接中断也可能发生在请求已经被处理、但响应尚未送达之后。对有副作用的操作,必须把“明确失败”和“结果未知”分开:前者可以纠正输入,后者先凭业务标识查询或对账。
HTTP 方法名可以提供语义线索,但不能代替业务设计。RFC 9110 将 PUT、DELETE 和安全方法定义为幂等,并明确非幂等请求不应在未知执行结果时自动重试,除非客户端知道其语义可幂等或能确认原请求未生效。实际接口仍要检查日志、通知、计费等附带动作是否会重复。
- 为每类错误标注是否可能自行恢复、是否已产生副作用以及结果能否查询。
- 将客户端取消、调用超时、服务拒绝、业务拒绝和未知结果分开统计。
用端到端截止时间约束整条调用链
一次页面提交可能经过客户端、网关、业务服务、数据库和第三方接口。每层独立设置一个更长的超时,会让上游已经放弃后下游仍继续占用线程、连接和锁;每层都把完整时间留给重试,又会使总等待远超用户可接受范围。
先从用户或任务允许的总时间开始,预留网络返回、结果持久化与降级处理时间,再给关键依赖分配单次调用预算。向下游传递剩余截止时间,并让取消信号尽量停止尚未开始或已无业务价值的工作;但取消不能作为事务已回滚的证明,写操作仍需查询最终状态。
超时阈值不应直接照搬平均响应时间,也不能为降低超时率无限放宽。应观察代表性负载下的延迟分布、连接建立、DNS、TLS 和连接池等待,识别哪些阶段被客户端库的一个超时覆盖、哪些需要单独配置。跨区域、弱网小程序和 APP 应保留不同网络条件。
- 画出关键路径的总截止时间及每段预算,明确剩余时间不足时停止重试。
- 验证上游取消后,下游在途工作、数据库连接和锁能够在约定时间内释放。
只让一个明确层负责有限重试
客户端、网关、服务框架、数据库驱动和云 SDK 可能自带重试。若调用链每层都尝试多次,最底层收到的请求会组合放大;故障越久,失败流量越可能挤占恢复所需容量。上线前必须盘点默认行为,而不是只检查业务代码里的循环。
为每个依赖指定重试责任层、可重试错误、最大尝试次数、最大总耗时和并发预算。AWS Well-Architected 建议控制重试次数、使用指数退避与抖动,并避免多层重试造成重试风暴;这些原则需要结合所用 SDK 的实际默认值验证,不能假设所有库都相同。
退避让相邻尝试逐渐拉开,抖动用于打散同时失败的客户端;两者都不能解决持续过载。依赖明确拒绝或容量已饱和时,应减少源头请求、快速失败、受控排队或降级。对用户主动重复点击还需按钮状态和服务端幂等共同保护,前端防抖不能作为唯一保障。
- 维护一张客户端、网关、服务、SDK 与队列消费者的重试清单。
- 记录原始请求、额外尝试和被预算阻止的重试,计算实际放大率。
幂等键要绑定业务意图、请求指纹和确定结果
下单、支付、发券、创建 AI 任务或提交审批通常使用 POST,不能因为需要重试就假定天然幂等。客户端为一次业务意图生成稳定标识,后端原子地认领它,并保存处理中、成功或明确失败状态;同一标识再次到达时返回已有结果或一致的处理中状态。
幂等标识应限制在租户、用户或接口作用域,并与规范化后的关键请求参数建立指纹。相同标识携带不同金额、商品或收件人时应拒绝,不能静默复用旧结果。记录的保留期至少覆盖客户端、消息和人工补偿可能再次到达的窗口,过期策略要与业务单号和对账能力配合。
唯一约束可以防止同一业务键重复落库,但外部支付、短信、推送和消息发布可能已经发生。需要确定副作用顺序、事务边界与可查询凭证;无法放入同一事务时,使用可恢复状态和可靠事件记录逐步完成,并让消费者同样按业务标识去重。幂等的目标是稳定业务结果,不是声称代码只执行一次。
- 验收相同键相同参数得到同一业务结果,不新增副作用。
- 验收相同键不同参数被明确拒绝,跨租户或跨用户不能互相命中。
实施步骤:从调用清单到故障注入与灰度
先列出关键入口及其下游调用,盘点所有显式和隐式重试;随后定义错误分类、端到端截止时间、责任层与重试预算,再为关键写操作补齐业务标识、状态查询和结果存储,最后在灰度流量中验证。
改造顺序优先覆盖会产生资金、库存、权限、通知或高成本计算的操作。小程序和 APP 在弱网下可能因用户返回、进程切换或网关超时重复提交;AI 智能体还可能由工具调用框架再次执行动作,因此业务接口本身必须承担最终防重责任。旧系统若没有稳定业务键,可先增加请求台账和对账接口,再逐步收口写入口。
故障注入应在可控环境模拟连接拒绝、响应丢失、长尾延迟、部分成功、限流和依赖恢复。每次只改变一种条件,观察入口请求、下游尝试、幂等命中、业务记录与资源释放;达到停止条件后自动终止,避免测试重试逻辑本身压垮共享环境。
- 先灰度一条高风险写接口,保留关闭自动重试和查询结果的开关。
- 把重试策略、幂等字段、保留期、状态机和人工对账步骤写入接口契约。
验收与风险:证明失败被限制,结果仍可核对
验收不能只比较故障前后的成功率。应在相同业务负载下记录总入口量、各层尝试次数、依赖收到量、超时分布、幂等命中和最终业务结果,证明依赖变慢时额外流量受预算约束,恢复后不会集中补发新的尖峰。
重复提交测试应覆盖并发相同请求、首次已成功但响应丢失、处理中再次到达、记录过期、参数冲突和消息重复投递。对每种结果核对数据库、支付或库存、消息和通知,确认用户看到的状态与权威记录一致;发现未知结果时进入可追踪的查询或人工队列,不能直接标记失败。
降低重试次数可能让部分瞬时错误更快暴露,提高重试次数则增加延迟与容量消耗,没有通用最优值。最终报告应说明哪些依赖仍由第三方控制、哪些副作用无法原子提交、SDK 升级何时需要复核默认策略,以及监控失效时怎样关闭重试或保护入口。
- 故障阶段记录请求放大率、有效完成量、拒绝量和资源水位。
- 恢复阶段检查退避请求、队列任务和人工补偿不会再次重复执行。
需求讨论中的问题
接口超时后马上重试一次,不是最稳妥吗?
不一定。超时可能表示原请求已经执行但响应丢失,也可能来自依赖过载。先确认操作是否幂等、错误是否可能瞬时恢复、剩余截止时间和重试预算;写操作结果未知时,应复用同一业务标识查询或重放。
POST 请求能不能做自动重试?
HTTP 语义不会自动保证 POST 幂等。若服务端用稳定幂等标识识别同一业务意图、校验参数并保存确定结果,调用方可以按契约有限重试;否则可能产生重复副作用。
用了指数退避和抖动,就不会发生重试风暴吗?
不会自动保证。多层重试、无总截止时间、过高尝试次数或持续过载仍会放大流量。还要限定责任层、总预算和并发重试量,并在依赖无法恢复时快速失败或降级。
数据库加唯一索引是否就完成了幂等设计?
唯一索引只能约束它覆盖的数据写入。外部支付、通知、消息和处理中状态仍需可恢复的流程、结果查询与对账;同一键携带不同参数也必须明确拒绝。
接口重试与幂等上线验收清单
可逐项勾选,辅助本次阅读;刷新页面后不保留。
参考资料
技术概念可进一步阅读以下资料;文中的业务场景与实施清单用于方案讨论。
- RFC 9110:HTTP Semantics
核验 HTTP 幂等方法的定义,以及非幂等请求在无法确认原请求是否生效时不应自动重试的边界。
- AWS Well-Architected:Control and limit retry calls
核验限制重试、指数退避、抖动与避免多层重试放大的官方可靠性建议;具体数值需按项目实测。
- Amazon Builders’ Library:Making retries safe with idempotent APIs
核验结果未知时重复副作用的风险,以及以调用方业务标识识别重复意图的设计原则。
需要评估现有系统的性能瓶颈?
提供关键业务路径、真实峰值、服务器与数据库配置,以及现有压测或故障记录,月弦科技可据此梳理优化顺序、实施范围和可验证的验收条件。