分布式事物解决选择

事物4大特性:ACID :原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)
分布式环境下的ACID的常见解决方案
可以使用全局事物2pc(两段提交协议)、3pc(三段提交协议),消息中间件、tcc、gts、提供回滚接口、分布式数据库

一、X/A
概念:
X/Open 这个组织定义的一套分布式事务的标准,定义了规范和API接口,由各个厂商进行具体的实现。
X/Open DTP 定义了三个组件: AP,TM,RM
     AP:应用程序
    RM:资源管理器,这里可以是一个DBMS或者消息服务器管理系统,应用程序通过资源管理器对资源进行控制,资源必须实现XA定义的接口
    TM:事务管理器,负责协调和管理事务,提供给AP应用程序编程接口以及管理资源管理器
 AP,TM,RM三者关系:
    AP(应用程序) -> 通过TM控制事物 -> XA接口(TM去注册RM) -> RM
    AP(应用程序) -> 本地接口 -> 通过RM操作资源 -> D
DTP定义了以下几个概念
    1 事务:一个事务是一个完整的工作单元,由多个独立的计算任务组成,这多个任务在逻辑上是原子的。
    2 全局事务:对于一次性操作多个资源管理器的事务,就是全局事务
    3 分支事务:在全局事务中,某一个资源管理器有自己独立的任务,这些任务的集合作为这个资源管理器的分支任务
    4 控制线程:用来表示一个工作线程,主要是关联AP,TM,RM三者的一个线程,也就是事务上下文环境(就是需要标识一个全局事务以及分支事务的关系)
   如果一个事务管理器管理着多个资源管理器,DTP是通过两阶段提交协议来控制全局事务和分支事务
第一阶段:准备阶段 事务管理器通知资源管理器准备分支事务,资源管理器告之事务管理器准备结果
第二阶段:提交阶段 事务管理器通知资源管理器提交分支事务,资源管理器告之事务管理器结果
流程:
第一阶段:
    1、调者会问所有的参与者结点,是否可以执行提交操作
    2、各个参与者开始事务执行的准备工作:如:为资源上锁,预留资源
    3、参与者响应协调者,如果事务的准备工作成功,则回应“可以提交”,否则回应“拒绝提交”
第二阶段:
    1、如果所有的参与者都回应“可以提交”,那么,协调者向所有的参与者发送“正式提交”的命令。参与者完成正式提交,并释放所有资源,然后回应“完成”,           协调者收集各结点的“完成”回应后结束这个Global Transaction
    2、如果有一个参与者回应“拒绝提交”,那么,协调者向所有的参与者发送“回滚操作”,并释放所有资源,然后回应“回滚完成”,协调者收集各结点的“回               滚”回应后,取消这个Global Transaction

XA协议:全局事务管理器与资源管理器的接口。全局事务管理器一般使用XA二阶段协议与数据库进行交互
XA的ACID特性:
原子性:XA议使用2PC原子提交协议来保证分布式事务原子性
隔离性:XA要求每个RM实现本地的事务隔离,子事务的隔离来保证整个事务的隔离
一致性:通过原子性、隔离性以及自身一致性的实现来保证“数据库从一个一致状态转变为另一个一致状态”;
通过MVCC来保证中间状态不能被观察到

两阶段提交协议:XA用于在全局事务中协调多个资源的机制。TM和RM之间采取两阶段提交的方案来解决一致性问题

分布式事物原理:分段式提交
阶段一为准备(prepare)阶段。即所有的参与者准备执行事务并锁住需要的资源。参与者ready时,向transaction manager报告已准备就绪
阶段二为提交阶段(commit)。当transaction manager确认所有参与者都ready后,向所有参与者发送commit命令

XA的性能:
在二阶段提交的过程中,所有的节点都在等待其他节点的响应,无法进行其他操作。这种同步阻塞极大的限制了分布式系统的性能
XA的性能很低。一个数据库的事务和多个数据库间的XA事务性能对比可发现,性能差10倍左右。因此要尽量避免XA事务,例如可以将数据写入本地,用高性能的消息系统分发数据。或使用数据库复制等技术

