如果让你实现一个分布式事务的补偿机制,应该注意哪些问题?补偿失败了怎么办?
- 标签:后端、系统设计、场景题、分布式事务
🏛 业务架构师
分布式事务补偿机制(Saga/TCC 的 Cancel、逆向冲正),我重点讲容易踩的坑和补偿失败的终极出路——前半段是技术,后半段是业务兜底设计。
设计补偿机制的注意点
1. 补偿必须幂等 补偿因超时而"未确认",框架会重试——补偿动作重复执行必须无害(退款按唯一流水号去重)。幂等做不到,补偿机制本身就会制造资损(退两次款)。
2. 补偿要处理"未开始"和"已完成" 正向操作还没执行就要补偿(空回滚):Cancel 发现 Try 没执行过,直接标记成功返回;正向已生效补偿回滚:正常路径。用事务状态表记录每个分支的执行进度,补偿按进度决定动作。
3. 补偿的顺序与原子性 多步逆向按正序的反向逐个执行(先退款后释放库存),每步可独立重试;不做"补偿的事务"——补偿本身失败靠重试,不嵌套事务。
4. 防悬挂 Cancel 先于 Try 到达(网络乱序):Cancel 记录"已回滚"标记,迟到的 Try 检查标记拒绝执行——否则 Try 成功后无人补偿,悬挂是 TCC 最隐蔽的坑。
5. 退避与上限 补偿重试用指数退避(1s/2s/4s...),上限(如 10 次/24 小时)后转人工——无限自动重试没有意义,持续失败说明有结构性问题。
补偿失败怎么办(题眼)
- 死信 + 告警:失败的补偿进死信队列,值班响应
- 人工工单:自动补偿超限自动生成工单,附带完整上下文(订单信息、失败步骤、日志),让人工处理从"查问题"变成"点确认"——补偿平台要把人工效率设计进去
- 资损核算与熔断:持续补偿失败说明系统级异常,触发业务熔断(暂停该活动/渠道),止损优先于修复
- 事后复盘补偿失败的根因,反哺流程(超时设太短?第三方限制?)
收尾:我的考核指标是**"自动补偿成功率 > 99.9%,人工工单 24 小时闭环率"——补偿机制的价值不在"不出错",在于错了之后多快、多省力地恢复**。
🏗 系统架构师
补偿机制是 Saga/最终一致架构的生命线,我讲设计要点、失败处理、工程治理三层。
1. 补偿的设计要点
- 补偿必须幂等:补偿可能被重试多次(undo 操作本身也要防重),以业务单号 + 操作类型做唯一约束
- 补偿必须可靠触发:正向操作成功后补偿的触发点(失败、超时)都要有事件——不能靠人记得,用状态机驱动:每个订单/任务有显式状态(处理中/成功/待补偿/补偿失败),扫描器兜底扫描"处理中超时"的记录
- 补偿与正向的语义对称:扣款 ↔ 退款、预占 ↔ 释放,补偿是业务意义的逆操作,不是数据库回滚;设计正向接口时就要设计好逆接口(TCC 的 Cancel 在 Try 阶段就要预留资源,空回滚与悬挂问题要处理)
- 可补偿性分级:能自动逆的(库存释放、积分扣减)排前面;不能自动逆的(短信已发出)只做记录与人工流程——业务设计时"把不可补偿的操作放最后执行"
- 补偿要有超时与可见性:补偿不是立刻成功就完事,金额类的补偿要对账确认到账
2. 补偿失败了怎么办(题眼,分情况):
- 瞬时失败(网络/依赖抖动):自动重试,指数退避 + 抖动,间隔递增(1m/5m/30m/2h),避免雪崩
- 重试耗尽:任务进死信/人工干预队列,升级告警(电话/IM),运营后台提供一键重试 + 手动完成 + 手动跳过三个动作,处理过程留痕
- 业务性失败(如余额已被用户提现无法扣回):自动流程无解,必须转业务流程——挂账(生成应收/应付单)、风控介入、甚至法务流程;系统只负责准确记录负债,不要试图用技术强扣
- 极端场景对账兜底:T+1 对账把所有"正逆向不平"的单子捞出来驱动补偿闭环,这是最终防线
3. 工程治理:补偿成功率、积压量、最老积压时长三个监控指标;每季度容灾演练"杀掉补偿服务"验证恢复;补偿记录与正向记录同样的审计级别(资损追溯依据)。
一句话:幂等的逆操作 + 状态机驱动的可靠触发 + 分级自动重试 + 死信人工兜底 + 对账终极闭环。
💻 全栈开发专家
补偿机制我总结为六个字:幂等、可重、兜底。展开讲注意点和补偿失败的处置。
设计注意点
- 幂等与空回滚:补偿可能被执行多次(重试),必须幂等(唯一流水号 + 状态检查)。要防"空回滚"——Try 没到,Cancel 先到,直接回滚会留下"已回滚但 Try 后来又执行"的悬挂;解法是 Cancel 执行前检查 Try 是否执行过,没执行过就记录"空回滚标记",Try 到了看到标记直接拒绝。
- 补偿顺序:严格逆序(扣库存 → 锁优惠券 → 创建订单,补偿就先退订单再退券再回库存),乱序补偿会产生中间不一致状态。
- 补偿必须持久化:补偿任务落表(谁、补什么、状态、重试次数),绝不能放内存定时器里——进程一挂补偿就丢了。
- 重试策略:指数退避(1m/5m/30m/2h...)+ 最大次数(如 6 次),防止把下游打崩;重试间隔要考虑下游恢复时间。
- 可观测:每次补偿打点监控,补偿率上升说明主流程有问题,要告警而不是默默吞掉。
补偿失败怎么办(递进兜底)
java
for (int i = 0; i < MAX_RETRY; i++) {
if (compensate(msg)) return SUCCESS;
sleep(backoff(i));
}
alert("人工介入: " + msgId); // 进入待人工队列- 自动重试:调度器(XXL-JOB 扫补偿表)按退避策略重试。
- 告警 + 人工台:超限后进"待人工处理"队列,运营后台支持查看上下文、一键重放、标记坏账。
- 对账修复:终极兜底,T+1 对账发现不一致按主键自动修复——补偿失败不代表数据永远错,对账会追上。
一句话:补偿失败不是灾难,没有兜底闭环才是灾难。