3分布式事务常用方案 · SpringCloud微服务实战 · 看云

导航

本节我们主要介绍分布式事务常用的解决方案是有哪些,并且介绍每种方案的原理和优缺点。详细如下

1. 基于XA协议的两阶段提交

XA是由X/Open DTP(X/Open Distributed Transaction Processing Reference Model)组织提出的分布式事务的规范。XA规范主要定义了(全局)事务管理器(Transaction Manager)和(局部)资源管理器(Resource Manager)之间的接口。XA接口是双向的系统接口,在事务管理器(Transaction Manager)以及一个或多个资源管理器(Resource Manager)之间形成通信桥梁,下图说明了事务管理器、资源管理器,与应用程序之间的关系。

e32d897e6448a72c98d992c398dd4a1f_MD5.webp

DTP 模型主要是通过 2PC 来控制事务管理器和资源管理之间的交互的

  1. 准备阶段,事务管理器发送PreCommit请求,并进入Prepared阶段,询问资源管理器分支事务是否正常执行完毕,资源管理返回是或者否
  2. 提交阶段,事务管理器根据上一阶段所有资源管理器的反馈结果,如果都是那么提交事务进行doCommit请求,如果否一个失败,那么回滚事务

1.1 JTA 事务规范

Java平台上事务规范JTA Transaction API)也定义了对XA事务的支持,在JTA 中,事务管理器抽象为javax.transaction.TransactionManager接口,并通过底层事务服务(即JTS)实现,JTA仅仅定义了接口,具体的实现则是由供应商(如J2EE厂商)负责提供,目前JTA的实现主要由以下几种

  • J2EE容器所提供的JTA实现(JBoss)
  • 独立的JTA实现:如JOTM,Atomikos.这些实现可以应用在那些不使用J2EE应用服务器的环境里用以提供分布事事务保证。如Tomcat,Jetty以及普通的java应用

1.2 XA 优缺点

优点: 对业务无侵入,但是对事务管理器和资源管理器要求比较高
缺点

  • 同步阻塞:在二阶段提交的过程中直到 commit 结束为止,所有节点都需要等到其它节点完成后才能够释放事务资源,这种同步阻塞极大的限制了分布式系统的性能
  • 单点问题:协调者(事务管理器)如果一旦出现故障那么整个服务就会不可用,所以需要实现对应的协调者(资源管理器)选举操作
  • 数据不一致:如果说所有资源管理器都反馈的准备好了,这时候进入 commit 阶段,但是由于网络问题或者说某个资源管理器挂了,那么就会存在一部分机器执行了事务,另外一部分没有执行导致数据不一致
  • 容错性不好:如果说在提交询问阶段,参与者挂了那么这个时候协调者就只能依靠超时机制来处理是否需要中断事务

可以看出基于XA协议的两阶段提交存在阻塞低效和数据不一致的问题,所以在大型应用需要较高的吞吐量的应用是很少使用这种方案的,一般会用在数据库中间件中,如Shardingsphere

2. 三阶段提交

三阶段提交 (3PC)是在两阶段提交 (2PC)的基础上进行优化
主要涉及两个方面

  • 引入超时机制:在协调者和参与者中引入超时机制
  • 细分阶段:把两阶段提交协议的第一个阶段再次细分成询问阶段、和预备阶段
    767cca2b24897539de4ccc079bea0096_MD5.webp 3PC 来控制事务管理器和资源管理之间交互如下
  1. 询问阶段,事务管理器向参与者发送CanCommit请求。询问是否可以执行事务提交操作。然后开始等待参与者的响应
  2. 准备阶段,事务管理器发送PreCommit请求,并进入Prepared阶段,询问资源管理器分支事务是否正常执行完毕,资源管理返回是或者否
  3. 提交阶段,事务管理器根据上一阶段所有资源管理器的反馈结果,如果都是那么提交事务进行doCommit请求,如果有一个失败,那么回滚事务

2.1 3PC的优缺点

优点: 相对于二级段提交协议,三阶段提交协议的最大的优点就是降低了参与者的阻塞的范围,并且能够在出现单点故障后继续达成一致

缺点: 三阶段提交协议在去除阻塞的同时也引入了新的问题,那就是参与者接收到precommit消息后,如果出现网络分区,此时协调者所在的节点和参与者无法进行正常的网络通信,在这种情况下,该参与者依然会进行事务的提交,这必然出现数据的不一致性。

3. TCC 模式

TCC编程模式本质上也是一种二阶段协议,不同在于TCC编程模式需要与具体业务耦合,下面首先看下TCC编程模式步骤:

  • 所有事务参与方都需要实现Try,Confirm,Cancle接口。
  • 事务发起方向事务协调器发起事务请求,事务协调器调用所有事务参与者的try方法完成资源的预留,这时候并没有真正执行业务,而是为后面具体要执行的业务预留资源,这里完成了一阶段。
  • 如果事务协调器发现有参与者的try方法预留资源时候发现资源不够,则调用参与方的cancle方法回滚预留的资源,需要注意cancle方法需要实现业务幂等,因为有可能调用失败(比如网络原因参与者接受到了请求,但是由于网络原因事务协调器没有接受到回执)会重试。
  • 如果事务协调器发现所有参与者的try方法返回都OK,则事务协调器调用所有参与者的confirm方法,不做资源检查,直接进行具体的业务操作。
  • 如果协调器发现所有参与者的confirm方法都OK了,则分布式事务结束。
  • 如果协调器发现有些参与者的confirm方法失败了,或者由于网络原因没有收到回执,则协调器会进行重试。

