2-1-1-分布式事务

本文详细总结论述分布式事务的解决方案、应用场景、优劣势及技术组件,并进行横向对比。主要内容如下:

  • 分布式事务的核心挑战与基础理论:介绍分布式事务的基本概念和理论基础,包括ACID、CAP、BASE理论等。
  • 主流分布式事务解决方案详解:详细分析2PC、3PC、TCC、Saga模式、本地消息表、事务消息和Seata框架等七种解决方案的原理和特点。
  • 应用场景与选型指南:根据不同业务场景提供解决方案选择建议,包括金融交易、电商订单等典型场景。
  • 技术组件与生态支持:介绍各解决方案的常见技术实现和框架支持。
  • 横向对比与总结:通过表格对比各方案的关键特性,并提供综合选型建议。

一、分布式事务全面解析:解决方案、应用场景与选型指南

1 分布式事务的核心挑战与基础理论

分布式事务是指跨越多个网络节点(不同服务、数据库或系统)的事务操作,这些操作必须作为一个整体遵循 "全部成功或全部失败" 的原子性原则。随着微服务架构的普及,分布式事务已成为系统设计中不可避免的技术挑战。在单体应用中,我们依赖数据库的ACID事务保证数据一致性;但在分布式环境中,数据和服务分散在不同节点上,传统的单机事务模型无法直接应用,需要采用专门的分布式事务解决方案。

分布式事务的核心挑战源于分布式系统的固有特性:网络分区、节点故障和时钟同步等问题。这些挑战直接映射到CAP定理的描述中——在分布式系统中,一致性(Consistency)、可用性(Availability)和分区容错性(Partition Tolerance)三者不可兼得。基于这一理论基础,衍生出了两种不同的分布式事务设计哲学:强一致性方案(如XA协议)遵循ACID原则,而最终一致性方案(如TCC、Saga)则遵循BASE理论,通过在一致性和可用性之间权衡来满足不同业务场景的需求。

理解分布式事务还需要掌握其关键特性。与传统事务类似,分布式事务也追求ACID属性,但实现方式有所不同:

  • 原子性(Automicity):所有参与节点要么全部提交,要么全部回滚
  • 一致性(Consistency):事务完成后系统状态必须一致
  • 隔离性(Isolation):并发事务相互隔离,不影响最终结果
  • 持久性(Durability):事务提交后更改永久保存

2 主流分布式事务解决方案详解

2.1 两阶段提交(2PC)

2PC是最经典的强一致性分布式事务解决方案,采用"协调者-参与者"架构模式,将事务提交过程分为两个阶段。在准备阶段,协调者询问所有参与者是否就绪,参与者执行事务但不提交,记录redo和undo日志;在提交阶段,协调者根据所有参与者的反馈决定全局提交或回滚。这种方案依赖数据库原生支持,如MySQL的XA协议就是2PC的一种实现。

2PC的主要优势在于其强一致性保证和相对简单的理解难度,但存在明显缺陷:同步阻塞导致性能低下(参与者等待协调者期间资源被锁定);协调者单点故障风险(协调者宕机可能导致参与者一直处于阻塞状态);数据不一致问题(在极端情况下,如协调者和参与者同时故障,可能出现部分提交部分未提交的数据不一致情况)。

2.2 三阶段提交(3PC)

3PC是针对2PC缺陷的改进方案,通过引入超时机制和预提交阶段来减少阻塞时间和单点故障影响。3PC将事务过程分为三个阶段:CanCommit阶段(协调者询问参与者是否可提交,得到肯定答复后进入下一阶段)、PreCommit阶段(参与者执行事务操作但不提交,写入redo/undo日志)和DoCommit阶段(协调者根据之前结果发送提交或回滚指令)。

3PC通过两个关键机制改善2PC的问题:首先,在所有参与者节点引入超时机制,当参与者长时间未收到协调者消息时自动提交,避免了无限期阻塞;其次,新增的CanCommit阶段可以提前检查参与者状态,减少无效的事务操作准备。但这些改进也带来了更高的实现复杂度和网络通信开销,且仍无法彻底解决数据一致性问题(如网络分区时可能出现数据不一致)。

2.3 TCC(Try-Confirm-Cancel)

TCC是一种业务层面的分布式事务解决方案,通过将业务操作拆分为三个步骤实现最终一致性。Try阶段负责资源检查和预留(如冻结库存、预扣金额),Confirm阶段确认执行业务(实际扣减资源),Cancel阶段在业务失败时执行补偿操作(释放预留资源)。TCC要求每个参与者服务都必须实现这三个接口,并且所有操作都必须保证幂等性和可重试性,以应对网络重试等异常情况。

