在业务系统里,重复请求不是异常边角,而是正常流量的一部分。用户连续点击、客户端超时重试、消息队列重复投递、网关重放请求,都可能让同一个业务动作被执行多次。如果接口没有幂等设计,轻则产生重复记录,重则造成重复扣款、重复发货或状态错乱。
本文讨论的是工程落地层面的接口幂等,不展开分布式事务、强一致协议或特定框架实现。结论先说清楚:幂等不是简单加一个唯一索引,也不是所有接口都要用同一种方案;它需要先定义业务动作的唯一性,再选择合适的约束、状态机和重试响应策略。
适用场景包括订单创建、支付回调、优惠券领取、任务提交、消息消费等“重复执行会产生副作用”的接口。对于纯查询接口,重点通常不在幂等,而在缓存一致性、分页稳定性和权限边界。
一、问题背景
接口幂等值得单独设计,是因为请求层面的“只发一次”在真实系统里无法保证。客户端可能因为网络抖动重试,服务端可能在写库成功后返回失败,消息中间件也可能采用至少一次投递语义。只要业务动作存在副作用,就必须假设同一意图会被系统看到多次。
本文限定讨论服务端接口的防重复执行,重点放在单个业务动作的识别、存储约束、并发控制和返回语义。这里不讨论跨多个微服务的全局事务,也不把幂等设计等同于简单的接口防抖或前端按钮置灰。
一个可落地的幂等方案至少要回答三个问题:什么算同一个业务动作、重复请求应该返回什么、并发到达时谁负责做最终裁决。如果这三个问题没有写清楚,后续代码很容易变成零散补丁。
二、核心思路
先区分“请求重复”和“业务重复”。两次 HTTP 请求内容完全一致,不一定表示同一个业务动作;同一个用户在不同时间购买同一商品,也可能是两个合法订单。幂等键应该表达业务意图,而不是简单表达报文内容。
常见判断方式有三类:
- 客户端生成幂等键:适合下单、提交任务等由客户端主动发起的动作。服务端要求同一用户、同一幂等键只能成功创建一次业务结果。
- 外部事件唯一编号:适合支付回调、物流回调、消息消费等场景。服务端以第三方事件 ID 或消息 ID 作为去重依据。
- 业务唯一约束:适合自然存在唯一性的动作,例如同一用户同一活动只能领取一次优惠券。
设计取舍上,不建议只依赖内存锁或短期缓存。缓存可以减少数据库压力,但最终裁决应该落在持久化约束上。原因很简单:服务重启、缓存淘汰、并发穿透都会让只存在于内存里的判断失效。
返回语义也要提前确定。重复请求不一定要报错;如果第一次请求已经成功,重复请求通常应该返回同一个业务结果。只有当同一个幂等键携带了不同业务参数时,才应该返回明确的冲突错误。
三、落地步骤
第一步,定义幂等范围。常见范围是“用户 + 幂等键”或“租户 + 业务类型 + 外部事件 ID”。不要使用全局单字段,除非可以确认所有调用方共享同一命名空间。
第二步,建立幂等记录表。表中至少保存幂等键、业务类型、请求摘要、处理状态、业务结果引用和时间字段。示例结构如下:
1 | CREATE TABLE idempotency_record ( |
第三步,用数据库唯一约束争抢执行权。请求进入后先尝试插入 PROCESSING 状态记录;插入成功的一方继续执行业务逻辑,插入失败的一方读取已有记录并按状态返回。
1 | 1. 校验必填参数和幂等键格式 |
第四步,把业务写入和幂等状态更新放进同一个本地事务。典型做法是先创建业务记录,再更新幂等记录为成功。如果业务记录已经写入但幂等状态没有更新,系统需要有补偿任务根据业务结果修复幂等记录。
第五步,明确失败处理。参数校验失败通常不需要写入幂等记录,因为业务动作尚未开始。外部依赖超时则要谨慎处理:如果无法确认业务动作是否完成,不要随意删除幂等记录后放开重试,否则可能造成重复执行。
四、常见坑
- 把幂等键设计成服务端随机生成。服务端生成的随机值只能标识一次请求,无法标识客户端的同一次业务意图。
- 只做 Redis
SETNX,没有数据库唯一约束。短期锁可以做流量削峰,但不能承担最终一致的业务裁决。 - 忽略参数冲突。同一个幂等键如果第一次是创建 A 商品订单,第二次变成创建 B 商品订单,应该返回冲突,而不是复用第一次结果。
- 成功后只记录状态,不记录结果引用。重复请求到来时只能返回“已处理”,调用方仍然不知道对应的业务单号。
- 把所有接口都套同一套幂等逻辑。查询、草稿保存、状态覆盖更新、追加型写入的幂等要求不同,统一封装前需要先抽象清楚业务语义。
- 没有处理
PROCESSING超时。处理中的记录如果因为进程崩溃长期停留,需要有超时判定、人工排查或补偿任务,否则后续请求会一直卡住。
五、检查清单
- 是否明确了幂等范围,例如用户、租户、业务类型或外部系统编号。
- 是否区分了请求重复和业务重复。
- 是否为幂等键建立了持久化唯一约束。
- 是否保存了请求摘要,用于识别同键不同参。
- 是否定义了
PROCESSING、SUCCESS、FAILED等状态的返回语义。 - 是否能在重复请求时返回同一个业务结果。
- 是否考虑了业务写入成功但幂等状态更新失败的补偿路径。
- 是否限制了幂等记录的保留周期和清理策略。
六、小结
接口幂等的核心不是挡住重复请求,而是让同一个业务意图在重复到达时只产生一次确定结果。实现上应当把业务唯一性、持久化约束、状态流转和返回语义放在一起设计,而不是在控制器里临时加锁。
落地时可以从高风险写接口开始,例如支付回调、订单创建和消息消费。先把这些接口的幂等键、状态表和重复响应定义清楚,再逐步沉淀成公共组件,会比一开始追求通用框架更稳。
- 本文链接: https://blog.hansong.icu/2026/07/20/daily_post_2026_07_20/
- 版权声明: 本博客所有文章除特别声明外,均默认采用 CC BY-NC-SA 4.0 许可协议。