分布式事务

分布式事务解决方案:

       

 X/Open分布式事务模型

       X/Open DTP包含三种角色:

  • AP: Application 表示应用程序
  • RM: Resource Manager 资源管理器,比如数据库
  • TM: Transaction Manager 表示事务管理器,协调事务和管理资源,类似于Spring的Transaction Manager

      

  分布式环境,RM代表数据库,可能有多个,TM需要管理多个数据库的事务,就是说TM就是一个全局事务管理器。步骤如下:

  • 配置TM,多个RM注册到TM上,相当于TM注册RM作为数据源;
  • AP从TM管理中的RM获取连接,如果RM是数据库则获取JDBC连接;
  • AP向TM发起一个全局事务,生成全局事务ID(XID),XID通知各个RM;
  • AP获取到连接后直接操作RM,此时AP每次操作时会把XID传递给RM;
  • AP结束全局事务,TM会通知各个RM全局事务结束;
  • 根据各个RM的事务执行结果,执行提交或者回滚操作。

  需要注意的是:TM与多个RM之间的事务控制,是基于XA协议来完成的,XA协议是X/Open提出分布式事务处理规范。 

  

2.两阶段提交协议

     根据上图可知,2PC具体步骤:

  • 准备阶段:TM通知RM准备分支事务,记录事务日志,并告诉事务管理器的准备结果。
  • 提交/回滚阶段:如果所有的资源管理器RM,在准备阶段明确返回成功,则TM向所有的资源管理器RM发起事务提交完成数据的变更,反之如果有任何一个资源管理器RM明确返回失败,则TM会向所以RM发送事务回滚指令。

  

缺点:

  • 同步阻塞:所有RM都需要及时反馈,否则会一直阻塞,占用资源不释放。
  • 事务协调者TM的单点故障:如果TM在第二阶段出现故障,那么其它参与者RM会一直处于锁定状态;
  • “脑裂”导致数据不一致问题:在第二阶段commit提交时,只有部分RM接受到了并且完成commit提交了事务,但是由于网络问题,导致只有一部分RM未收到commit导致无法提交事务,会出现数据不一致的问题。

三阶段提交协议

       三阶段提交协议是两阶段的改良版,利用超时机制解决了同步阻塞的问题,步骤如下:

  第一阶段:CanCommit(询问阶段):事务协调者向参与者发送事务执行请求,询问是否可以完成指令,参与者只需要回答是或者不是,会有超时机制;

  第二阶段:PreComiit(准备阶段):事务协调者会根据参与者的反馈结果决定是否继续执行,如果在第一个阶段CanCommit都收到了请求的话,就开始执行写redo和undo日志,并且返回ACK给协调者,这里即是二阶段提交的第一阶段。

  第三阶段:DoCommit(提交或回滚阶段):如果在第二阶段PreCommit提交成功以后,那么事务协调者会向所有的参与者发起事务提交指令,如果其中某个参与者返回失败,则执行终止指令回滚事务。

  

  比较二阶段提交协议,三阶段提交协议有以下不同:

  1)增加CanCommit阶段:可以尽早发现参与者无法执行的情况,及时中断;

  2)增加了超时机制:参与者与事务协调者都引入超时机制,一旦超时,事务协调者和参与者会继续提交事务并且任务处于成功状态(因为在这种情况下事务默认为成功的可能性比较大),事实上,第三阶段提交协议下仍然可能出现不一致的情况(但是概率很小)

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

TCC补偿型方案(最常用)

        TCC 是一种比较成熟的分布式数据一致性解决方案,是把一个完整业务拆分为三个步骤:

    Try:主要是数据的校验或者资源的预留;

    Confirm:确认真正执行的任务,只操作Try阶段预留的资源;

    Cancel:取消执行,释放Try阶段预留的资源。

  其实TCC是2PC的思想,第一阶段通过Try进行准备工作,第二阶段Confirm/Cancel表示Try阶段操作的确认和回滚。

       

   比如:用户通过账户余额购买一个理财产品,涉及两个事件,对应两个不同的微服务中的方法:账户服务中,对用户账户余额进行扣款;理财产品中,对指定产品可申购金额进行扣减。则需要TCC补偿方案控制:

  • 在账户服务中Try方法对余额进行冻结,Confirm方法把Try方法冻结的余额进行实际扣款,Cancel方法把Try方法冻结余额进行解冻;
  • 在申购金额中Try方法对本次申购部分额度进行冻结,Confirm方法把Try方法冻结的额度进行实际减扣,Cancel方法把Try方法冻结额度进行释放;
  • 主业务方法中就会调用两个微服务业务中的方法(即对余额减扣、申购额度减扣),就会先调用Try对资源预留,如果Try阶段都正常,则进行Confirm对预留资源进行实际应用,如果不正常则Cancel取消对资源的预留,对资源进行回滚,从而保证数据的一致性。

   假设用户下单操作来自3个系统下单系统、资金账户系统、红包账户系统,下单成功需要同时调用资金账户服务和红包服务完成支付。假设购买商品1000元,使用账户红包200元,余额800元,确认支付。

   Try操作

  1. tryX 下单系统创建待支付订单

  2. tryY 冻结账户红包200元

  3. tryZ 冻结资金账户800元

         Confirm操作

  1. confirmX 订单更新为支付成功

  2. confirmY 扣减账户红包200元

  3. confirmZ 扣减资金账户800元

         Cancel操作

  1. cancelX 订单处理异常,资金红包退回,订单支付失败

  2. cancelY 冻结红包失败,账户余额退回,订单支付失败

  3. cancelZ 冻结余额失败,账户红包退回,订单支付失败

        注意的是,微服务框架宕机或者网络异常导致没法完成Cancel/Confirm请求,TCC事务框架会记录一些分布式事务的操作日志,保存分布式事务运行的各个阶段和状态,之后TCC事务协调器根据日志进行重试,达到数据的最终一致性

        并发控制

  用户在实现 TCC 时,应当考虑并发性问题,将锁的粒度降到最低,以最大限度的提高分布式事务的并发性。

  以下还是以A账户扣款为例,“账户 A 上有 100 元,事务 T1 要扣除其中的 30 元,事务 T2 也要扣除 30 元,出现并发”。