TCC的优势在于其细粒度资源控制(不需要全局锁,只在Try阶段锁定必要资源)和高并发性能,适用于电商、金融等高并发场景。但其缺点也十分明显:高度业务侵入性(需要改造现有业务逻辑,实现三个接口);开发复杂度高(需要处理各种异常情况和幂等性问题);确认和取消操作需要实现幂等性,增加了额外开发负担。

2.4 Saga模式

Saga模式将长事务拆分为一系列本地事务,每个本地事务都有对应的补偿操作。在正常流程中,Saga按顺序执行各个本地事务;当某个事务失败时,Saga按相反顺序调用补偿操作,回滚已完成的更改。Saga有两种实现方式:事件/编排式(Choreography)无中心协调器,各服务通过事件交互;命令/协调式(Orchestration)有中心协调器负责协调所有参与者的执行和回滚。

Saga适用于长事务和跨多服务的复杂业务流程(如订单履约、供应链管理),因为它不需要长期锁定资源,提高了系统并发性能。但其缺点是无法保证隔离性,可能出现脏读(如一个事务读取了另一个未完成Saga的中间状态);补偿操作的设计和实施也增加了系统复杂性。

2.5 本地消息表

本地消息表是一种基于最终一致性的轻量级解决方案,其核心思想是将分布式事务拆解为本地事务和异步消息处理。具体实现时,业务应用在同一个本地事务中同时处理业务数据和消息表写入;然后通过定时任务轮询消息表,将待处理消息发送到消息中间件;消息消费者从MQ获取消息并处理业务,处理成功后更新消息状态。

这种方案的优势在于简单易实现(只需额外维护一张消息表),避免了分布式事务,实现了最终一致性。但其缺点包括:消息表与业务数据耦合(占用业务系统资源);消息延迟(异步处理可能导致数据临时不一致);需要保证幂等性(防止消息重复消费导致数据错误)。

2.6 事务消息

事务消息是消息中间件提供的分布式事务支持,以RocketMQ的事务消息为代表。该方案流程为:生产者发送半消息(对消费者不可见);生产者执行本地事务并根据结果提交或回滚消息;消息队列向消费者投递已提交的消息;消费者消费消息并执行业务操作。如果生产者长时间未确认消息状态,消息队列会回查生产者确认事务状态。

事务消息的优势在于消息数据独立存储,降低业务系统与消息系统的耦合度,提供了比本地消息表更好的吞吐性能。但其缺点是需要消息队列支持事务消息(如RocketMQ支持,但Kafka和RabbitMQ不支持);一次消息发送需要两次网络请求(半消息+commit/rollback消息);需要实现消息回查接口,增加了额外开发工作。

2.7 Seata框架

Seata是阿里开源的一站式分布式事务解决方案,支持AT、TCC、Saga和XA多种模式。Seata架构包含三个核心组件:事务协调器(TC)、事务管理器(TM)和资源管理器(RM)。在AT模式(自动TCC)下,Seata代理数据源,在业务方法执行时拦截SQL,生成undo日志;在事务提交时,Seata向TC注册分支事务;根据TC的指令决定全局提交或回滚(回滚时使用undo日志补偿)。

Seata的优势在于多模式支持(可根据业务需求选择不同事务模式);对业务侵入性低(特别是AT模式,几乎无需改造业务代码);开源社区活跃,文档完善。但其缺点是需要部署额外的Seata-Server协调器;AT模式下的全局行锁可能影响性能;需要依赖特定版本的数据库和框架。

3 应用场景与选型指南

分布式事务解决方案的选择高度依赖于具体业务场景和需求特点。以下是典型场景的方案推荐:

  • 金融核心交易、银行转账:这类业务对强一致性要求极高,且能够接受一定的性能损失。推荐使用XA/2PC方案(如基于MySQL XA协议),因为它能提供最接近ACID的保证。传统银行系统多采用这种方案。
  • 电商订单、支付业务:高并发场景下需要平衡一致性和性能。TCC模式是优选(如蚂蚁金服的支付系统),它通过业务资源预留和确认机制实现高性能的最终一致性。Seata的AT模式也适用于此类场景,因为它对业务代码侵入性较低。
  • 长事务流程:如供应链管理、保险理赔等涉及多步骤的业务流程。Saga模式特别适合,因为它将长事务拆分为多个短事务,避免了长期资源锁定。ServiceComb对Saga模式有良好实现。
  • 异步通知、事件驱动系统:如物流跟踪、用户注册后发送欢迎邮件等场景。基于消息队列的最终一致性方案(本地消息表或事务消息)是理想选择,京东的物流跟踪采用本地消息表方案。RocketMQ的事务消息机制能很好地支持这类场景。

