作者:温若衡-259|分布式系统 / 安全加固|8 年 栈:Spring Boot 3.2.4(内嵌 Tomcat 10.1.19)、MyBatis-Plus 3.5.5、MySQL 8.0.36(InnoDB)、Nginx 1.25.4 + 某云 WAF、Python + sqlmap 1.7.5(复现) 结论先行:默认重试的假设是错误的。5xx 不等于「失败」,重试可能引发重复请求,破坏业务幂等性。需要明确重试条件、区分错误类型、实现服务端幂等性。
一、现象:一个「看起来无害」的默认重试,引发业务瘫痪
最近主持复盘一起 P2:订单服务调支付网关偶发 5xx,框架默认开了「失败重试 3 次」,看起来是无害的兜底。结果支付侧当晚收到了约 4 倍的重复请求,风控直接把我们拉黑 30 分钟,业务全链路瘫痪。事后一查:支付网关对部分 5xx(其实是「处理中,结果未定」)返回了 503,而我们的重试逻辑只认 HTTP 5xx 为「失败」,导致重复请求。
二、根因:为什么「默认重试」是错误的假设
1. 5xx 不等于「失败」
503 是「处理中,结果未定」,不是「失败」。重试会导致重复请求,破坏业务幂等性。例如,支付网关未实现幂等性,重复请求会触发多次扣款,导致业务异常。
2. 未区分重试条件
重试逻辑未区分「明确失败」与「暂时不可用」。重试只对 5xx 重试,但 503 是「暂时不可用」,重试会导致重复请求。
3. 未考虑服务端的幂等性边界
支付网关未实现幂等性,重复请求会触发多次处理,导致业务异常。需要服务端支持幂等性,如使用唯一请求 ID 保证重复请求只处理一次。
三、解决路径:明确重试条件、区分错误类型、实现幂等性
1. 明确重试条件
- 只对「明确失败」重试:如 500、400 等明确失败的错误。
- 对「暂时不可用」不重试:如 503,使用指数退避 + jitter 等策略。
2. 服务端实现幂等性
- 支付网关需支持幂等性,如使用唯一请求 ID 保证重复请求只处理一次。
- 业务端需在请求中携带唯一标识,确保重复请求不导致业务异常。
3. 客户端重试策略
- 对 503 使用指数退避 + jitter,避免短时间内重复请求。
- 对 429 使用退避重试,但优先信 Retry-After 头。
四、参考与建议
- 《超时与重试:高并发后端最容易翻车、却最少被认真讨论的两件事》:讨论重试的边界与幂等性。
- 《429 到底是「不可重试」还是「必须带退避的可重试」?》:讨论重试风暴的边界与退避策略。
建议:
- 重试逻辑需区分错误类型,避免对「暂时不可用」的错误重试。
- 服务端需实现幂等性,确保重复请求不导致业务异常。
- 客户端重试策略应结合业务场景,使用指数退避 + jitter 避免重试风暴。
0赞
0收藏
0评论
2浏览