仅靠数据库唯一索引不足以防重复提交,因其无法拦截并发请求在校验、生成订单号等前置阶段的并行执行,且无法支持带时间窗口等复杂业务规则;真正可靠的幂等性需在请求入口通过idempotency-key+Redis缓存前置判重,并结合DB幂等表保障强一致性。

为什么直接用数据库唯一索引还不够
很多开发者以为加个 UNIQUE 约束就能解决重复提交,但实际中用户点两次“提交订单”,可能在极短时间内发出两个请求,都还没走到数据库插入那步——比如都在校验库存、生成订单号阶段就并发执行了,结果还是生成两单。更麻烦的是,某些业务逻辑无法靠单字段唯一约束覆盖(比如“用户A在10分钟内只能发起一次退款”,这种带时间窗口的规则没法用索引硬控)。
真正可靠的幂等性得在请求入口就识别并拦截重复动作,而不是等冲突发生再回滚。
用 idempotency-key 请求头 + 缓存做前置判重
这是目前最通用、低侵入的做法:客户端每次发起幂等请求时,必须带上一个全局唯一的 idempotency-key(比如 UUIDv4),服务端收到后先查缓存(Redis 最常用),如果该 key 已存在且状态是 success 或 processing,就直接返回上次结果或 409 Conflict;只有首次见到这个 key 才放行执行业务逻辑,并在执行前把 key 写入缓存、标记为 processing,执行完再更新为 success + 存储响应体。
-
idempotency-key必须由客户端生成(服务端不生成),否则无法保证跨请求一致性 - 缓存过期时间要明显长于业务最大执行耗时(比如业务最长跑 30s,缓存设为 5 分钟),避免因超时导致误判为新请求
- 务必用 Redis 的
SET key value EX seconds NX原子操作写入processing状态,防止并发写入竞争 - 若业务执行失败,应将状态设为
failed并保留错误信息,方便客户端判断是否可重试
如何处理「执行中」状态下的重复请求
用户第二次带着相同 idempotency-key 请求进来时,不能简单返回 409,尤其当第一次请求还在跑(比如调第三方支付接口卡住了)。这时候应该返回 409 + Retry-After: 2 头,或者更实用的做法:主动轮询缓存中的执行状态,直到变成 success 或 failed 后透传结果。但注意,轮询逻辑不能放在主请求链路里——得用异步任务或前端轮询配合。
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
立即学习“Python免费学习笔记(深入)”;
- 不要在同步接口里阻塞等待另一个请求完成,会拖垮吞吐量
- 如果业务允许,可让第二次请求立即返回
{"status": "processing", "idempotency_key": "xxx"},由客户端决定是否轮询 - Redis 中存储的 processing 状态建议附带一个时间戳,超时(如 2 分钟)未更新就自动降级为可重试
哪些场景不适合纯缓存方案
涉及强一致性读写的场景,比如银行转账、库存扣减,仅靠 Redis 判重不够——万一缓存写成功但后续 DB 扣减失败,而 Redis 状态已标记 success,下次请求就会被错误放过。这时需要引入「两阶段提交」思想:先用缓存占位,再在 DB 事务中写入幂等记录表(含 idempotency_key、request_hash、status),最后以 DB 记录为准。缓存只作快速通路,DB 才是权威。
- 幂等记录表必须和业务表在同一数据库、同一事务中写入,否则失去原子性
-
request_hash可以是请求 body 的 SHA256(排除时间戳等动态字段),用于识别语义重复而非仅 key 相同 - 这种方案性能略低,但对资金/库存类操作是必要代价
幂等性真正的难点不在代码怎么写,而在怎么定义“重复”——是同一个 key?同一组参数?还是同一业务意图?这需要和产品一起明确边界,而不是堆技术方案。

















