分布式事务与系统一致性:核心方案与实战解析
事务的本质是在一个会话过程中,对上下文的影响保持一致。要么所有更改都生效,要么全部撤销,不存在半死不死的中间状态。就像薛定谔的猫,事务是为了保障业务数据的完整性和准确性。
在单体应用中,借助关系型数据库自带的 ACID 特性(原子性、一致性、隔离性、持久性),配合 Spring、JDBC 等框架通常能轻松实现事务需求。但在大型互联网平台,尤其是微服务架构下,一个功能往往涉及多个服务调用和多个数据库操作,单一技术手段已无法满足复杂场景。
CAP 理论与最终一致性
在分布式系统中,同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance)是不可能的。互联网领域大多场景需要牺牲强一致性来换取高可用性,转而追求'最终一致性'。只要这个最终状态在用户可接受的时间范围内即可。
常见解决方案
1. 两阶段提交 (2PC) 与 XA
这是最经典的分布式事务处理办法。有一个事务协调器,先询问参与者是否准备好提交,若都回复准备就绪,则统一发送提交指令。
XA 协议定义了 TM(事务管理器)和 RM(资源管理器)之间的接口。主流关系型数据库都实现了 XA 接口。在 Java EE 平台下,WebLogic 等应用服务器提供了 JTA 支持;而在 Tomcat 等轻量级容器中,可能需要借助第三方框架如 Jotm 或 Atomikos 来实现 Spring 事务整合。
优缺点:
- 优点: 实现难度相对较低,适合传统单体应用中的跨库操作。
- 缺点: 性能影响大,不适合高并发场景。且存在阻塞风险,例如某个节点宕机或超时,可能导致整个事务挂起。
2. 补偿机制 (Saga)
当业务逻辑无法保证强一致性时,采用补偿策略是更务实的选择。核心思路是将长事务拆分为多个本地事务,每个步骤都有对应的回滚操作。
比如订单支付后出票的场景,如果要求强一致性,用户页面可能需等待数分钟甚至更久,体验极差。实际做法通常是:支付成功后先返回用户,后台异步处理出票。若出票失败,则触发退款补偿。
实施要点:
- 将业务流程拆解为多个小流程,引入中间状态。
- 中间状态不允许用户直接操作,待所有状态一致成功,或检测到不一致时全部回滚至失败。
- 涉及资金流转时,可采用准实时处理,通过扎差或冲正操作平衡数据。
3. 本地消息表
这种方案源于 eBay 和支付宝的实践,基本思想是将远程分布式事务拆分成一系列本地事务。
以跨行转账为例:
- 扣款并插入凭证消息到本地消息表,利用本地事务保证原子性。
- 通知对方账户加款。通知方式可选 MQ 订阅或定时轮询扫描消息表。
- 消费方通过'消费状态表'记录处理状态,避免重复消费。
优缺点:
- 优点: 避免了分布式锁,实现了最终一致性。
- 缺点: 频繁读写消息表会给数据库造成压力,高并发场景下可能存在瓶颈。
4. 消息队列 (MQ) 事务
非事务消息
使用普通 MQ 时,很难将业务操作与投递消息放在同一个本地事务域中。可能出现'扣款成功但消息未投递'的情况。
解决思路:
- 生产者端:确保幂等性或依赖重试机制。
- 消费者端:保证业务接口的幂等性,或通过状态表记录消费进度。
事务消息
以 RocketMQ 为例,其设计保证了消息发送与本地事务的最终一致性。
- 发送 Prepared 消息(半消息)。
- 执行本地事务。
- 根据本地事务结果确认提交或回滚消息。
若确认失败,Broker 会定期扫描并询问发送端状态,由发送端决定最终动作。这种方式适合电商等广泛场景,可靠性高,但技术实现难度较大,且部分开源 MQ 需二次开发支持。
5. 其他补偿方式
成熟的交易系统中,回调接口常包含重试机制。例如支付宝回调,只有输出成功标识才会停止请求,否则间隔重试。