二、TCC(补偿事务)


概念:
一个完整的TCC业务由一个主业务服务和若干个从业务服务组成,主业务服务发起并完成整个业务活动,
TCC模式要求从服务提供三个接口:Try、Confirm、Cancel
    Try:完成所有业务检查,预留必须业务资源
    Confirm:真正执行业务,不作任何业务检查;只使用Try阶段预留的业务资源;Confirm操作满足幂等性
    Cancel:释放Try阶段预留的业务资源;Cancel操作满足幂等性
流程:
第一阶段:
    主业务服务分别调用所有从业务的try操作,并在活动管理器中登记所有从业务服务。当所有从业务服务的try操作都调用成功或者某个从业务服务的try操作失 败,进入第二阶段
第二阶段:
    活动管理器根据第一阶段的执行结果来执行confirm或cancel操作。如果第一阶段所有try操作都成功,则活动管理器调用所有从业务活动的confirm操作。否则调用所有从业务服务的cancel操作

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

TCC事务框架 都是要记录一些分布式事务的活动日志的,可以在磁盘上的日志文件里记录,也可以在数据库里记录。
保存下来分布式事务运行的各个阶段和状态

区别:
    X/A是资源层面的分布式事务,强一致性,在两阶段提交的整个过程中,一直会持有资源的锁。
    TCC是业务层面的分布式事务,最终一致性,不会一直持有资源的锁

三、三阶段提交

相比2PC:
    1、引入超时机制。同时在协调者和参与者中都引入超时机制
    2、在第一阶段和第二阶段中插入一个准备阶段。保证了在最后提交阶段之前各参与节点的状态是一致的。
         除了引入超时机制之外,3PC把2PC的准备阶段再次一分为二,这样三阶段提交就有CanCommit、PreCommit、DoCommit三个阶段

流程:
CanCommit阶段:
    3PC的CanCommit阶段其实和2PC的准备阶段很像。协调者向参与者发送commit请求,参与者如果可以提交就返回Yes响应,否则返回No响应
    1、事务询问 协调者向参与者发送CanCommit请求。询问是否可以执行事务提交操作。然后开始等待参与者的响应
    2、响应反馈 参与者接到CanCommit请求之后,正常情况下,如果其自身认为可以顺利执行事务,则返回Yes响应,并进入预备状态。否则反馈No
PreCommit阶段 :
    协调者根据参与者的反应情况来决定是否可以记性事务的PreCommit操作。 根据响应情况,有以下两种可能。
    假如协调者从所有的参与者获得的反馈都是Yes响应,那么就会执行事务的预执行
        1、发送预提交请求 协调者向参与者发送PreCommit请求,并进入Prepared阶段
        2、事务预提交 参与者接收到PreCommit请求后,会执行事务操作,并将undo和redo信息记录到事务日志中
        3、响应反馈 如果参与者成功的执行了事务操作,则返回ACK响应,同时开始等待最终指令
   假如有任何一个参与者向协调者发送了No响应,或者等待超时之后,协调者都没有接到参与者的响应,那么就执行事务的中断
        1、发送中断请求 协调者向所有参与者发送abort请求
        2、中断事务 参与者收到来自协调者的abort请求之后(或超时之后,仍未收到协调者的请求),执行事务的中断
doCommit阶段:
    真正的事务提交。 根据响应情况,有以下两种可能。
        1、发送提交请求 协调接收到参与者发送的ACK响应,那么他将从预提交状态进入到提交状态。并向所有参与者发送doCommit请求
        2、事务提交 参与者接收到doCommit请求之后,执行正式的事务提交。并在完成事务提交之后释放所有事务资源
        3、响应反馈 事务提交完之后,向协调者发送Ack响应
        4、完成事务 协调者接收到所有参与者的ack响应之后,完成事务
    中断事务(协调者没有接收到参与者发送的ACK响应(可能是接受者发送的不是ACK响应,也可能响应超时),那么就会执行中断事务)
        1、发送中断请求 协调者向所有参与者发送abort请求
        2、事务回滚 参与者接收到abort请求之后,利用其在阶段二记录的undo信息来执行事务的回滚操作,并在完成回滚之后释放所有的事务资源
        3、反馈结果 参与者完成事务回滚之后,向协调者发送ACK消息
        4、中断事务 协调者接收到参与者反馈的ACK消息之后,执行事务的中断