4fb5fea6688841afa15cfb0effb11d96_MD5.png

TCC 交互如下

  1. 主业务服务分别调用所有从业务的 try 操作,并在活动管理器中登记所有从业务服务。当所有从业务服务的 try 操作都调用成功或者某个从业务服务的 try 操作失败,进入第二阶段。
  2. 活动管理器根据第一阶段的执行结果来执行 confirm 或 cancel 操作。
  3. 如果第一阶段所有 try 操作都成功,则活动管理器调用所有从业务活动的 confirm操作。否则调用所有从业务服务的 cancel 操作。

3.1 TCC的优缺点

优点:解决了跨应用业务操作的原子性问题
缺点:TCC的Try、Confirm和Cancel操作功能需业务提供,开发成本高

4. 消息一致性

首先消息一致性属于柔性事务,消息一致性需要利用消息中间件比如ActiveMQ、RocketMQ、Kafka、RabbitMQ等,当生产者执行成功时候,此时业务所产生的消息一定要推送出去,否则就丢消息。目前解决消息一致性的方案有3种。

  • 使用RocketMQ处理事务消息
  • 使用本地消息表
  • 使用独立消息服务

4.1 使用RocketMQ处理事务消息

目前流行的消息中间件如RabbitMQ、Kafka、RocketMQ等,只有RocketMQ支持事务消息。RocketMQ的事务消息模型借鉴了2PC模式,整个交互流程如下图所示:
eff5927c7ca11e0c31a93ca8375bf7ea_MD5.png

基于 RocketMQ 交互如下

  1. 事务发起方首先发送一条半消息到MQ;
  2. MQ通知事务发起方,表示成功收到了这条半消息;
  3. 事务发起方执行本地事务;
  4. 根据本地事务执行结果向MQ反馈结果是commit或者是rollback,如果消息是rollback,MQ将删除该半消息不进行下发,如果是commit消息,MQ将会把这个消息发送给consumer端。
  5. 如果第4步没有成功反馈,MQ会发送状态回查确认;
  6. 事务发起方检查本地事务状态;
  7. 将第6步结果反馈给MQ。

4.2 使用本地消息表

因为很多消息中间件并没有支持事务消息,因此我们可以搭建一个消息服务,有消息服务来控制消息的发送和丢弃。基于独立消息服务的流程如下图:
95958d021f73c280b0d3dfc2d01112df_MD5.png

需要注意的是执行业务和记录消息数据要在一个事务里面执行

使用本地消息表的交互步骤如下

  1. 事务发起方正常执行业务,并记录消息数据
  2. 定时任务到DB中去轮训状态为待发送的消息,然后将消息投递给MQ发送消息到MQ
  3. 如果正常返回,标记状态为已发送,否则清楚记录
  4. 后面消息的消费失败的话,则依赖MQ本身的重试来完成

基于本地消息表的方案虽然可以做到消息的最终一致性,但是它有一个比较严重的弊端,每个业务系统在使用该方案时,都需要在对应的业务库创建一张消息表来存储消息。针对这个问题,我们可以将该功能单独提取出来,做成一个消息服务来统一处理,下面就是我们要讨论的。

4.3 使用独立消息服务

因为很多消息中间件并没有支持事务消息,因此我们可以搭建一个消息服务,有消息服务来控制消息的发送和丢弃。基于独立消息服务的流程如下图:
88231ee90e3be4df2193b841aac8b1f0_MD5.png

独立消息服务最终一致性本地消息表最终一致性最大的差异就在于将消息的存储单独地做成了一个RPC的服务,这个过程其实就是模拟了事务消息的消息预发送过程,如果预发送消息失败,那么生产者业务就不会去执行,因此对于生产者的业务而言,它是强依赖于该消息服务的。

使用独立消息服务的交互步骤如下

  1. 事务发起方先预发送消息到消息服务
  2. 事务发起方正常执行业务
  3. 发送业务处理结果给消息服务
  4. 定时任务到DB中去轮训状态为待发送的消息,到事务发起方确认直接结果,如果结果是执行成功,标记状态为已发送,否则清楚记录
  5. 定时任务到DB中去轮训状态为可发送的消息,然后将消息投递给MQ发送消息到MQ
  6. 后面消息的消费失败的话,则依赖MQ本身的重试来完成

4.4 消息一致性的优缺点

优点: : 消息数据独立存储,降低业务系统与消息系统之间的耦合。
缺点:

  • 对代码侵入性较高
  • RocketMQ: 一次消息发送需要两次网络请求(half消息 + commit/rollback)
  • 本地消息表: 每个应用都需要建本地消息表,并且有额外的定时任务开发
  • 独立消息服务: 存在单点问题,一次发送至少两次网络请求,并且有额外的定时任务开发

5. 总结

本节我们主要介绍了4种常见的分布式解决方案,其中消息一致性又分了3种方案,针对以上方案的特点,读者可以根据实际的的需要选择使用哪种解决方案,也可以多种配合使用。