在一阶段 Try 操作中,分布式事务 T1 和分布式事务 T2 分别冻结资金的那一部分资金,相互之间无干扰;这样在分布式事务的二阶段,无论 T1 是提交还是回滚,都不会对 T2 产生影响,这样 T1 和 T2 在同一笔业务数据上并行执行。

  

 

   允许空回滚

   事务协调器在调用 TCC 服务的一阶段 Try 操作时,可能会出现因为丢包而导致的网络超时,此时事务管理器会触发二阶段回滚,调用 TCC 服务的 Cancel 操作,而 Cancel 操作调用未出现超时。

   TCC 服务在未收到 Try 请求的情况下收到 Cancel 请求,这种场景被称为空回滚;空回滚在生产环境经常出现,用户在实现TCC服务时,应允许允许空回滚的执行,即收到空回滚时返回成功。

  

  防悬挂控制

  事务协调器在调用 TCC 服务的一阶段 Try 操作时,可能会出现因网络拥堵而导致的超时,此时事务管理器会触发二阶段回滚,调用 TCC 服务的 Cancel 操作,Cancel 调用未超时;在此之后,拥堵在网络上的一阶段 Try 数据包被 TCC 服务收到,出现了二阶段 Cancel 请求比一阶段 Try 请求先执行的情况,此 TCC 服务在执行晚到的 Try 之后,将永远不会再收到二阶段的 Confirm 或者 Cancel ,造成 TCC 服务悬挂。

  用户在实现  TCC 服务时,要允许空回滚,但是要拒绝执行空回滚之后 Try 请求,要避免出现悬挂。

  

 

   幂等控制

  网络数据包重传,还是异常事务的补偿执行,都会导致 TCC 服务的 Try、Confirm 或者 Cancel 操作被重复执行;用户在实现 TCC 服务时,需要考虑幂等控制,即 Try、Confirm、Cancel 执行一次和执行多次的业务结果是一样的。

2.基于MQ最终一致性方案(基于具有事务模型消息的MQ,如RocketMQ)

  基于可靠消息的一致性是比较常用的分布式数据一致性解决方案,比如支付服务于账户服务之间的流程需要MQ:

       

       但是支付服务本地事务到MQ发送消息存在非原子操作问题,如果先执行本地事务,再发送消息到MQ,MQ可能出现超时情况,导致本地事务可能回滚,从而导致数据不一致

  

    如果先发送消息,再执行数据库事务,在这种情况下可能会出现消息发送成功但是本地事务更新失败的情况下,仍然存在数据不一致的问题

  

  MQ与服务之间的数据不一致的情况,可以采用MQ的事务消息模型,比如RocketMQ为例:

  • 生产者发送事务消息到消息队列,消息队列此时只记录消息的数据,消费者无法消费此信息;
  • 生产者之后执行本地事务,根据执行结果发送一条确认消息给消息队列服务器,告诉消费者是否消费该消息;如果生产者本地事务执行成功则发送一条Commit消息,即告诉消费者可以消费该消息,否则消息队列服务器就会删除该消息;
  • 如果在生产者执行本地事务的过程中因为某些情况一直未给消息队列服务器发送确认,那么消息队列服务器就会主动回查生产者执行本地事务的结果,然后根据结果执行上述步骤;
  • 消息队列服务器上存储的消息被生产者确认后,消费者就可以消费该消息,最后发送一个确认标识给消息队列服务器,表示该消息投递成功。

  

可知:

  • 在RocketMQ事务模型中,事务是由生产者完成的,当消息没有签收的情况下,MQ队列服务会重复投递。
  • RocketMQ的事务消息模型最核心的就是事务回查(在没有收到生产的commit/rollback的情况下,主动查询事务状态)

