接口幂等性设计:为什么重试三次,订单变成了三个

举报
yd_238022924 发表于 2026/11/05 00:21:54 2026/11/05
【摘要】 重复请求是分布式系统的默认行为,不是异常。用幂等令牌、唯一约束和状态机三层设防。

接口幂等性设计:为什么重试三次,订单变成了三个

一个很常见的事故

用户点了「提交订单」,页面转圈,于是又点了一下。三秒后,他发现自己买了两份同样的东西。

更糟的是自动重试的场景:客户端超时重发、网关失败重投、消息队列重复消费——这些在分布式系统里都是默认行为,不是异常。

幂等性要解决的就是这件事:同一个操作执行一次和执行多次,结果必须一样。

先分清「幂等」和「重复」

很多人一上来就说「加个唯一索引就行了」。唯一索引能挡住重复插入,但挡不住重复的业务影响。

举个例子:扣库存。第一次请求扣了 1 个、返回超时、客户端重试。加唯一索引能拦住第二笔订单记录,但库存已经被扣了两次——因为扣库存这个动作发生在写订单之前。

所以幂等的关键不在「防重复记录」,而在识别这是一次重试,并让后续处理短路。

方案一:幂等令牌

最直接的做法是让客户端在发起请求时自带一个唯一标识。

流程是这样的:

  1. 客户端生成一个 UUID,作为 Idempotency-Key 放在请求头里
  2. 服务端先查这个 key 是否处理过
  3. 没处理过 → 落库占位,正常执行业务,把结果一起存下来
  4. 处理过 → 直接返回上次的结果,不再执行业务

这里有个细节值得注意:占位和业务执行必须能区分状态。如果只记「已处理」,那么在占位之后、业务完成之前挂掉,后续重试会拿到一个空结果。所以要记状态机:处理中 / 成功 / 失败,只有 成功 才回放结果,处理中 应该返回「请稍后重试」而不是假装成功。

方案二:数据库唯一约束——真正的兜底

幂等令牌是应用层的,应用层会出错、会被人绕过。所以最后一道防线应该在数据库。

常见做法是给业务表加一个业务唯一键,比如「订单号 + 用户 ID」。哪怕上游逻辑写错、并发打进来两笔,数据库会直接拒绝第二笔。

关键在于捕获这个唯一冲突,并把它翻译成「已存在」而不是 500。很多系统卡在这里:重复请求返回 500,客户端以为失败继续重试,雪崩。

方案三:状态机——用状态本身做幂等

订单表通常有状态字段:待支付 → 已支付 → 已发货。

把这个状态迁移写成带条件的更新:

UPDATE orders
SET status = '已支付'
WHERE id = ? AND status = '待支付'

影响行数为 1,说明这次迁移生效;为 0,说明状态已经不是待支付了,直接返回成功即可——因为目标状态已经达成。

这种写法天然幂等,而且不需要额外的表。它的局限是只适用于「状态单向推进」的场景。

什么时候不需要幂等

不是所有接口都值得做幂等。

查询接口天然幂等,不用管。而「创建一条独立记录」这类接口,比如发一条评论——用户发两次就是两条评论,那是业务需求,不是 bug。

需要幂等的是那些重复执行会造成不可逆后果的操作:扣款、扣库存、发券、发短信、创建订单。

判据可以简化成一句话:如果这个操作重放一次会让人损失钱或数据,就必须做幂等。

一个容易忽略的细节:幂等键的有效期

幂等令牌不能永久保存,否则表会爆炸。常见的做法是保留 24 小时到 7 天。

但过期时间要跟「重试窗口」对齐。如果客户端可能在 3 天后补偿重试,而你 24 小时就把令牌删了,那这次的幂等就失效了。

保守的做法是:令牌过期时间 ≥ 业务的最长重试周期。

小结

幂等不是「加个 if 判断」,而是一套组合:

  • 应用层用幂等令牌识别重试,短路重复处理
  • 数据库用唯一约束兜底,防止并发穿透
  • 业务层用状态机保证迁移只发生一次

三者不是二选一,而是层层设防。只做一层,总会有场景漏出去。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。