优点:
1、单点故障问题
2、减少阻塞,一旦参与者无法及时收到来自协调者的信息之后,会默认执行commit,而不会一直持有事务资源并处于阻塞状态
缺点:
一致性问题:由于网络原因,协调者发送的abort响应没有及时被参与者接收到,那么参与者在等待超时之后执行了commit操作。
这样就和其他接到abort命令并执行回滚的参与者之间存在数据不一致的情况

四、本地信息表(异步确保一致性)(可靠事件模式)

条件
服务消费者需要创建一张消息表,用来记录消息状态
服务消费者和提供者需要支持幂等
需要补偿逻辑
每个节点上起定时线程,检查未处理完成或发出失败的消息,重新发出消息,即重试机制和幂等性机制

思路:
    1、消息生产方,需要额外建一个消息表,并记录消息发送状态。
         消息表和业务数据要在一个事务里提交,也就是说他们要在一个数据库里面。然后消息会经过MQ发送到消息的消费方。
         如果消息发送失败,会进行重试发送。
    2、消息消费方,需要处理这个消息,并完成自己的业务逻辑。
         此时如果本地事务处理成功,表明已经处理成功了,如果处理失败,那么就会重试执行。
         如果是业务上面的失败,可以给生产方发送一个业务补偿消息,通知生产方进行回滚等操作
    3、生产方和消费方定时扫描本地消息表,把还没处理完成的消息或者失败的消息再发送一遍。
         如果有靠谱的自动对账补账逻辑,这种方案还是非常实用的
优点:
    一种非常经典的实现,避免了分布式事务,实现了最终一致性
缺点:
     消息表会耦合到业务系统中,如果没有封装好的解决方案,会有很多杂活需要处理

五、MQ事务消息(比如RocketMQ)(可靠事件模式)
    部分第三方的MQ是支持事务消息的,比如RocketMQ,他们支持事务消息的方式也是类似于采用的二阶段提交,
    但是市面上一些主流的MQ都是不支持事务消息的,比如 RabbitMQ 和 Kafka 都不支持
思路:
    首先,发送一个事务消息,这个时候,RocketMQ将消息状态标记为Prepared,注意此时这条消息消费者是无法消费到的
    接着,执行业务代码逻辑,可能是一个本地数据库事务操作
    最后,确认发送消息,这个时候,RocketMQ将消息状态标记为可消费,这个时候消费者,才能真正的保证消费到这条数据

如果确认消息发送失败了,RocketMQ会定期扫描消息集群中的事务消息,如果发现了Prepared消息,它会向消息发送端(生产者)确认。
RocketMQ会根据发送端设置的策略来决定是回滚还是继续发送确认消息。
这样就保证了消息发送与本地事务同时成功或同时失败

六、MQ非事务消息(加独立消息服务、或者本地事务表)


思路:
将消息先发送到一个自己编写的一个"独立消息服务"应用中,刚开始处于prepare状态
业务逻辑处理成功后,确认发送消息,这个时候"独立消息服务"才会真正的把消息发送给消息队列
消费者消费成功后,ack时,除了对消息队列进行ack,对于独立消息服务也要进行ack,"独立消息服务"一般是把这条消息删除。而定时扫描prepare状态的消息,向消息发送端(生产者)确认的工作也由独立消息服务来完成

对于"本地事务表",其实和"独立消息服务"的作用类似,只不过"独立消息服务"是需要独立部署的,而"本地事务表"是将"独立消息服务"的功能内嵌到应用中


常用的分布式框架

LCN: 核心采用3PC+TCC补偿机制
国内开源的TCC: ByteTCC,tcc-transaction,himly

群交流(262200309)
原文地址:https://www.cnblogs.com/webster1/p/12246147.html