分布式事务重试幂等控制:网络抖动导致重复扣款?业务流水号 + 状态机拦截!
公司的支付系统出了个大事故。一笔订单因为网络抖动,支付回调超时了,系统重试了扣款。结果扣了两笔——用户投诉,财务对账发现了差异,运营逐笔人工退款。查日志发现,不是网络不通,是"半通"——第一次扣款请求发出去了,银联处理成功了,但响应在回来的路上丢了。系统认为扣款失败,又发了一次。
这种"网络半通"是分布式系统里最危险的情况——操作已经执行了,但调用方不知道。不加重试怕丢,加多了怕重。今天聊聊怎么用业务流水号加状态机,让重试在"安全"的边界内进行。
问题的本质:操作不是天然幂等的
HTTP 的 POST 不是幂等的,数据库的 INSERT 不是幂等的,银行的扣款接口更不是幂等的。如果不做任何处理,同一个扣款请求发两次,钱就会扣两笔。
分布式系统里有太多场景需要重试——网络抖动、超时、服务暂时不可用。重试本身是合理的,问题在于你拿什么来判断"这次重试的请求,上一次有没有已经成功过了"。
答案就是业务流水号。不是数据库自增 ID,不是 UUID,而是一个由业务方生成、全链路唯一、可用来判断"这条请求我见没见过"的标识。
业务流水号:请求的唯一身份证
业务流水号的核心原则:由发起方生成,贯穿全链路,每个环节用它做去重。
订单服务生成 bizNo = "ORD20260614" + 时间戳 + 随机数
│
├─ 调支付网关扣款 → 带上 bizNo
│ └─ 支付网关记录 bizNo,状态=PROCESSING
│
├─ 支付回调 → 更新 bizNo 状态=SUCCESS
│
└─ 重复回调 → 查 bizNo 已是 SUCCESS → 幂等返回,不再扣款
生成规则一般用:前缀 + 日期 + 业务ID + 随机数。保证全集群唯一,不依赖数据库自增。
状态机:让每个 bizNo 只有一条合法的执行路径
光有流水号不够,还得有状态机来约束它的行为。同一个流水号,在不同状态下允许的操作是不一样的。
支付状态机:
INIT → PROCESSING → SUCCESS ✓ 正常流程
INIT → PROCESSING → FAILED ✓ 失败了
SUCCESS → ??? ✗ 已成功,不能再处理
FAILED → INIT ✓ 允许手动重置后重试
基于状态机的幂等控制就很简单:
processPayment(bizNo, amount):
// Step 1: 查流水号状态
record = find(bizNo)
if record == null:
// 第一次见到这个流水号,插入并处理
insert(bizNo, PROCESSING)
调银行扣款()
update(bizNo, SUCCESS)
return SUCCESS
// Step 2: 状态机判断
switch record.status:
case SUCCESS:
return "已付款,幂等返回"
case PROCESSING:
// 正在处理中——可能上次卡住了
// 等一会儿再查一次
return queryLater(bizNo)
case FAILED:
// 上次失败了,可以重试
update(bizNo, PROCESSING)
调银行扣款()
update(bizNo, SUCCESS)
return SUCCESS
注意 PROCESSING 状态的处理——不能直接重试,因为银行那边可能正在处理。等一阵再查,或者调银行的查询接口确认最终状态。
不是所有的重复都需要拦截
有一种情况需要特别处理:确实想发起两笔独立的扣款。 比如用户先下了订单 A 付了 100 块,然后又下了订单 B 付 200 块。这是两个业务行为,不是重试。
区分的方式:流水号要包含业务唯一标识。 比如 PAY_ORD001_TIMESTAMP,不同的订单 ID 生成不同的流水号。这样订单 A 和订单 B 的流水号不同,不会互相拦截。
反倒是同一个订单的扣款,应该用同一个流水号。即使扣款金额变了(比如用户改了订单),也不应该修改流水号——而是应该在状态流转中体现变更。
总结
分布式重试的幂等控制,两层保障:
业务流水号——请求的全局唯一身份证,发起方生成,全链路透传。查流水号就能知道"这条请求我见过没有"。
状态机拦截——不是所有状态都能重试。SUCCESS 的直接返回,PROCESSING 的等结果,只有 FAILED 的才允许重试。
设计上注意三点:流水号包含业务标识避免误拦截;状态机覆盖所有合法流转路径;PROCESSING 状态下别急着重试,先查银行的最终状态。
做到位,网络再怎么抖,钱不会多扣一笔。
有用的话转给还在靠运气处理支付回调的同事。
标题:分布式事务重试幂等控制:网络抖动导致重复扣款?业务流水号 + 状态机拦截!
作者:jiangyi
地址:http://jiangyi.space/articles/2026/06/14/1781421355229.html
公众号:服务端技术精选
评论