秒杀订单幂等性靠“状态驱动+控制结构约束”闭环实现:定义INIT、PRE_LOCKED、PAYING、PAID、CANCELLED等细化状态,所有请求统一经stateTransition入口,前置校验、原子更新、后置钩子三重控制,令牌与状态深度绑定,异常通过补偿事件兜底。

秒杀订单全链路的幂等性,核心不是靠单点校验,而是靠“状态驱动 + 控制结构约束”形成闭环防线。关键在于:每个操作必须对应唯一、可验证、不可逆的状态跃迁,且所有入口(如重试请求、前端重复提交、消息重复消费)都必须被同一套状态机拦截和归一化处理。
状态机定义要覆盖业务全生命周期
不能只定义“创建中→已支付→已完成”,而应细化到秒杀特有的中间态,例如:
- INIT:用户进入秒杀页,未点击下单(用于预占库存前的防刷校验)
- PRE_LOCKED:库存预扣成功、生成待支付单,但未调用支付网关
- PAYING:已发起支付请求,等待异步通知或轮询结果
- PAID:支付成功且库存已终态锁定(此时才允许发券/减库存/写订单)
- CANCELLED:超时未支付或主动取消,需触发库存回滚
每个状态必须有明确的进入条件、退出动作和禁止转移路径(如从 PAYING 不允许直接跳到 PAID,必须经由支付回调事件驱动)。
控制结构嵌入状态跃迁的强制守门逻辑
所有外部请求(HTTP接口、MQ消息、定时任务)不直接操作业务数据,而是统一走 stateTransition(orderId, event) 入口。该方法内部包含三重控制:
- 前置校验:检查当前状态是否允许接收该 event(如只有 PRE_LOCKED 状态才接受 PAY_REQUEST)
- 原子更新:用数据库 UPDATE ... WHERE status = oldStatus 实现状态+版本号双校验,失败即拒绝(避免ABA问题)
- 后置钩子:仅当状态真正变更成功后,才触发下游动作(如发MQ、调积分服务),并记录完整跃迁日志(含traceId、event、旧/新状态、耗时)
幂等令牌与状态机深度绑定
前端每次秒杀请求携带唯一业务令牌(如 userId+skuId+timestamp+nonce 的HMAC签名),该令牌在 INIT 阶段就存入 Redis(EX 10min),并在 PRE_LOCKED 状态建立时与 orderId 绑定。后续所有请求必须同时提供 orderId 和原始令牌,状态机在每次跃迁前校验:
- 令牌是否存在且未被 used
- 该令牌是否已关联到当前 orderId(防止 A 订单盗用 B 的令牌重放)
- 若令牌已存在且关联了其他 orderId,则直接返回“重复请求”,不进入状态机流转
这样既防前端重复提交,也防 MQ 消费端因网络抖动导致的重复投递。
异常分支必须收口到状态机兜底
支付超时、库存回滚失败、下游服务不可用等异常,不能裸抛异常或静默忽略。应设计专用补偿事件(如 TIMEOUT_NOTIFY、STOCK_ROLLBACK_FAIL),由定时任务扫描长期处于 PAYING 或 PRE_LOCKED 的订单,触发对应事件驱动状态机进入 CANCELLED 或 RECOVERING,并记录告警。所有补偿动作仍走 stateTransition,确保控制流不逃逸。

















