1、分布式事务的背景和解决方案

1、事务特性

我们都知道在进行业务开发的时候要考虑事务,这里的事务特性一般是指数据库操作的ACID,即原子性、一致性、隔离性和持久性。

  • A原子性(Atomic):整个事务作为业务完整性的最小单元,要么都执行,要么都不执行
  • C一致性(Consistent):事务在完成时,必须要整数据的一致状态,事务结束时所有的内部数据结构必须是正确的。即使事务是并发多个,系统也必须保证如同串行事务一样执行,比如A给B转500块钱,A扣了500,B必须收到500,不能多也不能少
  • I隔离性(Isolation):一个事务对数据的并发修改对其它事务不可见,事务之间相互隔离
  • D持久性(Duration):事务完成后,对系统的影响是永久的

如果是常规业务场景操作的是同一个数据源,这直接通过数据库本身的事务支持就可以很好地保证。当时如果把业务场景放到现在流行的分布式开发,比如说微服务架构下,一个业务可能调用多个服务的接口,同时会操作多个数据源,这个时候又该如何保证数据库的ACID呢?

也就是这个时候必须要依赖分布式事务解决方案了,所谓分布式事务,其本质就是要保证数据的一致性

2、分布式事务理论

2.1、X/OpenDTP(X/Open Distributed Transaction Processing Reference Model)

X/Open DTP是一套分布式事务模型标准,具体实现由不同的厂商去实现,它主要就是提出了二阶段提交(2pc)这个分布式事务模型,同时也定义了分布式事务中的一些参与者的角色

  • RM:资源管理器,比如我们的数据库
  • TM:事务管理(协调)器
  • AP:应用程序

image-20251225083608863

2.1.1、事务流程

事务流程简单描述:

  • 应用程序开启并注册全局事务

  • 资源管理器分别向事务管理器注册资源

  • 应用服务器操作资源

  • 应用程序告知事务管理器是否要提交事务

  • 事务管理器通知资源管理器预提交

  • 预提交操作都正常响应之后事务管理器再通知资源管理器正式提交事务

2.1.2、两阶段

这里两阶段主要是体现在最后的事务提交过程中

  • 第一阶段:表决。事务管理器向资源管理器发送一个提交事务的请求,资源管理器接收到这个请求后开始准备对应的事务日志,如果事务日志写成功,就会给TM一个成功的回执,否则就会返回一个失败的消息
  • 第二阶段:提交。TM收到所有参与者的回执之后,如果所有的预提交都是成功的,则事务管理器会再发送一个提交的消息给所有的参与者,但凡有一个预提交的回执收到的是失败的结果,则TM在第二阶段会通知所有的参与者进行事务回滚。而对于所有的参与者,如果收到提交事务的消息,则会对本地事务进行提交,否则就会回滚

2.1.3、两个协议

在这个过程中还涉及两个协议

  • XA:资源管理器用来通知和协调事务的开始、提交或回滚,它是一个双向的接口,目前主流的数据库都支持XA协议

    https://dev.mysql.com/doc/refman/8.0/en/xa.htmlopen in new window

    https://dev.mysql.com/doc/refman/8.0/en/xa-statements.htmlopen in new window

    XA {START|BEGIN} xid [JOIN|RESUME] --负责开启或者恢复一个事务分支,并且管理XID到调用线程
    XA END xid [SUSPEND [FOR MIGRATE]] --负责取消当前线程与事务分支的关联
    XA PREPARE xid --负责询问RM 是否准备好了提交事务分支
    XA COMMIT xid [ONE PHASE] --知RM提交事务分支
    XA ROLLBACK xid --通知RM回滚事务分支
    XA RECOVER [CONVERT XID]
    

    结合上面两阶段的定义大概就是下面这个过程

    image-20251225093729102

  • TX:全局事务管理器与资源管理器之间通信的接口

2.2、基于XA协议的分布式事务框架

2.2.1、JTA

Java Transaction Api,在rt.jar里边javax.transaction包下,具体可以自己看一下

2.2.2、Atomikos

Atomikos是对JTA的实现,官网地址open in new window,分为开源版本和商业版本,使用方法晚上很多,随便一搜一大堆,比如:

Spring Boot集成atomikos快速入门Demoopen in new window

2.2.3、Bitronix

2.2.4、Seata

seata是阿里开源的一款分布式事务解决方案

2.3、基于可靠消息的最终一致性方案

image-20251225111020809

基于可靠消息的最终一致性方案大致过程如上图所示,在执行本地事务的同时,向消息中间件的broker发送一条可靠消息,然后消费者基于这个消息再去执行下游的业务,最终完成整个上下游事务的一致性。

但是这里有个问题,发送mq消息肯定不能在执行本地事务之前,因为如果在执行本地事务之前发送成功了,那么如果本地事务执行失败,mq消息没办法撤回。但是如果把发送mq消息放到执行本地事务之后呢?

begin transaction;
//1.数据库操作
//2.发送MQ
commit transation;

这样看似没有问题,当时想想,如果发送mq消息的时候由于网络超时导致事务回滚了,而mq消息最终还是被发送到了broker了,这时候不就又有问题了吗?

2.3.1、基于本地消息表

针对上面提到的问题,我们可以在本地记录一张本地消息表,将上游的事务操作改成执行业务逻辑和记录本地事务消息表,比如:

begin transaction;
//1.数据库操作
//2.记录消息日志表
commit transation;

这样就能保证本地事务完成的时候业务操作和记录消息日志是一个完整的本地事务,后续通过定时任务去扫描这个消息日志表中发送状态是非成功状态的消息记录,并将其发送到broker。后续下游事务操作的消费者收到这个消息再去做对应的业务处理。剩下需要考虑的问题就是消息的可靠性和幂等性问题。

消息的幂等性是指不能重复消费,收到一次这个消息和收到一百次这个消息最终的结果是一致的。一般做消息的幂等性的方案可以采用状态机。

消息的可靠性是指首先要保证消息正确发送到broker,另外消费者也需要正确消费。一般消息的可靠性主要依赖消息中间件的ack机制,比如生产者发送消息的时候基于broker的ack应答和消费者消费消息的手动ack等。

2.3.2、基于RocketMQ事务消息

Apache RocketMQ 4.3版本之后,开始支持事务消息,事务消息为分布式事务提供了非常方便的解决方案。RocketMQ的事务消息过程如下:

image-20251224154023077

也就是定义了消息的两种状态中间状态提交状态 ,对于中间状态的消息对于消费者是不可见的,只有经过commit的消息消费者才能正常消费。另外RocketMQ提供了执行本地事务和回查本地事务状态的接口,换句话说就是broker能够拿到事务的执行状态和结果,一旦本地事务执行成功,生产者就可以通知broker正常提交消息,反之就通知走回滚逻辑,而如果长时间没有接收到生产者的提交或回滚,RocketMQ就会调用回查接口拿到本地事务的状态:UNKNOWNCOMMITROLLBACK

2.3.2、TCC事务模型

TCC事务模型理论上也可以理解为2pc的模型,它将一个事务拆分成了Try、Confirm和Cancel三个操作,在try部分去完成业务逻辑,如果业务逻辑正常结束,则走confirm部分的事务提交逻辑,否则就走cancel部分的事务回滚逻辑,所以本质上是一种基于补偿的事务机制

image-20251225115130897

TCC使用起来相对复杂,它的控制权在业务开发者,业务开发者需要在每个分布式事务的业务操作中实现Try、Confirm、Cancel这三个方法


扫码关注

最后更新时间: 2026/1/9 14:14:44