当前位置:网站首页>常见的分布式事务解决方案

常见的分布式事务解决方案

2022-08-09 06:44:00 悬浮海

全文:Java-微服务下的分布式事务介绍及其解决方案-目录导航


第五步:解决问题的方案:常见的分布式事务解决方案

常见的分布式事务解决方案

1、基于XA协议的两阶段提交(2pc)

在此之前,有篇关于:MySQL高级特性之分布式(XA)事务的介绍,感兴趣的可以看看:MySQL高级特性之分布式(XA)事务的介绍

XA是一个分布式事务协议,由Tuxedo提出。XA中大致分为两部分:事务管理器(协调者)和本地资源管理器(参与者)。其中本地资源管理器(参与者)往往由数据库实现,比如Oracle、DB2这些商业数据库都实现了XA接口,而事务管理器(协调者)作为全局的调度者,负责各个本地资源的提交和回滚。XA实现分布式事务的原理如下:

两阶段提交(Two-phase Commit,2PC),通过引入协调者(Coordinator)来协调参与者的行为,并最终决定这些参与者是否要真正执行事务。两个阶段过程如下:

  • 第一阶段:准备阶段:协调者询问参与者事务是否执行成功,参与者发回事务执行结果。

  • 第二阶段:提交阶段:如果事务在每个参与者上都执行成功,事务协调者发送通知让参与者提交事务;否则,协调者发送通知让参与者回滚事务。

需要注意的是,在准备阶段,参与者执行了事务,但是还未提交。只有在提交阶段接收到协调者发来的通知后,才进行提交或者回滚

  • 2PC的优点:实现强一致性,部分关系数据库支持(Oracle、MySQL等)。

  • 缺点:整个事务的执行需要由协调者在多个节点之间去协调,增加了事务的执行时间,性能低下。

1.1、 两阶段提交存在的问题:

  • 同步阻塞 :所有事务参与者在等待其它参与者响应的时候都处于同步阻塞状态,无法进行其它操作。

  • 单点问题 :协调者在 2PC 中起到非常大的作用,发生故障将会造成很大影响。特别是在阶段二发生故障,所有参与者会一直等待状态,无法完成其它操作。

  • 数据不一致 :在阶段二,如果协调者只发送了部分 Commit 消息,此时网络发生异常,那么只有部分参与者接收到 Commit 消息,也就是说只有部分参与者提交了事务,使得系统数据不一致。

  • 太过保守 :任意一个节点失败就会导致整个事务失败,没有完善的容错机制。

总的来说,XA协议比较简单,而且一旦商业数据库实现了XA协议,使用分布式事务的成本也比较低。但是,XA也有致命的缺点,那就是性能不理想,特别是在交易下单链路,往往并发量很高,XA无法满足高并发场景。XA目前在商业数据库支持的比较理想,在mysql数据库中支持的不太理想,mysql的XA实现,没有记录prepare阶段日志,主备切换回导致主库与备库数据不一致。许多nosql也没有支持XA,这让XA的应用场景变得非常狭隘。

1.2、三阶段提交

在两阶段提交的基础上有了三阶段提交:

  • 三阶段提交是在两阶段提交之前加了一步询问的操作:保证所有系统都是正常的(询问能不能行),增加了成功了概率

  • 三个阶段都加了超时机制

  • 3pc不能解决2pc的所有问题,只是降低灾难发生的概率

2、MQ消息事务+最终一致性

所谓的消息事务就是基于消息中间件的两阶段提交,本质上是对消息中间件的一种特殊利用,它是将本地事务和发消息放在了一个分布式事务里,保证要么本地操作成功成功并且对外发消息成功,要么两者都失败,开源的RocketMQ就支持这一特性,具体原理如下:

过程如下:

  • 1、A系统向消息中间件发送一条预备消息
  • 2、消息中间件保存预备消息并返回成功
  • 3、A执行本地事务
  • 4、A发送提交消息给消息中间件

通过以上4步完成了一个消息事务。对于以上的4个步骤,每个步骤都可能产生错误,下面一一分析:

  • 步骤一出错,则整个事务失败,不会执行A的本地操作

  • 步骤二出错,则整个事务失败,不会执行A的本地操作

  • 步骤三出错,这时候需要回滚预备消息,怎么回滚?答案是A系统实现一个消息中间件的回调接口,消息中间件会去不断执行回调接口,检查A事务执行是否执行成功,如果失败则回滚预备消息

  • 步骤四出错,这时候A的本地事务是成功的,那么消息中间件要回滚A吗?答案是不需要,其实通过回调接口,消息中间件能够检查到A执行成功了,这时候其实不需要A发提交消息了,消息中间件可以自己对消息进行提交,从而完成整个消息事务

基于消息中间件的两阶段提交往往用在高并发场景下,将一个分布式事务拆成一个消息事务(A系统的本地操作+发消息)+B系统的本地操作,其中B系统的操作由消息驱动,只要消息事务成功,那么A操作一定成功,消息也一定发出来了,这时候B会收到消息去执行本地操作,如果本地操作失败,消息会重投,直到B操作成功,这样就变相地实现了A与B的分布式事务。原理如下:

虽然上面的方案能够完成A和B的操作,但是A和B并不是严格一致的,而是最终一致的,我们在这里牺牲了一致性,换来了性能的大幅度提升。当然,这种玩法也是有风险的,如果B一直执行不成功,那么一致性会被破坏,具体要不要玩,还是得看业务能够承担多少风险。

2.1、第二种解释

对比上面第一种解释,来看看第二种解释。

有一些第三方的MQ是支持事务消息的,比如RocketMQ,他们支持事务消息的方式也是类似于采用的二阶段提交,但是市面上一些主流的MQ都是不支持事务消息的,比如 RabbitMQ 和 Kafka 都不支持。

以阿里的 RocketMQ 中间件为例,其思路大致为:

  • 第一阶段Prepared消息,会拿到消息的地址。

  • 第二阶段执行本地事务,

  • 第三阶段通过第一阶段拿到的地址去访问消息,并修改状态。

也就是说在业务方法内要想消息队列提交两次请求,一次发送消息和一次确认消息。如果确认消息发送失败了RocketMQ会定期扫描消息集群中的事务消息,这时候发现了Prepared消息,它会向消息发送者确认,所以生产方需要实现一个check接口,RocketMQ会根据发送端设置的策略来决定是回滚还是继续发送确认消息。这样就保证了消息发送与本地事务同时成功或同时失败。
在这里插入图片描述

  • 优点: 实现了最终一致性,不需要依赖本地数据库事务。

  • 缺点: 实现难度大,主流MQ不支持,RocketMQ事务消息部分代码也未开源。

3、补偿事务(TCC)

TCC 其实就是采用的补偿机制,其核心思想是:针对每个操作,都要注册一个与其对应的确认和补偿(撤销)操作。它分为三个阶段:

  • Try 阶段主要是对业务系统做检测及资源预留
  • Confirm 阶段主要是对业务系统做确认提交,Try阶段执行成功并开始执行 Confirm阶段时,默认 Confirm阶段是不会出错的。即:只要Try成功,Confirm一定成功。
  • Cancel 阶段主要是在业务执行错误,需要回滚的状态下执行的业务取消,预留资源释放。

举个例子,假入 Bob 要向 Smith 转账,思路大概是: 我们有一个本地方法,里面依次调用

  1. 首先在 Try 阶段,要先调用远程接口把 Smith 和 Bob 的钱给冻结起来。
  2. 在 Confirm 阶段,执行远程调用的转账的操作,转账成功进行解冻。
  3. 如果第2步执行成功,那么转账成功,如果第二步执行失败,则调用远程冻结接口对应的解冻方法 (Cancel)。

优点: 跟2PC比起来,实现以及流程相对简单了一些,但数据的一致性比2PC也要差一些

缺点: 缺点还是比较明显的,在2,3步中都有可能失败。TCC属于应用层的一种补偿方式,所以需要程序员在实现的时候多写很多补偿的代码,在一些场景中,一些业务流程可能用TCC不太好定义及处理。

简单如下:

所谓的TCC编程模式,也是两阶段提交的一个变种。TCC提供了一个编程框架,将整个业务逻辑分为三块:Try、Confirm和Cancel三个操作。以在线下单为例,Try阶段会去扣库存,Confirm阶段则是去更新订单状态,如果更新订单失败,则进入Cancel阶段,会去恢复库存。总之,TCC就是通过代码人为实现了两阶段提交,不同的业务场景所写的代码都不一样,复杂度也不一样,因此,这种模式并不能很好地被复用。

4、本地消息表(异步确保)

本地消息表与业务数据表处于同一个数据库中,这样就能利用本地事务来保证在对这两个表的操作满足事务特性,并且使用了消息队列来保证最终一致性。

  1. 在分布式事务操作的一方完成写业务数据的操作之后向本地消息表发送一个消息,本地事务能保证这个消息一定会被写入本地消息表中。
  2. 之后将本地消息表中的消息转发到 Kafka 等消息队列中,如果转发成功则将消息从本地消息表中删除,否则继续重新转发。
  3. 在分布式事务操作的另一方从消息队列中读取一个消息,并执行消息中的操作。

在这里插入图片描述

  • 优点: 一种非常经典的实现,避免了分布式事务,实现了最终一致性。

  • 缺点: 消息表会耦合到业务系统中,如果没有封装好的解决方案,会有很多杂活需要处理。

总结

分布式事务,本质上是对多个数据库的事务进行统一控制,按照控制力度可以分为:不控制、部分控制和完全控制

  • 不控制就是不引入分布式事务,
  • 部分控制就是各种变种的两阶段提交,包括上面提到的消息事务+最终一致性、TCC模式,
  • 而完全控制就是完全实现两阶段提交。
  • 部分控制的好处是并发量和性能很好,缺点是数据一致性减弱了,
  • 完全控制则是牺牲了性能,保障了一致性

具体用哪种方式,最终还是取决于业务场景。作为技术人员,一定不能忘了技术是为业务服务的,不要为了技术而技术,针对不同业务进行技术选型也是一种很重要的能力。

以上是一些关于分布式事务的简单总结

原网站

版权声明
本文为[悬浮海]所创,转载请带上原文链接,感谢
https://blog.csdn.net/wang_luwei/article/details/126201857