文章目录
- 1.2PC
- 1.1 可能会存在哪些问题
- 2.三阶段提交(3PC)
- 3.补偿事务(TCC)
- 3.1 TCC解决了2PC的问题
- 4.本地消息表
- 5.消息事务
- 6.最大努力通知
- 7.Sagas 事务模型
1.2PC
两阶段提交
mysql是通过日志系统完成事务的。就是两阶段提交:undolog和binlog的两阶段提交。
两阶段协议可以用于单机集中式系统,由事务管理器协调多个资源管理器;也可以用于分布式系统,由一个全局的事务管理器协调各个子系统的局部事务管理器完成两阶段提交。
第一阶段:投票阶段
1.协调者写命令进写入日志
2.协调者发一个prepare命令,给B和C两个参与者
3.B和C接受到信息后,根据自己的情况,判断是否可以commit
4.将处理结果记录到日志系统
5.将结果返回给协调者
第二阶段:决定阶段
1.A收到B和C的消息后,批判所有协调者是否可以提交
2.如果可以写入日志并且commit,如果不可以写入日志发起a’bort命令
3.参与者收到协调者的命令,将执行命令写入日志,返回结果给协调者
1.1 可能会存在哪些问题
1.单点故障
一旦事务管理器出现问题,整个系统瘫痪
2.数据不一致
阶段2中,如果事务管理器只发送部分commit消息,此时网络异常,那么只有部分参与者得到commit消息,也就是说只有部分参与者提交了事务,数据不一致
3.响应事件长,阻塞
整个消息链路是串行的,要等待响应结果,不适合高并发的场景
4.不确定性
当事务管理器发送 commit 之后,并且此时只有一个参与者收到了 commit,那么当该参与者与事务管理器同时宕机之后,重新选举的事务管理器无法确定该条消息是否提交成功。
2.三阶段提交(3PC)
三阶段提交又称3PC,相对于2PC来说增加了CanCommit阶段和超时机制。如果段时间内没有收到协调者的commit请求,那么就会自动进行commit,解决了2PC单点故障的问题。
但是性能问题和不一致问题仍然没有根本解决。
第一阶段:「CanCommit阶段」这个阶段所做的事很简单,就是协调者询问事务参与者,你是否有能力完成此次事务。
如果都是yes的话,进入2阶段
如果一个no的话,中断事务,发送about请求
第二阶段:「PreCommit阶段」此时协调者会向所有的参与者发送PreCommit请求,参与者收到后开始执行事务操作,并将Undo和Redo信息记录到事务日志中。参与者执行完事务操作后(此时属于未提交事务的状态),就会向协调者反馈“Ack”表示我已经准备好提交了,并等待协调者的下一步指令。
第三阶段:「DoCommit阶段」在阶段二中如果所有的参与者节点都可以进行PreCommit提交,那么协调者就会从“预提交状态”转变为“提交状态”。然后向所有的参与者节点发送"doCommit"请求,参与者节点在收到提交请求后就会各自执行事务提交操作,并向协调者节点反馈“Ack”消息,协调者收到所有参与者的Ack消息后完成事务。相反,如果有一个参与者节点未完成PreCommit的反馈或者反馈超时,那么协调者都会向所有的参与者节点发送abort请求,从而中断事务。
3.补偿事务(TCC)
TCC其实就是采用的补偿机制,其核心思想是:「针对每个操作,都要注册一个与其对应的确认和补偿(撤销)操作」。它分为三个阶段:
Try,Confirm,Cancel
- Try阶段主要是对「业务系统做检测及资源预留」.
- Confirm 阶段主要是对「业务系统做确认提交」,Try阶段执行成功并开始执行 Confirm阶段时,默认 Confirm阶段是不会出错的。即:只要Try成功,Confirm一定成功。
- Cancel 阶段主要是在业务执行错误,需要回滚的状态下执行的业务取消,「预留资源释放」。
1.Try阶段:订单系统将当前订单状态设置为支付中,库存系统校验当前剩余库存数量是否大于1,然后将可用库存数量设置为库存剩余数量-1
2.成功:confirm,订单状态改为成功,库存剩余量为可用库存数量
3.cancel:失败状态,可用库存修改为库存剩余量
3.1 TCC解决了2PC的问题
1.单点故障问题:由主业务方发起并完成这个业务活动。业务活动管理器也变成多点,引入集群。
2.同步阻塞:引入超时机制,超时后进行补偿,并且不会锁定全部资源,资源转化为业务逻辑形式,粒度变小
3.数据一致性问题:有了补偿机制之后,由业务活动管理器控制一致性
总之,TCC 就是通过代码人为实现了两阶段提交,不同的业务场景所写的代码都不一样,并且很大程度的「增加」了业务代码的「复杂度」,因此,这种模式并不能很好地被复用。
4.本地消息表
- 消息生产方,需要额外建一个消息表,并「记录消息发送状态」。消息表和业务数据要在一个事务里提交,也就是说他们要在一个数据库里面。然后消息会经过MQ发送到消息的消费方。
- 如果消息发送失败,会进行重试发送。
- 消息消费方,需要「处理」这个「消息」,并完成自己的业务逻辑。
- 如果是「业务上面的失败」,可以给生产方「发送一个业务补偿消息」,通知生产方进行回滚等操作。
- 此时如果本地事务处理成功,表明已经处理成功了
- 如果处理失败,那么就会重试执行。
- 生产方和消费方定时扫描本地消息表,把还没处理完成的消息或者失败的消息再发送一遍。
5.消息事务
消息事务的原理是将两个事务「通过消息中间件进行异步解耦」,和上述的本地消息表有点类似,但是是通过消息中间件的机制去做的,其本质就是’将本地消息表封装到了消息中间件中’。
- 发送prepare消息到消息中间件
- 发送成功后,执行本地事务
如果事务执行成功,则commit,消息中间件将消息下发至消费端
如果事务执行失败,则回滚,消息中间件将这条prepare消息删除 - 消费端接收到消息进行消费,如果消费失败,则不断重试
这种方案也是实现了「最终一致性」,对比本地消息表实现方案,不需要再建消息表,「不再依赖本地数据库事务」了,所以这种方案更适用于高并发的场景。目前市面上实现该方案的「只有阿里的 RocketMQ」。
6.最大努力通知
最大努力通知的方案实现比较简单,适用于一些最终一致性要求较低的业务。
- 系统 A 本地事务执行完之后,发送个消息到 MQ;
- 这里会有个专门消费 MQ 的服务,这个服务会消费 MQ 并调用系统 B 的接口;
- 要是系统 B 执行成功就 ok 了;要是系统 B 执行失败了,那么最大努力通知服务就定时尝试重新调用系统 B, 反复 N 次,最后还是不行就放弃。
7.Sagas 事务模型
Saga事务模型又叫做长时间运行的事务
其核心思想是「将长事务拆分为多个本地短事务」,由Saga事务协调器协调,如果正常结束那就正常完成,如果「某个步骤失败,则根据相反顺序一次调用补偿操作」。
TC, TM, RM
「Transaction Coordinator (TC)」:事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚。「Transaction Manager ™」:控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议。「Resource Manager (RM)」:控制分支事务,负责分支注册、状态汇报,并接收事务协调器的指令,驱动分支(本地)事务的提交和回滚。
seata框架「为每一个RM维护了一张UNDO_LOG表」,其中保存了每一次本地事务的回滚数据。
1.1.首先TM 向 TC 申请「开启一个全局事务」,全局事务「创建」成功并生成一个「全局唯一的 XID」。
2.XID 在微服务调用链路的上下文中传播。
3.RM 开始执行这个分支事务,RM首先解析这条SQL语句,「生成对应的UNDO_LOG记录」。下面是一条UNDO_LOG中的记录,UNDO_LOG表中记录了分支ID,全局事务ID,以及事务执行的redo和undo数据以供二阶段恢复。
4.RM在同一个本地事务中「执行业务SQL和UNDO_LOG数据的插入」。在提交这个本地事务前,RM会向TC「申请关于这条记录的全局锁」。
如果申请不到,则说明有其他事务也在对这条记录进行操作,因此它会在一段时间内重试,重试失败则回滚本地事务,并向TC汇报本地事务执行失败。
6.RM在事务提交前,「申请到了相关记录的全局锁」,然后直接提交本地事务,并向TC「汇报本地事务执行成功」。此时全局锁并没有释放,全局锁的释放取决于二阶段是提交命令还是回滚命令。
7.TC根据所有的分支事务执行结果,向RM「下发提交或回滚」命令。
- RM如果「收到TC的提交命令」,首先「立即释放」相关记录的全局「锁」,然后把提交请求放入一个异步任务的队列中,马上返回提交成功的结果给 TC。异步队列中的提交请求真正执行时,只是删除相应 UNDO LOG 记录而已。
- RM如果「收到TC的回滚命令」,则会开启一个本地事务,通过 XID 和 Branch ID 查找到相应的 UNDO LOG 记录。将 UNDO LOG 中的后镜与当前数据进行比较,
- 如果不同,说明数据被当前全局事务之外的动作做了修改。这种情况,需要根据配置策略来做处理。
- 如果相同,根据 UNDO LOG 中的前镜像和业务 SQL 的相关信息生成并执行回滚的语句并执行,然后提交本地事务达到回滚的目的,最后释放相关记录的全局锁。