选型时还需综合考虑以下因素:数据一致性要求(强一致还是最终一致)、性能要求(吞吐量和延迟要求)、开发成本(方案复杂度和团队熟悉程度)、系统侵入性(是否愿意改造现有代码)和运维成本(是否需要部署额外组件)。世界上解决一个计算机问题最简单的方法:"恰好"不需要解决它,有时人工核对和补偿可能比引入复杂的分布式事务框架更经济。

4 技术组件与生态支持

各分布式事务方案都有相应的技术框架和中间件支持。对于XA协议,主流数据库如MySQL、Oracle等都提供了XA接口实现,Java生态中的JTA(Java Transaction API)定义了XA接口规范,常见实现有Atomikos、Bitronix等。

在TCC模式方面,开源框架包括TCC-Transaction(提供TCC基础能力)、Hmily(轻量级TCC框架)和Seata TCC(Seata的TCC模式实现)。这些框架减少了自行实现TCC所需的工作量。

对于Saga模式,ServiceComb Saga是华为开源的Saga实现,Axon Framework提供Saga支持,Seata Saga也是可选方案。这些框架提供了状态机和事件驱动两种编程模型。

在消息事务领域,RocketMQ是支持事务消息的代表性中间件(阿里内部DTS基于类似原理),KafkaRabbitMQ需要通过外部机制实现类似功能。Seata作为综合解决方案支持XA、AT、TCC和Saga多种模式,与Spring Cloud、Dubbo等微服务框架集成良好。

5 横向对比与总结

下表总结了各分布式事务解决方案的关键特性:

方案 一致性 性能 复杂度 业务侵入 适用场景
2PC/XA 强一致 低(同步阻塞) 金融核心交易、短事务
3PC 强一致 中低 对可用性要求稍高的系统
TCC 最终一致 高(无锁) 电商订单、支付业务
Saga 最终一致 中高 长事务、流程审批
本地消息表 最终一致 异步通知、事件驱动
事务消息 最终一致 中高 消息驱动系统
Seata 多模式支持 中高 低-中 多场景适配

从对比可以看出,没有一种解决方案是完美的,每种方案都在一致性、性能和复杂性之间做出了不同权衡。强一致性方案(如2PC/XA)适用于对数据一致性要求极高的关键业务,但需要付出性能代价;最终一致性方案(如TCC、Saga、消息事务)通过放松一致性要求来获得更高的性能,适用于大多数互联网业务场景。

在实际系统设计中,我们常常根据业务特点采用混合方案。例如,电商系统可能同时使用TCC处理订单创建、使用消息队列处理物流通知、使用Saga管理售后流程。重要的是根据具体业务需求选择最合适的方案,而不是追求技术上的"完美"解决方案。分布式事务的本质是在各种约束下寻求最优平衡点的艺术。

分布式事务中的协议与解决方案之间存在紧密的对应和支撑关系。简单来说:

  • 协议(Protocol) 更多指底层的交互规范和机制(如XA、2PC、3PC),定义了事务管理器(TM)和资源管理器(RM)之间如何通信与协作,以确保事务的原子性。
  • 解决方案(Solution) 则是在协议或特定设计模式之上,构建出的完整、可落地的技术实现或架构模式(如TCC、Saga、本地消息表、事务消息),它们会利用一种或多种底层协议,并结合业务逻辑来最终保障数据一致性。

6 协议与解决方案对应关系表

下表清晰地展示了主流分布式事务协议与解决方案之间的对应和支撑关系:

解决方案 (Solution) 核心协议/机制 (Protocol/Mechanism) 一致性模型 关键特征
XA方案 XA协议 (基于2PC) 强一致性 数据库层面支持,对业务侵入低,但存在同步阻塞和单点故障风险。
TCC TCC协议 (Try-Confirm-Cancel) 最终一致性 业务层面实现,通过预留资源、确认提交、补偿回滚三阶段操作,避免长事务锁。
Saga模式 ** Saga模式** (事件驱动+补偿事务) 最终一致性 将长事务拆分为多个本地事务,每个事务有对应的补偿操作,适用于长流程业务。
本地消息表 最终一致性 (基于数据库表+重试) 最终一致性 通过业务数据库中的消息表保障可靠性,配合定时任务和重试机制实现数据同步。
事务消息 事务消息协议 (如RocketMQ) 最终一致性 消息中间件提供事务支持,确保消息生产与本地事务的原子性。
Seata AT AT协议 (Automatic Transaction) 最终一致性 代理数据源,自动生成反向SQL作为回滚日志,对业务代码几乎无侵入。
Seata XA XA协议 强一致性 在Seata框架中统一管理XA事务。
Seata TCC TCC协议 最终一致性 在Seata框架中实现TCC模式。
Seata Saga Saga模式 最终一致性 在Seata框架中实现Saga模式。