3.最大努力通知型

  最大努力通知就是在客户端没有返回消息确认时,支付会不断地进行重试,直到收到一个消息确认或者达到最大重试次数。

  例如如果商户不返回SUCCESS标识,每隔1min、5min…会不断通知商户支付结果,达到最大次数以后就不通知,将会同时提交查询结果,定时任务出发查询。

Seata框架的分布式事务模式

      Seata是致力于微服务架构下提高性能和简易使用的分布式事务,它提供了AT、TCC、Saga和XA事务模式。

1.AT模式

  AT模式是Seata最主推的分布式事务且基于XA演进而来的解决方案,主要有三个角色:TM、RM和TC,其中TM和RM作为Seata的客户端和业务集成:

  • TC作为Seata服务器独立部署。
  • TM向TC注册一个全局事务,并生成全局唯一的XID;

  在AT模式下,数据库资源被当做RM,访问RM时,Seata会对请求进行拦截;

  每个本地事务提交时,RM会向TC(Transaction Coordinator,事务协调器)注册一个

       

具体步骤如下:

  1) TM向TC注册全局事务,并生成全局唯一XID;

  2) RM向TC注册分支事务,并将其纳入到该XID对应的全局事务范围。

  3) RM向TC汇报资源的准备状态

  4) TC汇总所有事务参与者的执行状态,决定分布式事务全部回滚还是提交

  5) TC通知所有的RM提交/回滚事务

AT模式和XA类似,也是一个2PC模型

Saga模式

       Saga模式又称之为长事务解决方案,核心思想是:把一个业务流程中的长事务拆分成多个本地短事务,业务流程中每个参与者事务执行失败,则通过补偿机制前面已经成功的参与者

       

  按照Saga的工作模式,一般有两种方式:

  1) T1, T2,T3,T4…,Ti,表示所有事务正常运行

  2) T1, T2,T3,T4…,Tj, Cj,…,C2,C1:表示执行到Tj事务时出现异常,通过补充操作撤销之前的所有成功的sub-transaction。

  另外,提供两种补偿模式:一是向后补偿,即第二种方式,任一子事务执行失败,则把之前的子事务结果逐一撤销。另一种就是向前恢复,都可以出现失败情况,在最坏的情况下只能进行人工干预处理。

优势:

  • 一阶段直接提交本地事务(相比较XA/TCC模式没有Try);
  • 没有锁等待,性能比较高;
  • 在事件驱动模式下短事务可以异步执行;
  • 补偿机制实现比较简单。

劣势:

  不提供原子性与隔离性支持,合理性影响性比较大,比如用户赠送了一张优惠券,但是已经把优惠券用完了,无法对这个sub-transaction进行补偿

Saga的整个过程会涉及一个两种协调模式:

1)事件/编排式

  把Saga的决策和执行顺序逻辑分布在Saga的每一个参与者中,通过交换事件的方法进行沟通。

  即第一个服务执行完本地事务之后,发送一个事件,这个事件会被一个或多个服务监听,监听到该事件的服务本地事务并发布新的事件,此后一直延续这种事件触发模式,直到该业务流程中最后一个服务的本地事务执行结束,才意味着整个分布式长事务也执行结束。

     

       由事件发布来驱动事务执行

  • 订单创建新的订单,把订单状态设置为待支付,并发布一个ORDE_CREATE_EVENT事件;
  • 库存服务监听到ORDER_CREATE_EVENT事件后,执行本地的库存冻结方法,如果执行成功,则发布一个ORDER_PREPARED_EVENT事件;
  • 支付服务监听ORDER_PREPARED_EVENT事件后,执行账户扣款方法,并发布PAY_ORDER_EVENT事件;
  • 最后积分服务监听PAY_ORDER_EVENT事件,增加账户积分,并更新订单状态为成功。

上述某个步骤如果执行失败,都会发送一个失败事件,每个服务都会监听失败的情况根据实际需要逐一回滚。

2)命令/协同式

  把Saga的决策和执行顺序逻辑集中在一个Saga控制类中,它以命令/回复的方式与每项服务进行通信,告诉它们应该执行哪些操作。

    

      命令/协调式的实现步骤如下:

  • 订单服务首先创建一个订单,然后创建一个订单Saga协调器,启动订单事务
  • Saga协调器向库存服务发送冻结库存命令,库存服务通过Order Saga Reply Queue回复执行结果;
  • 接着Saga协调器继续向支付服务发起账户扣款命令,支付服务通过Order Saga Reply Queue回复执行结果。
  • 最后,Saga协调器向积分服务发起增加积分服务,积分服务回复执行结果

  值得注意的是,订单Saga协调器必须需要提前知道“创建订单”的所有流程,并且在某个环节执行失败,都需要每个参与者发送命令撤销之前的事务操作。

   

参考:

      https://www.cnblogs.com/jian0110/p/14744291.html

      https://www.cnblogs.com/jajian/p/10014145.html

      https://blog.csdn.net/zxd1435513775/article/details/122178290

 

posted on 2022-04-06 11:37  溪水静幽  阅读(140)  评论(0)    收藏  举报