3. 幂等核心概念
在支付系统中,重复扣款往往不是“代码写错了”这么简单,而是网络超时、用户重试、消息重投、定时补偿等多种因素叠加的结果。要治理重复扣款,第一步不是写拦截逻辑,而是把幂等这件事拆成可落地的概念体系。本节围绕六个关键词展开:幂等定义、幂等键、唯一请求号、去重表、状态机幂等与业务幂等。
一、幂等定义:同一语义操作执行多次,结果一致
幂等(Idempotence)在支付语境下,指的是同一个业务请求,无论被提交、消费或补偿多少次,对账户余额、订单状态、资金流水产生的最终影响都相同。注意,幂等不等于“不执行”,也不等于“返回相同报文”,它约束的是业务终态。
例如用户发起一笔 100 元支付,因超时连续点击三次。若系统幂等设计正确,最终只会产生一条扣款流水、一次余额减少 100 元;后两次请求要么被识别为重复并直接返回首次结果,要么进入状态机判断后拒绝执行。幂等的本质是:用一次业务语义,抵消多次物理请求。
二、幂等键与唯一请求号:把“同一件事”标识出来
要实现幂等,系统必须能判断“这两次请求是不是同一件事”。这依赖两个概念:
- 幂等键(Idempotency Key):由业务语义抽象出的唯一标识,通常由业务类型 + 业务单号 + 关键维度组成,例如
PAY:ORDER_20240501_001。 - 唯一请求号(Request No):调用方为每次请求生成的全局唯一编号,如 UUID 或雪花 ID,用于识别“这一次物理调用”。
两者关系容易混淆:唯一请求号标识“一次调用”,幂等键标识“一件业务”。同一笔支付被用户重试时,唯一请求号会变,但幂等键必须不变。因此工程上常见做法是:调用方传入业务幂等键,服务端再结合自身生成的请求号做双层校验。
POST /pay/create
{
"requestNo": "req_9f3a7c21e8",
"idempotentKey": "PAY:ORDER_20240501_001",
"orderId": "ORDER_20240501_001",
"amount": 10000,
"currency": "CNY"
}
三、去重表:幂等落地的第一道闸门
去重表是幂等最常用的存储型方案。核心思路:为幂等键建立唯一索引,请求到达时先尝试插入,插入成功说明是首次请求,继续执行业务;插入冲突说明是重复请求,直接返回已有结果或拒绝。
| 字段 | 说明 | 示例 |
|---|---|---|
| idempotent_key | 业务幂等键,唯一索引 | PAY:ORDER_20240501_001 |
| request_no | 首次请求号 | req_9f3a7c21e8 |
| status | 处理状态 | PROCESSING / SUCCESS / FAIL |
| result_snapshot | 首次结果快照 | {"code":"SUCCESS","tradeNo":"T001"} |
| expire_at | 过期时间,控制表膨胀 | 2024-05-08 00:00:00 |
去重表的关键细节:唯一索引必须落在幂等键上;插入与业务执行要控制在同一事务或补偿框架内;重复请求应返回首次结果快照,而不是简单报错,否则调用方仍可能继续重试。
四、状态机幂等与业务幂等:从“防重”到“防错”
去重表解决的是“同一请求重复到达”,但支付系统还有更复杂的情况:不同请求号、不同幂等键,却试图对同一笔订单重复扣款。这时需要状态机幂等与业务幂等。
- 状态机幂等:为订单定义严格状态迁移,如
INIT → PAYING → SUCCESS / FAIL。只有处于INIT的订单才允许发起扣款;已SUCCESS的订单再次扣款直接拒绝。状态迁移本身带条件更新,天然防重。 - 业务幂等:从业务规则层面判断“这笔操作是否还有意义”。例如退款金额不能超过可退余额、同一订单同一渠道只能成功扣款一次。业务幂等是最后一道防线,能拦截语义重复但标识不同的请求。
UPDATE pay_order
SET status = 'PAYING', update_time = NOW()
WHERE order_id = 'ORDER_20240501_001'
AND status = 'INIT'; -- 影响行数为 0 说明状态不允许,直接幂等拒绝
下面这张图展示了从请求进入到最终落库的幂等判断路径,包含去重表、状态机与业务规则三层校验。
五、常见误区与复盘
在实际项目中,幂等设计常踩以下坑:
- 把唯一请求号当幂等键:调用方每次重试都生成新请求号,去重表永远不冲突,重复扣款照样发生。
- 去重表与业务表不在同一事务:去重记录写入成功但业务执行失败,后续重试被误判为重复,导致漏单。
- 只做接口层幂等,不做状态机幂等:不同入口、不同请求号仍可能对同一订单重复扣款。
- 忽略结果快照:重复请求返回“处理中”或报错,调用方继续重试,放大系统压力。
- 去重表无过期策略:数据无限膨胀,查询与写入性能持续下降。
总结来看,幂等定义是目标,幂等键与唯一请求号是标识手段,去重表是存储闸门,状态机幂等控制流程,业务幂等守住语义底线。五者配合,才能把重复扣款从“事后发现”变成“事前拦截、事中可控、事后可查”。