6.1 详细解读与常见组合

1. XA 协议与两阶段提交 (2PC)

XA协议 是由X/Open组织定义的分布式事务处理规范,它详细规定了事务管理器(TM)与资源管理器(RM)之间的接口。两阶段提交(2PC) 是XA协议实现其原子性承诺的核心算法。

  • 关系:可以理解为 XA ≈ 2PC + 标准化接口。我们常说的“基于XA协议的解决方案”就是指采用2PC机制的事务处理。
  • 特点:实现强一致性,但存在同步阻塞协调者单点问题数据不一致的潜在风险。

2. TCC(Try-Confirm-Cancel)

TCC是一种业务侵入性较强的分布式事务解决方案,它将一个完整的业务操作拆分为三个动作:

  1. Try:尝试执行,完成所有业务的检查,并预留必需的业务资源。
  2. Confirm:确认执行,真正执行业务,使用Try阶段预留的资源。此操作需保证幂等性
  3. Cancel:取消执行,释放Try阶段预留的资源。此操作也需保证幂等性
  • 关系:TCC是一个解决方案模式,它并不依赖数据库底层的事务协议(如XA),而是通过业务逻辑在应用层实现了类似2PC的流程。它可以被看作是2PC的一种柔性事务实现。
  • 特点最终一致性,性能较好,避免了长事务锁,但对业务有侵入,需要开发者实现全套Try/Confirm/Cancel接口和补偿逻辑。

3. Saga 模式

Saga模式将一个长事务拆分为多个连续的本地子事务,每个子事务都有相应的补偿操作。如果所有子事务依次成功完成,则事务完成;如果某个子事务失败,则按相反顺序调用之前所有子事务的补偿操作进行回滚。

  • 关系:Saga是一个解决方案模式设计模式。它通常通过事件驱动命令编排的方式来协调各个子事务和补偿操作,其底层可能依赖消息队列进行通信。
  • 特点:适用于长流程、跨系统的业务场景,无锁设计提升性能,保证最终一致性。但补偿逻辑的设计和实施也增加了系统复杂性。

4. 消息队列方案(本地消息表 & 事务消息)

这两种方案都利用消息队列的可靠性来实现分布式系统间的最终一致性

  • 本地消息表:在业务数据库中同步维护一张消息表,将消息的发送和业务操作放在同一个本地事务中。然后通过定时任务扫描并投递消息,依靠重试机制确保消息最终被消费。
  • 事务消息:依赖支持事务消息的消息中间件(如RocketMQ)。生产者先发送一个“半消息”,执行本地事务后再向MQ确认提交或回滚。MQ会定期回查事务状态,确保消息的正确投递。
  • 关系:它们是最终一致性的经典解决方案。其底层依赖于消息队列的持久化、重试和投递机制,而不是传统的事务协议。

5. Seata 框架

Seata是阿里开源的一站式分布式事务解决方案,它封装并支持了多种模式

  • Seata AT模式:基于支持ACID事务的数据库,通过数据源代理,在业务无感知的情况下拦截SQL,生成行前快照和行后快照,自动生成反向回滚日志。可理解为一种自动化的最终一致性方案。
  • Seata XA模式:基于数据库对XA协议的原生支持,在Seata的框架内管理XA事务,提供强一致性保障。
  • Seata TCC模式:在框架层面提供对TCC模式的支持,简化TCC的开发。
  • Seata Saga模式:在框架层面提供基于状态机引擎的Saga模式支持,用于编排长流程事务。
  • 关系:Seata是一个框架,它实现和整合了多种分布式事务解决方案(AT, XA, TCC, Saga),并让这些方案可以更方便地应用于微服务架构中。

6.2 总结

协议是构建解决方案的基石,而解决方案则是在特定协议或设计思想之上,为应对不同业务场景而封装出的完整实践。选择时,强一致性场景可考虑基于XA协议的方案(对性能不敏感);高并发长事务场景则更适合TCCSaga或基于消息队列最终一致性方案。Seata这类框架的出现,为我们根据实际需求选择和切换不同模式提供了极大的便利。

二、分布式事务协议与解决方案代码实现详解

