目录
刚逛超市回来,路上路过一家麻辣烫,吃的时候无聊,就刷手机看,看到群里有人问分布式事务的处理方案,于是,回来之后,我也总结一下(因为单身狗,所以一个人吃,所以才无聊,呜呜呜)
什么是分布式事务
ok,首先,我们要知道什么是分布式事务,事务我们都知道(不知道事务的去,自己去补习去)
分布式事务,也就很简单,意味着分布式的事务
分布式事务,指的就是参与事务的服务器、资源等,分别位于不同的节点上,一个操作由这些参与者共同完成,这些操作者的各自小操作,要么都成功,要么,都失败
为什么有分布式事务
我们知道了什么是分布式事务,那,为什么要有分布式事务呢?
为什么要有事务,我们都知道,已经烂大街的例子,转账,这边扣了那边得加
而在分布式环境下,也有相同的问题,线上为了保证稳定与高可用,我们不可能是单体架构的
所以,一定要有分布式事务这个东西
CAP与BASE理论
CAP理论
一致性C-Consistency,可用性A-Availability,分区容错性P-Partition tolerance
一致性C:在分布式系统中的所有数据备份,在同一个时刻,是否有相同的值?
可用性A:在分布式系统中,集群内某些节点发生故障后,集群对外整体是否还能正常响应用户请求?
分区容错性P:就算出现了单个组件的无法使用,操作能否完成?
在系统中,我们说CAP最多只能满足两个,很明显的,任何扩展都需要依赖于数据分区,因此,我们必须在一致性和可用性舍弃一个
我们在实际开发中,一般都会采用可用性而放弃一致性
当然,所谓的放弃一致性,并非不管数据的正确性,而是放弃强一致性,得到弱一致性
一致性
一致性分为三种,强一致性、弱一致性、最终一致性
- 强一致性:系统中某个数据被更新后,后续任何对该数据的读取操作,都将得到的是更新后的值,也可以称做原子一致性,说简单点,在任何时刻,所有节点的数据都是一样的;而如果一个集群要提供强一致性,只要一个节点数据发生变化,其他节点必须都同步完成才可以之后,整个集群才可以继续对外提供服务,这样一定会损耗可用性
- 弱一致性:系统中某个数据被更新后,后续任何对该数据的读取操作,可能拿到更新后的值,也可能拿到更新前的值
- 最终一致性:最终一致性不保证任何时刻拿到的同一份数据是相同的,但是随着时间的推移,不同节点的数据一定是朝着相同的方向变化,最终一段时间后,达到一致状态(目前我们的系统就采用的最终一致性)
BASE理论
BASE理论是对CAP的可用性和一致性的一种平衡,BASE理论的核心观点就是:无法做到强一致性,但,可以根据不同节点的特点,使用适当的方式让系统达到最终一致性
BA-Basic Available 基本可用、S-Soft State 柔性状态、E-Eventual Consisstency 最终一致性
- BA:基本可用;在某些情况下,系统仍旧能够返回一个明确的结果,就属于基本可用
- S:柔性状态;允许系统的数据存在中间状态,该中间状态的存在,不会影响系统的整体可用性
- E:最终一致性:同一个数据的不同副本,可以不用实时一致,但,最终一定要是一致的
分布式事务解决方案
接下来我们来看分布式事务的解决方案
在现在的分布式架构中,每个节点都只会知道自己的操作结果,而不知道别人的操作结果,所以,为了保持事务的原子性与一致性,需要引入一个协调者来掌控所有的操作结果,来决定到底是继续,还是回退
二阶段提交 2PC
二阶段提交,就是将事务的提交过程分为两个阶段来处理,这两个阶段分别是准备阶段和提交阶段,而参与到的角色,就是事务的协调者与事务的参与者,所谓事务协调者,一般都是事务的发起者,而事务的参与者,就是事务的执行者,当然,事务的参与者本身也可以是事务的执行者
第一阶段:准备阶段
准备阶段,由事务的协调者来发起询问,或者说查询,来确认所有的参与者是否能够提交事务,注意,所有的参与者只是确认,并非提交
第二阶段:提交执行
提交执行阶段,这一段就是协调者通知所有参与者提交事务,当所有的参与者都回复了成功,意味着这个事务就完成了
如果任何一个参与者在第一阶段认为不可以提交事务,或者说,协调者去协调能否提交,在规定时间内没有得到响应,这个事务也会被回退
无论结果如何,在第二阶段,这个事务都会结束
二阶段提交的优点
二阶段提交的优点已经算很明显了,尽可能保证了数据的强一致性(当然,不是100%)
二阶段提交的缺点
有经验的人一下就能看到二阶段提交的缺点
- 性能问题:在执行的过程中,所有参与了事务的节点,都是阻塞类型的,当参与者占用了公共资源,其他访问公共资源的,不得不等待或阻塞
- 可靠性问题:为了防止参与者出问题,协调者需要给每一个参与者指定超时时间,超时后认为失败,而协调者如果发生故障,参与者就会一直阻塞下去,需要额外的容错手段
- 数据一致性问题:假设协调者在发出提交指令之后挂了,而接收这条指令的参与者也挂了,即使通过类似选举的手段选出了新的协调者,谁也不知道这个事务是不是被更改过
- 实现复杂:直接牺牲了可用性,对性能影响巨大,高并发场景不适合用,且实现也复杂
三阶段提交 3PC
三阶段提交是二阶段提交的改良版,对比于二阶段提交,三阶段提交有两个地方变更
- 在协调者、参与者,都设置超时机制
- 在一阶段和二阶段中间额外插入一个准备阶段,保证在最后提交之前各个参与节点的状态是一样的
也就是说,三阶段提交有三个阶段,确认可提交---准备提交---提交
阶段一
三阶段提交的阶段一和二阶段提交的阶段一是一样的
阶段二
将操作都写入事务,但是不提交
假设参与者超时,或者参与者告知无法提交,·协调者就执行事务中断
阶段三
进行最后的提交,在这一个阶段,就算参与者无法收到协调者的消息,参与者都会在超时之后,继续执行事务
三阶段提交的优点
对比二阶段提交,三阶段提交降低了阻塞范围,等待超时后,参与者或者协调者就会中断事务,而阶段三中,就算协调者出现问题,各个参与者也会正常执行事务
三阶段提交的缺点
三阶段提交,很明显也依然存在着数据不一致的问题,如果参与者收到了准备提交指令,等待提交指令,而这个时候协调者要求中断事务,协调者无法通知到参与者,参与者会自己提交事务,就会导致数据不一致
事务补偿 TCC
所谓补偿事务,是一种侵入业务的两阶段提交方式,应该是现在用的比较多的,我们的系统有一部分是这样的,想法就是,针对每个操作,都实现一个对应的确认和错误补偿操作
T,代表尝试,try
C,代表取消,cancel
C,代表确认,confirm
以下订单举例
锁定库存,尝试创建订单,这就是try
根据try阶段的执行结果,确认执行哪个C,两个C具有幂等性,如果执行失败,那就不断重试,直到成功
如果成功,那就确认,如果失败,那就取消
TCC保证的是最终一致性,以try的操作为核心,其他操作为辅助
当然,TCC这种,也有概率在两个C失败,必须由人工处理的情况
TCC的优点
TCC对比上面,有一些比较明显的优点
- 性能:在性能上,对资源的锁粒度变小了,不会锁定所有资源
- 一致性:因为两个C操作,保证数据的最终一致性
- 可靠性:可以解决协调者单点故障的问题,由主要发起方控制所有活动
TCC的缺点
TCC的缺点也十分明显,它要侵入业务,根据具体的业务来实现对应操作,开发成本会上去
本地消息表
本地消息表的核心,就是把分布式事务拆分成不同的本地事务
参加的角色分为事务主动方和事务被动方,事务主动方在自己的本地额外建立一个消息表,并且记录消息的发送状态和执行状态,这张消息表和自己业务数据的变更,在一个事务提交,然后消息通过MQ发送给事务被动方,发送失败,那就重试
而事务被动方,需要处理自己接收到的消息,并依照消息,完成自己应该执行的逻辑,如果业务失败,可以给事务主动方发送一个补偿消息,通知事务主动方回退等
如果处理成功了,发送给事务主动方,事务主动方更新消息表的状态,并执行其他操作
当然,需要做一些可能出现的容错处理
- 如果事务主动方出错,因为在本地,可以直接回退
- 如果发送消息等出错,直接重试发送就好,直到发送成功,对方接收到
- 如果业务处理失败,可以发消息,进行对应回退操作
- 如果事务被动方已经消费了消息,事务主动方要回滚,需要发消息等操作,来进行回滚
- 主动方和被动方定期扫描自己的消息表,失败的进行对应处理
本地消息表的优点
消息数据的可靠性存在DB,对MQ的依赖比较少
(这儿有人说实现简单,不过我个人觉得,不是特别合适的说法)
本地消息表的缺点
缺点也很明显,与具体业务绑定耦合,并且消息表也需要占用DB的资源,也会受限DB的资源
MQ
所谓MQ,不是指的像普通使用MQ一样, 而是将本地消息表放在了MQ里,目前只有rocketmq支持
流程其实也和本地消息表有点类似,但并不是很相同
首先发送准备消息到MQ,发送成功后,执行本地事务,如果执行成功,那就提交,MQ会把消息下发到消费端,如果执行失败,那就回退,MQ将消息删除,消费端如果接受失败,那就一直重试
优点
数据独立存储,不占用DB的资源,也降低了业务与消息之间的耦合性
吞吐量大于本地消息表
缺点
一次消息发送,需要两次网络请求(消息,提交或回退消息)
需要实现对应的消息处理接口
最大程度通知
如同字面意思,最大程度上努力通知
事务发起主动方进行推送,并且,接收方也可以调用发起方,进行数据的核对,也就是说,双方可以互相调用
以上,是我了解的分布式事务解决方案,当然,实际上不止这些,不过,对于我听都没听过,或者完全不懂的,我也不乱整理乱分享
总之,分布式事务会提高整体系统的复杂度,并且一定程度上会影响性能,所以,在实际开发中,如果能避免使用分布式事务,还是尽量不要去用