数据不一致:当协调者发送 commit 之后,有的参与者收到 commit 消息,事务执行成功,有的没有收到,处于阻塞状态,这段时间会产生数据不一致。
不确定性:当协调者发送 commit 之后,并且此时只有一个参与者收到了 commit,那么当该参与者与协调器同时宕机之后,重新选举的协调器无法确定该条消息是否提交成功。
2PC 的优势在于对业务没有侵入,可以利用数据库自身机制来进行事务的提交和回滚。
常见的基于 2PC 的具体落地方案有:JTA(XA 规范) 和 Seata( AT 模式)。
2、三阶段提交三阶段提交(Three-phase commit),简称为 3PC,是 2PC 的改进版本。同时在协调者和参与者都引入了超时机制,还在 2PC 中的准备阶段和提交阶段中间增加了一个预提交阶段。
准备阶段(CanCommit):协调者向各个参与者发送请求,询问是否可以执行事务,但并不执行事务。
预提交阶段(PreCommit):如果从协调者得到的反馈是满足执行条件,那么就发送预提交请求,并开始执行事务;如果从协调者得到的反馈是不满足执行条件或者超时,则发送事务中断请求。
提交阶段(DoCommit):如果预提交阶段发送的是预提交请求,那么正常提交事务;如果预提交阶段发送的是事务中断请求,那么直接中断事务。
相对于 2PC,3PC 主要解决的单点故障问题,并减少阻塞,因为一旦参与者无法及时收到来自协调者的信息之后,他会默认执行 commit。而不会一直持有事务资源并处于阻塞状态。但是这种机制也会导致数据一致性问题,因为,由于网络原因,协调者发送的中断响应没有及时被参与者接收到,那么参与者在等待超时之后执行了 commit 操作。这样就和其他接到中断命令并执行回滚的参与者之间存在数据不一致的情况。而且 3PC 整体的交互过程更长,性能也会有所下降。
3PC 目前似乎只存在于理论,还没有具体落地方案。
3、TCC2PC 和 3PC 都是依赖于数据库的事务提交和回滚,但是有时候很多业务并不仅仅只涉及到数据库,可能还会发送短息、消息等等,而 TCC 就是属于业务层面或者说是应用层面的分布式事务。
TCC 方案分为Try-Confirm-Cancel三个阶段,属于补偿性分布式事务。
Try 阶段:完成所有业务检查(一致性),预留业务资源(准隔离性)
Confirm 阶段:确认执行业务操作,不再做任何业务检查, 只使用Try阶段预留的业务资源。
Cancel 阶段:取消Try阶段预留的业务资源。
有的朋友可能会问了,Try 成功了会执行 Confirm,失败了会执行 Cancel,那 Confirm 阶段失败了怎么办?这时候只能设置重试机制,不断重试调失败的 Confirm,直到成功为止,真有怎么也不成功的,就只能人工介入了。
TCC 需要根据每个场景和业务逻辑来设计相应的操作,所以很大程度增加了业务代码的复杂度,对业务有很大的侵入。
虽说对业务有侵入,但是 TCC 没有资源的阻塞,每一个方法都是直接提交事务的,如果出错是通过业务层面的 Cancel 来进行补偿,所以也称补偿性事务方法。
TCC 要注意的几个问题:
幂等问题:因为网络调用无法保证请求一定能到达,所以都会有重调机制,因此对于 Try、Confirm、Cancel 三个方法都需要幂等实现,避免重复执行产生错误。
空回滚问题:指的是 Try 方法由于网络问题没收到超时了,此时事务管理器就会发出 Cancel 命令,那么需要支持 Cancel 在未执行 Try 的情况下能正常的 Cancel。
悬挂问题:这个问题也是指 Try 方法由于网络阻塞超时触发了事务管理器发出了 Cancel 命令,但是执行了 Cancel 命令之后 Try 请求到了。所以空回滚之后还得记录一下,防止 Try 的再调用。
4、本地消息表本地消息表分布式事务解决方案是国外的 eBay 提出的一套方案。其实就是利用了各系统本地的事务来实现分布式事务,在数据库中存放一张事务消息表,在执行业务操作的时候, 将业务的执行和将消息放入消息表中的操作放在同一个事务中。