主要分布式事务协议与解决方案的代码实现样例,主要内容如下:

  • XA协议与两阶段提交(2PC)实现:介绍基于Java JTA和MySQL XA数据源的2PC代码配置和实现。
  • TCC模式实现:详细分析TCC模式的三个Phase(Try、Confirm、Cancel)的代码接口和业务实现。
  • Saga模式实现:展示基于状态机或编排器的Saga模式代码示例,包括正向操作和补偿操作。
  • 本地消息表示例:介绍使用本地数据库表确保可靠消息传递的代码实现。
  • 事务消息示例:以RocketMQ为例展示事务消息的发送和回查机制代码。
  • Seata框架应用:介绍Seata的AT模式、XA模式和TCC模式的配置和代码实现。

1 XA协议与两阶段提交(2PC)实现

XA协议是X/Open组织提出的分布式事务处理标准,基于两阶段提交(2PC)协议实现。在Java中,可以通过JTA(Java Transaction API) 和支持XA的数据库驱动来实现。

以下是基于Atomikos事务管理器和MySQL XA数据源的配置示例:

// 配置Atomikos JTA事务管理器
@Configuration
public class JtaConfig {
    @Bean
    public UserTransaction userTransaction() throws Exception {
        AtomikosUserTransaction userTransaction = new AtomikosUserTransaction();
        userTransaction.setTransactionTimeout(300);
        return userTransaction;
    }
    
    @Bean
    public TransactionManager transactionManager() {
        AtomikosTransactionManager transactionManager = new AtomikosTransactionManager();
        transactionManager.setForceShutdown(false);
        return transactionManager;
    }
    
    @Bean
    public DataSource xaDataSource() {
        MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();
        mysqlXaDataSource.setUrl("jdbc:mysql://localhost:3306/test");
        mysqlXaDataSource.setUser("root");
        mysqlXaDataSource.setPassword("password");
        
        AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
        xaDataSource.setXaDataSource(mysqlXaDataSource);
        xaDataSource.setUniqueResourceName("mysqlXA");
        xaDataSource.setPoolSize(5);
        return xaDataSource;
    }
}

在业务代码中使用JTA事务:

@Service
public class OrderService {
    @Autowired
    private DataSource xaDataSource;
    
    @Transactional // 使用JTA全局事务
    public void placeOrderAndProcessPayment(Order order, Payment payment) {
        try (Connection conn = xaDataSource.getConnection()) {
            // 执行订单数据库操作
            PreparedStatement orderStmt = conn.prepareStatement(
                "INSERT INTO orders (id, user_id, amount) VALUES (?, ?, ?)");
            orderStmt.setString(1, order.getId());
            orderStmt.setString(2, order.getUserId());
            orderStmt.setBigDecimal(3, order.getAmount());
            orderStmt.executeUpdate();
            
            // 执行支付数据库操作
            PreparedStatement paymentStmt = conn.prepareStatement(
                "INSERT INTO payments (order_id, amount, status) VALUES (?, ?, ?)");
            paymentStmt.setString(1, payment.getOrderId());
            paymentStmt.setBigDecimal(2, payment.getAmount());
            paymentStmt.setString(3, payment.getStatus());
            paymentStmt.executeUpdate();
        } catch (SQLException e) {
            throw new RuntimeException("分布式事务执行失败", e);
        }
    }
}

XA协议的工作流程分为两个阶段:准备阶段(协调者询问所有参与者是否可以提交)和提交阶段(协调者通知所有参与者提交或回滚)。虽然XA协议提供了强一致性保证,但其同步阻塞的特性和单点故障风险需要在设计时慎重考虑。

2 TCC模式实现

TCC(Try-Confirm-Cancel)是一种业务层面的分布式事务解决方案,通过三个Phase(Try、Confirm、Cancel)来管理分布式事务。

首先定义TCC服务接口:

public interface TccService {
    // Try阶段:尝试执行,预留资源
    boolean tryOperation(String businessId, Object data);
    
    // Confirm阶段:确认执行,真正提交
    boolean confirmOperation(String businessId);
    
    // Cancel阶段:取消执行,释放资源
    boolean cancelOperation(String businessId);
}

具体业务服务实现TCC接口:

@Service
public class PaymentTccService implements TccService {
    @Autowired
    private PaymentRepository paymentRepository;
    
    @Override
    public boolean tryOperation(String businessId, Object data) {
        // 检查业务状态,确保不会重复处理
        Payment payment = paymentRepository.findByBusinessId(businessId);
        if (payment != null && !"INIT".equals(payment.getStatus())) {
            return false; // 已处理过,不再处理
        }
        
        // 创建预支付记录
        Payment newPayment = new Payment();
        newPayment.setBusinessId(businessId);
        newPayment.setAmount((BigDecimal) data);
        newPayment.setStatus("TRY_SUCCESS");
        newPayment.setCreateTime(new Date());
        paymentRepository.save(newPayment);
        
        return true;
    }
    
