赞
踩
事务想必大家并不陌生,严格意义上讲事务应该具备ACID,目前常见的分布式事务解决方案包括2PC、3PC、TCC、本地消息表、消息事物、最大努力通知等。
分布式事务就是要在分布式系统中实现事物,它其实就是多个本地事物的结合,对于分布式事务几乎无法满足ACID,其实对于单机事物而言大部分情况也无法满足ACID,不然数据库怎么会有四种隔离级别呢?所以在分布式的领域里也无法全部满足。
二阶段提交是一种强一致性设计,2PC引入了一个事物协调者的角色来协调管理各参与者的提交和回滚,二阶段分别指:准备、提交两个阶段,它是一种尽可能保证强一致性的分布式事务,所以它是同步阻塞的,总体而言效率低,而且存在协调者单点故障问题,极端情况下无法保证数据一致性
协调者会给各参与者发送准备命令,它主要是为了对事物进行提交。
同步等待所有资源的响应之后就进入了提交阶段,这里提交阶段并不是只是提交,也可能是表示回滚。
假如在第一阶段有一个参与者失败,那么协调者就会向所有参与者发送回滚请求,也就是分布式失败
那如果二阶段提交失败怎么办?(两种情况,其实都是进行重试操作)
首先2PC是一个同步阻塞协议,像第一阶段协调者会等待所有参与者响应才会进行下一步操作,当然第一阶段的协调者有超时机制,假设因为网络原因没有收到参与者的响应或某参与者挂了,那么超时后就会判断事物失败,向所有参与者发送回滚命令。
第二阶段协调者没法超时,因为部分参与者已经提交成功了,所有必须要不断重试。
如何解决以上问题呢?如果只是因为协调者的单点故障导致服务不可用的话,协调者故障可以通过选举得到新可用的协调者。
如果处于第一阶段,其实影响不大,在第一阶段的事务肯定没有提交,此时都按回滚处理就好了
如果处于第二阶段,此时分为两种情况
问题就出在每个参与者自身的状态只有自己和协调者知道,因此新协调者无法通过在场的参与者推断出挂了的参与者处于什么状态。
这种情况,需要引入记录来判断新的协调者来的时候要不要继续确认,例如如下的信息。
参与者ID | 阶段 | 状态 |
---|---|---|
xxxxx1 | prepare | success |
xxxxx2 | prepare | success |
xxxxx3 | commit | fail |
但是就算协调者知道自己该发提交请求,那么参与者在一起挂了也没有用,因为你不知道参与者在挂之前有没有提交事物操作。
所以说2PC极端情况下还是无法解决数据不一致的问题
三阶段提交出现是为了解决2PC的一些问题,相比于2PC它在参与者中引入了超时机制,并且新增了一个阶段 预提交阶段 使得参与者利用这一阶段统一各自的状态。
与2PC不相同,准备阶段不会直接提交事务,而是会先询问此时的参与者是否有条件接收事务,所以它不会上来锁住所有资源,使得某些资源不可用的情况下所有参与者都阻塞着。
预提交阶段的引入起到了统一状态的作用,它表明预提交阶段前所有参与者还未都回应,预提交阶段后,所有参与者都进行了回应,但是多一段交互也会导致性能的下降,大多数情况所有参与者都是可用状态,极端情况会出现问题,毕竟极端情况占比比较小。
同步等待所有资源的响应之后就进入了提交阶段,这里提交阶段并不是只是提交,也可能是表示回滚。
上面我们说了2PC是同步阻塞的,上面我们分析了协调者在发送事务(提交或回滚)命令之前挂了,其影响是最大的,所有参与者都在等待着,但是如果引入了超时机制,参与者就不会傻等了。超时机制只针对于参与者,针对协调者而言,超时的问题还是存在
3PC的引入是为了解决提交阶段2PC,协调者与参与者都挂了,新选举的协调者不知道当前应该是提交还是回滚的问题,但是这也只解决了部分问题,比如他无法确定参与者到底有没有执行事务,这个是无法确定的,所以3PC通过预提交阶段可以减少故障恢复时的复杂性,但是不能保证数据一致性了。
3PC针对2PC做了一些改进,主要是引入了参与者的超时机制,并新增了预提交阶段使得故障恢复时协调者决策更清晰一些,但是性能上不如2PC,2PC和3PC都会出现数据不一致的情况
2PC和3PC在数据库层面用的比较多,TCC则在业务分布式事务层面比较多。想要了解更多可以查看Mysql中2PC的应用
TCC指的是Try - Confirm - Cancel
其实从思想上来看和2PC差不多,都是先试探性的执行,如果都可以就真正的执行了,如果不行就回滚。
如果所示,调用者发起事务,首先发给事务管理器,然后再请求服务1、服务2、服务3,都返回预定成功后,在交给事务管理器进行事务提交或回滚。
TCC的整体流程还是很简单的,难点在于业务上的定义对于每一个操作你都要定义三个动作(Try-Confirm-Cancel),这对于业务的代码侵入非常大,需要根据特定的场景和业务逻辑来设计操作。
它相比于2PC、3PC使用范围更大,但是开发量也会大,不过因为它是在业务上实现的,所有TCC可以跨数据库、跨不同的业务系统来实现事务。
本地消息表其实就是利用各系统本地的事务来实现分布式事务
本地消息表就是有一张存放本地消息的表,一般都是与业务表放在同一个库中,利用数据库事务来操作业务表与消息表,这样就可以保证业务表执行成功的,消息表中肯定会有相应的数据信息,然后在采用定时任务或者MQ的方式做分布式事务补偿
比如拿订单服务与库存服务举例,用户下单后需要生成递单以及更新库存,如果这两个服务使用不同的库,也就是我们说的分布式事务
如果4过程成功或失败,会不会对整体数据产生影响呢?
可以看出本地消息表实现是采用数据库事务实现的,利用消息表与业务表在同库中,实现事务,后续再由MQ进行事务的补偿,如果补偿失败可以利用定时任务轮询消息表中未处理的消息继续进行处理,也可以增加重试次数,最后由人工再次处理。
本地消息表不同与2PC/3PC,它是数据最终一致性的,容忍了暂时数据不一致的情况。
RockerMQ就很好的支持了消息事务,让我们看看如何通过消息实现事务。
第一步先给MQ发送事务消息即半消息**(消息对于消费者来说还不可见),这里利用MQ的事务机制,目前RocketMQ/kafka等主流MQ都支持事务投递。**然后发送成功后在执行本地事务。
第二步根据本地事务执行的结果(成功、失败)向MQ发送Commit/RollBack请求。
第三步MQ的发送方会提供一个反查事务状态的接口,如果一段时间内半消息还没收到Commit/RollBack请求,那么MQ可以通过反查结果得知发送方事务是否执行成功,然后执行Commit/RollBack
其实整个过程需要回查的原因就是考虑请求4可能由于网络等原因发送失败,但是本地事务已经提交了,那MQ服务器上这条半消息就无法实现Commit/RollBack,因此,MQ服务端会定时扫描存储于事务为提交的消息,并发起回调查询该消息的事务状态。上图的5、6、7就是MQ服务端定时回查的步骤
可以看出消息事务主要是利用MQ的部分特性实现的(1.支持事务投递、2.未完成的事务回调检查(可以利用RockerMQ的特征,也可以基于MQ自己实现),通过消息发送方先执行本地事务后的结果再执行其他事务最后达到最终一致性,所以消息事务也是数据最终一致性的。
其实我觉得最大努力通知就是一种柔性的事务思想,它适用于对时间不敏感的业务,本地消息表以及消息事务都可以成为最大努力通知的一种方案
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。