Java面试要点:数据不一致、分布式事务、补偿任务与对账修复
前言
数据不一致是业务系统中最敏感的问题。订单支付成功但状态未更新、库存扣了但订单失败、优惠券核销了但订单取消,这些都不是简单的技术异常,而是会影响资金、库存和用户体验。
典型现象
- 支付成功但订单仍是待支付。
- 库存扣减成功但订单创建失败。
- MQ 消息发送成功但消费失败。
- 用户积分重复发放。
- 报表数据和明细数据对不上。
- 主库和搜索索引数据不一致。
排查流程
flowchart TD
A[发现数据不一致] --> B[确定业务主记录]
B --> C[查看状态流转日志]
C --> D[查看本地事务]
D --> E[查看MQ消息]
E --> F[查看消费和补偿]
F --> G[对账确认影响范围]
G --> H[修复数据]
H --> I[补齐防重和监控]
先找业务主记录
排查数据不一致不能先看代码,要先确定主记录:
- 订单号。
- 支付流水号。
- 库存流水号。
- 优惠券核销记录。
- MQ messageId。
- traceId。
没有唯一业务号,就很难串起完整链路。
常见原因
本地事务边界错误
比如订单表插入成功,订单明细插入失败,但异常被吞掉,导致主从表不一致。
处理方式:
- 明确事务边界。
- 异常不能随意 catch 后不抛。
- 使用事务注解时注意同类方法调用失效。
- 事务内不要做远程调用。
MQ 发送和数据库事务不一致
数据库提交成功,但消息发送失败,或者消息发送成功但数据库回滚。
解决方案:
- 本地消息表。
- 事务消息。
- Outbox 模式。
- 定时扫描补偿。
企业里常用本地消息表:业务数据和消息记录在同一个本地事务提交,然后后台任务投递 MQ,投递成功后更新消息状态。
消费失败或重复消费
消费失败会导致后续数据没更新,重复消费会导致重复扣减或重复发放。
解决方式:
- 消费幂等。
- 消费记录表。
- 状态机前置判断。
- 死信队列。
- 补偿重放工具。
缓存和数据库不一致
写库成功后删缓存失败,或者缓存重建时读到旧数据。
处理方式:
- 先更新数据库,再删除缓存。
- 删除失败进入重试。
- 重要数据使用延迟双删或订阅 Binlog。
- 允许短时间最终一致时明确窗口。
对账机制
对账不是只有支付系统才需要。凡是跨系统、异步、最终一致的链路都需要对账。
对账内容:
- 主业务表和流水表。
- 本地库和 MQ 消费结果。
- 支付渠道和本地支付单。
- 库存流水和库存余额。
- DB 和 ES 索引。
对账结果要能生成差异单,支持自动修复和人工确认。
修复原则
- 先冻结影响范围,避免继续扩大。
- 以权威数据源为准。
- 修复前备份数据。
- 修复脚本可重复执行。
- 修复过程记录审计日志。
- 修复后重新对账。
资金类数据不能直接手工改状态,必须通过补单、冲正、调账等业务动作修复。
生产止血
- 暂停异常入口。
- 关闭相关异步消费者。
- 对问题用户或订单加白名单处理。
- 回滚最近发布。
- 补偿失败消息。
- 启动对账任务确认影响范围。
高频面试题
数据不一致怎么排查?
先确定业务主键和权威数据源,再按状态流转、本地事务、MQ 投递、消费结果、缓存和对账记录逐层排查,最后通过补偿任务或修复脚本处理。
最终一致性怎么保证?
通过本地消息表、事务消息、幂等消费、重试、死信、补偿任务和对账机制保证。关键是允许短时间不一致,但必须可发现、可补偿。
修复数据要注意什么?
要备份、可回滚、可重复执行、有审计日志,并以权威数据源为准。资金和库存场景必须走业务修复流程。
总结
数据不一致排查不是单点技术问题,而是完整业务链路问题。面试回答要体现事务、消息、幂等、补偿和对账的闭环。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 Junly博客!
评论