    @Override
    public boolean confirmOperation(String businessId) {
        Payment payment = paymentRepository.findByBusinessId(businessId);
        if (payment == null) {
            return false;
        }
        
        // 确认支付,更新状态
        payment.setStatus("CONFIRMED");
        payment.setConfirmTime(new Date());
        paymentRepository.save(payment);
        
        // 实际执行资金扣减等操作
        return true;
    }
    
    @Override
    public boolean cancelOperation(String businessId) {
        Payment payment = paymentRepository.findByBusinessId(businessId);
        if (payment == null) {
            return false;
        }
        
        // 取消支付,释放预留资源
        payment.setStatus("CANCELLED");
        payment.setUpdateTime(new Date());
        paymentRepository.save(payment);
        
        return true;
    }
}

TCC事务协调器负责协调多个TCC服务:

@Service
public class TccTransactionCoordinator {
    @Autowired
    private PaymentTccService paymentTccService;
    
    @Autowired
    private InventoryTccService inventoryTccService;
    
    public boolean executeDistributedTransaction(String businessId, BigDecimal amount, Integer inventoryQty) {
        // Phase 1: Try阶段 - 尝试所有操作
        boolean paymentTryResult = paymentTccService.tryOperation(businessId, amount);
        boolean inventoryTryResult = inventoryTccService.tryOperation(businessId, inventoryQty);
        
        if (!paymentTryResult || !inventoryTryResult) {
            // 任一Try失败,执行Cancel回滚
            paymentTccService.cancelOperation(businessId);
            inventoryTccService.cancelOperation(businessId);
            return false;
        }
        
        // Phase 2: Confirm阶段 - 确认所有操作
        try {
            boolean paymentConfirmResult = paymentTccService.confirmOperation(businessId);
            boolean inventoryConfirmResult = inventoryTccService.confirmOperation(businessId);
            
            return paymentConfirmResult && inventoryConfirmResult;
        } catch (Exception e) {
            // 任何Confirm失败需要人工干预或重试
            // 记录日志并告警
            return false;
        }
    }
}

TCC模式的优势在于其细粒度资源控制高并发性能,但需要开发者手动实现Try、Confirm、Cancel三个Phase的逻辑,并确保各Phase的幂等性

3 Saga模式实现

Saga模式将长事务拆分为一系列本地事务,每个事务都有对应的补偿操作。Saga有两种实现方式:编排式(Choreography)和协调式(Orchestration)。

以下是Saga协调器的简单实现:

public interface SagaStep {
    void execute();
    void compensate();
}

public class SagaOrchestrator {
    private final List<SagaStep> steps = new ArrayList<>();
    
    public void addStep(SagaStep step) {
        steps.add(step);
    }
    
    public void execute() {
        List<SagaStep> executedSteps = new ArrayList<>();
        
        try {
            for (SagaStep step : steps) {
                step.execute();
                executedSteps.add(step);
            }
        } catch (Exception e) {
            // 执行失败,需要补偿已完成的步骤
            Collections.reverse(executedSteps);
            for (SagaStep step : executedSteps) {
                try {
                    step.compensate();
                } catch (Exception ex) {
                    // 记录补偿失败日志,需要人工干预
                    System.err.println("补偿操作失败: " + ex.getMessage());
                }
            }
            throw new RuntimeException("Saga事务执行失败,已执行补偿", e);
        }
    }
}

具体业务步骤实现:

@Service
public class CreateOrderStep implements SagaStep {
    @Autowired
    private OrderRepository orderRepository;
    
    @Override
    public void execute() {
        // 创建订单
        Order order = new Order();
        order.setStatus("CREATED");
        orderRepository.save(order);
    }
    
    @Override
    public void compensate() {
        // 补偿操作:取消订单
        Order order = orderRepository.findLatest();
        order.setStatus("CANCELLED");
        orderRepository.save(order);
    }
}

@Service
public class DeductInventoryStep implements SagaStep {
    @Autowired
    private InventoryRepository inventoryRepository;
    
    @Override
    public void execute() {
        // 扣减库存
        Inventory inventory = inventoryRepository.findByProductId("product1");
        if (inventory.getQuantity() < 1) {
            throw new RuntimeException("库存不足");
        }
        inventory.setQuantity(inventory.getQuantity() - 1);
        inventoryRepository.save(inventory);
    }
    
    @Override
    public void compensate() {
        // 补偿操作:恢复库存
        Inventory inventory = inventoryRepository.findByProductId("product1");
        inventory.setQuantity(inventory.getQuantity() + 1);
        inventoryRepository.save(inventory);
    }
}

使用Saga协调器:

@Service
public class OrderSagaService {
    @Autowired
    private CreateOrderStep createOrderStep;
    
