请求重试如何避免重复创建订单:幂等键、原子性与结果复用
本文讨论创建订单接口的安全重试,内容由 AI 辅助整理,示例用于说明设计思路,未声称经过生产压测。
超时不代表没有成功
客户端等待创建订单的响应超时,可能是请求没有到达,也可能是订单已经落库、响应却在网络中丢失。直接重发请求,可能创建两笔订单。服务端需要识别这次调用是原请求的重试,还是一次新的下单意图。
HTTP 方法幂等与业务请求幂等需要区分。HTTP 幂等关注相同请求重复执行后的预期效果,并不要求响应完全相同。PUT、DELETE 和安全方法具有幂等语义;POST 没有这一默认保证,但可以通过业务设计支持安全重试。
幂等键标识一次业务意图
客户端在首次提交前生成幂等键,同一次业务操作的所有重试复用该键;新一次下单使用新键。
POST /orders HTTP/1.1
Content-Type: application/json
Idempotency-Key: checkout-7d0b19af
{"sku":"book-001","quantity":1,"currency":"CNY"}
服务端应把键绑定到租户、接口和规范化后的请求参数。同一键第一次购买一本书,重试时却改成两本,应该拒绝并提示参数冲突。每次请求仍需独立检查身份与权限,不能凭知道某个键就读取其他用户的订单。
原子性与结果复用
“先查询、再插入”存在并发窗口:两个请求可能同时查到键不存在。可用数据库唯一约束,例如“租户+接口+幂等键”,争夺执行资格。已完成的请求返回已保存的订单编号和结果;处理中请求可以等待,或返回明确的处理中状态。
若先创建订单,再单独保存幂等结果,中间崩溃就会留下订单已存在、去重记录却缺失的窗口。单库场景可把幂等记录、订单与可复用结果放入同一事务。提交成功后,即使响应丢失,重试也能查回原结果。Redis 锁可以协调部分并发,但不能替代持久化事实。
支付、发消息等外部动作无法由一个本地事务全部覆盖。可以用事务消息表保存待执行事项,由后台重试;下游按稳定业务编号去重。处理中记录需要恢复机制,不能仅因超时就认定原操作失败并再次执行。
重试窗口与验证
去重记录应覆盖约定的重试窗口,但缓存过期不会让历史订单消失。超出窗口的旧请求可要求先查询原订单,或依靠保留更久的业务唯一编号防止重复创建。保留多久、处理中如何响应、失败结果能否重试,都应写入接口契约。
验证至少覆盖同键并发提交、提交后响应丢失、事务提交前后进程退出,以及同键不同参数。检查订单数量与返回订单编号,才能判断重试是否复用了原业务结果。
参考文档:
- RFC 9110,第 9.2.2 节:Idempotent Methods。
- Stripe 官方 API 文档:Idempotent requests。
- 点赞
- 收藏
- 关注作者
评论(0)