1、二阶段提交协议(Two-Phase Commit)
1.1、运行过程
二阶段的一个解决思路就是找出一个全局的协调者,每个参与者的成功或失败都通知协调者,最终由协调者发布是否全部提交还是全部回滚。
分为两个阶段:
- 第一阶段:准备阶段(投票阶段)
- 第二阶段:提交阶段(写入数据阶段)
第一阶段(投票阶段)
该阶段其实就是执行我们所有参与者的代码的阶段,执行完之后将结果成功或失败最终反馈给全局协调者,由全局协调者做进一步的判断,但是此时的事务是还没有提交的。
第二阶段(提交阶段)
在经过第一阶段协调者的询盘之后,各个参与者会回复自己事务的执行情况,这时候存在 3 种可能性:
- 所有的参与者都回复能够正常执行事务。
- 一个或多个参与者回复事务执行失败。
- 协调者等待超时。
第 1 种情况,协调者将向所有的参与者发出提交事务的通知,具体步骤如下:
- 协调者向各个参与者发送 commit 通知,请求提交事务;
- 参与者收到事务提交通知之后执行 commit 操作,然后释放占有的资源;
- 参与者向协调者返回事务 commit 结果信息。
流程图如下所示:
第二种情况及第三情况 出现异常需要回滚操作:
- 协调者向各个参与者发送事务 rollback 通知,请求回滚事务;
- 参与者收到事务回滚通知之后执行 rollback 操作,然后释放占有的资源;
- 参与者向协调者返回事务 rollback 结果信息。
流程图如下所示:
1.2、存在的问题
- 同步阻塞: 在整个流程中每个参与者的事务都没有释放,等待统一提交之后才会释放,所以会阻塞其他的请求。
- 单点故障: 由于协调者的重要性,一旦协调者发生故障。参与者会一直阻塞下去。
- 数据不一致: 当参与者接收到commit请求之后,某些参与者故障,只提交了某些事务。
针对上述问题可以引入 超时机制 和 互询机制 在很大程度上予以解决。
超时机制: 若事务久久没有回复,则直接rollback策略。
互询机制: 参与者互相询问是否接收到了通知,如A询问B是否收到了commit或rollback的通知,若收到了可以与B做相同的操作。如果B此时还没有收到通知则可以稍微等待直到超时执行rollback策略。
2、三阶段提交协议
三阶段提交(Three-phase commit),也叫三阶段提交协议(Three-phase commit protocol),是二阶段提交(2PC)的改进版本。
与二阶段不同有两个改动点:
- 引入了超时机制,协调者与参与者都引入了超时机制。
- 引入准备阶段 ,检查是否可以执行事务,或业务是否满足均可。
一阶段 (CanCommit) :
- **询问事务 ** 是否可以提交,也就是说访问的数据此时是否被其他事务访问,而无法自己无法开启新的事务。
- 业务是否满足等,如转账业务余额是否足够等。
二阶段(PreCommit):
- 该阶段参与者执行我们具体的业务代码,也就是事务内的操作,观察是否可以正常执行。
- 并将
undo
和redo
信息记录到事务日志中,若出现异常进行回滚。 - 最终响应给协调者成功或失败的信息。
该阶段有一下几种可能:
- 正常执行,所有参与者都返回success信息,则协调者进入下一阶段提交阶段。
- 某个参与者回馈fail或超时,协调者直接发送放弃操作。
三阶段(doCommit):
该阶段进行真正的事务提交,协调者向参与者发送commit通知,参与者开始真正的事务提交,执行完之后返回事务执行结果。
有以下几种情况:
- 所有参与者均提交事务成功,则正常执行反馈给协调者事务结果即可。
- 部分参与者失败或参与者超时未反馈给协调者,则协调者对所有参与者发送rollback操作,根据前面保存的日志进行回滚。
- 协调者发送参与者消息(commit或rollback)失败,则参与者会默认commit,因为二阶段已经验证事务操作正常,所以这儿大概率会成功,但也有可能造成数据不一致的情况。
3、XA规范
XA 事务的基础是两阶段提交协议。需要有一个事务协调者来保证所有的事务参与者都完成了准备工作(第一阶段)。如果协调者收到所有参与者都准备好的消息,就会通知所有的事务都可以提交了(第二阶段)。
4、TCC模式
TCC:Try(预处理)、Confirm(确认)、Cancel(取消)
- Try阶段:主要是对业务系统做检测及资源预留,如转账业务查询余额是否充足。
- Confirm阶段:主要是对业务系统做确认提交,Try阶段执行成功并开始执行 Confirm阶段时,默认 Confirm阶段是不会出错的。即:只要Try成功,Confirm一定成功。如果confirm出错了,那么就需要引入补偿机制或者人工处理。
- Cancel阶段:主要是在业务执行错误,需要回滚的状态下执行的业务取消,预留资源释放。同样TCC中我们认为cancel阶段一定会执行成功,如果失败也需要引入重试或者人工处理。
当然上述操作要保证幂等性。
具体可参考另一篇博客:https://blog.csdn.net/zhengguofeng0328/article/details/124423358
5、本地消息表
流程图如下所示:
- 1.1 正常写入数据库
- 1.2 在写入数据库的同时,写入一张本地消息表。这张表,用来记录MQ消息处理的状态,可以有发送中和已完成两种状态。由于消息表和正常的业务表在一个DB中,所以可以达成本地事务,确保同时完成
- 2 写入消息表成功之后,可以异步发送MQ消息,且不用关心投递是否成功
- 3 后续业务订阅MQ消息。消费成功之后,将会把执行成功的状态,再通过MQ来发送。本地业务订阅这个执行状态,并把消息表中对应的记录状态,改为已完成;如果消费失败,则不做过多处理
- 4 存在一个定时任务,持续扫描本地消息表中,状态为发送中的消息(注意延时),并再次把这些消息发送到MQ,重复2的过程
当然这只保证最终一致性。
6、MQ事务方案(可靠消息事务)
与本地消息表类似,只不过把日志消息从数据库移到了MQ中,进行消费消息即可。
- A正常写入数据,以及将回滚消息与B参与者的事务消息发送到MQ中,由B消费。
- B消费后将success或fail消息发送到MQ中去,返回处理结果。
- A收到B反馈的消息若成功则结束,若失败A从MQ中获取回滚消息进行数据回滚。