    @Autowired
    private DeductInventoryStep deductInventoryStep;
    
    public void createOrder() {
        SagaOrchestrator saga = new SagaOrchestrator();
        saga.addStep(createOrderStep);
        saga.addStep(deductInventoryStep);
        
        saga.execute();
    }
}

Saga模式适用于长流程业务,但需要注意补偿操作的实现和** Saga的隔离性**问题。

4 本地消息表示例

本地消息表是一种简单有效的最终一致性方案,通过在业务数据库中维护一张消息表来确保消息的可靠传递。

首先创建本地消息表:

CREATE TABLE local_message (
    id VARCHAR(64) PRIMARY KEY,
    business_id VARCHAR(64) NOT NULL,
    content TEXT NOT NULL,
    status VARCHAR(20) NOT NULL DEFAULT 'PENDING',
    retry_count INT NOT NULL DEFAULT 0,
    created_time DATETIME NOT NULL,
    updated_time DATETIME NOT NULL
);

在业务代码中使用本地消息表:

@Service
public class OrderServiceWithLocalMessage {
    @Autowired
    private OrderRepository orderRepository;
    
    @Autowired
    private LocalMessageRepository localMessageRepository;
    
    @Transactional
    public void createOrder(Order order) {
        // 保存订单
        orderRepository.save(order);
        
        // 在同一个事务中保存本地消息
        LocalMessage message = new LocalMessage();
        message.setId(UUID.randomUUID().toString());
        message.setBusinessId(order.getId());
        message.setContent(convertToJson(order));
        message.setStatus("PENDING");
        message.setRetryCount(0);
        message.setCreatedTime(new Date());
        message.setUpdatedTime(new Date());
        
        localMessageRepository.save(message);
    }
}

定时任务发送消息:

@Component
public class MessageSender {
    @Autowired
    private LocalMessageRepository localMessageRepository;
    
    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    @Scheduled(fixedDelay = 5000) // 每5秒执行一次
    public void sendPendingMessages() {
        List<LocalMessage> pendingMessages = 
            localMessageRepository.findByStatus("PENDING");
        
        for (LocalMessage message : pendingMessages) {
            try {
                // 发送消息到MQ
                rabbitTemplate.convertAndSend(
                    "order.exchange", 
                    "order.routingkey", 
                    message.getContent());
                
                // 更新消息状态为已发送
                message.setStatus("SENT");
                message.setUpdatedTime(new Date());
                localMessageRepository.save(message);
            } catch (Exception e) {
                // 发送失败,增加重试次数
                message.setRetryCount(message.getRetryCount() + 1);
                message.setUpdatedTime(new Date());
                localMessageRepository.save(message);
                
                if (message.getRetryCount() > 3) {
                    // 重试超过3次,标记为失败,需要人工干预
                    message.setStatus("FAILED");
                    localMessageRepository.save(message);
                }
            }
        }
    }
}

本地消息表方案的优点简单可靠,不需要额外组件;缺点增加了数据库压力,并且需要自行处理消息重试逻辑。

5 事务消息示例

事务消息是消息中间件提供的分布式事务支持,以RocketMQ的事务消息为例。

RocketMQ事务消息生产者示例:

public class OrderServiceWithTransactionMessage {
    @Autowired
    private RocketMQTemplate rocketMQTemplate;
    
    public void createOrder(Order order) {
        // 发送半消息(对消费者不可见)
        Message<Order> message = MessageBuilder.withPayload(order)
            .setHeader("KEYS", order.getId())
            .build();
        
        TransactionSendResult result = rocketMQTemplate.sendMessageInTransaction(
            "order-topic", 
            message, 
            order);
        
        if (!result.getSendStatus().equals(SendStatus.SEND_OK)) {
            throw new RuntimeException("消息发送失败");
        }
    }
}

// 事务监听器,检查本地事务执行状态
@RocketMQTransactionListener
public class OrderTransactionListenerImpl implements RocketMQLocalTransactionListener {
    @Autowired
    private OrderRepository orderRepository;
    
    @Override
    public RocketMQLocalTransactionState executeLocalTransaction(Message msg, Object arg) {
        try {
            // 执行本地事务
            Order order = (Order) arg;
            orderRepository.save(order);
            
            // 返回COMMIT使消息对消费者可见
            return RocketMQLocalTransactionState.COMMIT;
        } catch (Exception e) {
            // 返回ROLLBACK回滚消息
            return RocketMQLocalTransactionState.ROLLBACK;
        }
    }
    
    @Override
    public RocketMQLocalTransactionState checkLocalTransaction(Message msg) {
        // 检查本地事务状态
        String orderId = msg.getHeaders().get("KEYS").toString();
        Order order = orderRepository.findById(orderId);
        
        if (order != null) {
            return RocketMQLocalTransactionState.COMMIT;
        } else {
            return RocketMQLocalTransactionState.ROLLBACK;
        }
    }
}

事务消息的优点消息数据独立存储,与业务系统解耦;缺点是需要消息中间件支持事务消息,且需要实现回查接口

6 Seata框架应用

Seata是阿里开源的一站式分布式事务解决方案,支持AT、TCC、Saga和XA多种模式。

6.1 Seata AT模式

AT模式对业务代码侵入性低,只需添加注解即可实现分布式事务。

首先配置Seata:

@Configuration
public class SeataConfig {
    @Bean
    public DataSourceProxy dataSourceProxy(DataSource druidDataSource) {
        return new DataSourceProxy(druidDataSource);
    }
    
    @Bean
    public GlobalTransactionScanner globalTransactionScanner() {
        return new GlobalTransactionScanner("your-app-name", "my_test_tx_group");
    }
}

在业务方法上使用@GlobalTransactional注解:

@Service
public class OrderService {
    @Autowired
    private OrderMapper orderMapper;
    
    @Autowired
    private AccountMapper accountMapper;
    
    @GlobalTransactional(name = "createOrder", timeoutMills = 300000)
    public void createOrder(Order order) {
        // 创建订单
        orderMapper.insert(order);
        
        // 扣减账户余额
        accountMapper.decreaseBalance(order.getUserId(), order.getAmount());
        
        // 如果此处抛出异常,Seata会自动回滚前面所有操作
    }
}

6.2 Seata TCC模式

在Seata中使用TCC模式,需要定义TCC接口:

// 定义TCC接口
@LocalTCC
public interface AccountTccService {
    @TwoPhaseBusinessAction(name = "decreaseBalance", commitMethod = "confirm", rollbackMethod = "cancel")
    boolean tryDecrease(@BusinessActionContextParameter(paramName = "userId") String userId,
                       @BusinessActionContextParameter(paramName = "amount") BigDecimal amount,
                       BusinessActionContext actionContext);
    
    boolean confirm(BusinessActionContext actionContext);
    boolean cancel(BusinessActionContext actionContext);
}

// 实现TCC接口
@Service
public class AccountTccServiceImpl implements AccountTccService {
    @Override
    public boolean tryDecrease(String userId, BigDecimal amount, BusinessActionContext actionContext) {
        // Try逻辑:检查并预留资源
        // 返回true表示Try成功
        return true;
    }
    
    @Override
    public boolean confirm(BusinessActionContext actionContext) {
        // Confirm逻辑:实际扣减资源
        String userId = (String) actionContext.getActionContext("userId");
        BigDecimal amount = (BigDecimal) actionContext.getActionContext("amount");
        // 执行实际扣减
        return true;
    }
    
    @Override
    public boolean cancel(BusinessActionContext actionContext) {
        // Cancel逻辑:释放预留资源
        String userId = (String) actionContext.getActionContext("userId");
        BigDecimal amount = (BigDecimal) actionContext.getActionContext("amount");
        // 释放预留资源
        return true;
    }
}

6.3 Seata XA模式

Seata也支持XA模式,配置方式如下:

// 启用Seata XA模式
@Bean
public GlobalTransactionScanner globalTransactionScanner() {
    return new GlobalTransactionScanner("your-app-name", "my_test_tx_group");
}

// 业务代码中使用
@Service
public class OrderService {
    @GlobalTransactional(timeoutMills = 300000)
    public void createOrder(Order order) {
        // 执行数据库操作
        orderMapper.insert(order);
        accountMapper.decreaseBalance(order.getUserId(), order.getAmount());
    }
}

Seata框架的优点多模式支持对业务低侵入缺点是需要部署额外的Seata-Server协调器。

7 总结

不同的分布式事务解决方案有各自的适用场景:

  • XA/2PC:适用于强一致性要求的传统企业应用,但性能较低。
  • TCC:适用于高并发场景,如电商、金融业务,但开发成本较高。
  • Saga:适用于长流程业务,如订单履约、供应链管理,但需要设计补偿机制。
  • 本地消息表:适用于中等规模系统,需要简单实现最终一致性的场景。
  • 事务消息:适用于消息驱动架构,需要高可靠性的场景。
  • Seata:适用于微服务架构,需要灵活选择事务模式的场景。

在实际项目中,应根据业务需求、一致性要求、开发成本和团队技术栈选择合适的分布式事务解决方案。

posted @ 2025-11-11 07:03  哈罗·沃德  阅读(2)  评论(0)    收藏  举报