分布式事务解决方案
2022-05-13 14:42:14 35 举报
AI智能生成
分布式事务解决方案是一种处理跨多个数据库或服务器的事务的技术。它确保了在分布式环境中,所有的操作要么全部成功,要么全部失败,从而保证了数据的一致性和完整性。这种解决方案通常包括两阶段提交协议(2PC)、三阶段提交协议(3PC)和补偿事务等技术。其中,2PC通过协调者和参与者之间的交互来保证事务的原子性,但可能会导致阻塞问题;3PC则通过引入超时机制来解决2PC的阻塞问题,但实现更复杂;补偿事务则是通过在事务失败后进行反向操作来恢复数据一致性,但可能会增加系统的复杂性和延迟。
作者其他创作
大纲/内容
出现的场景?
跨库事务
分库分表
微服务化
CAP定理:分布式系统设计理论
名词解释
分区容错性(Partition tolerance):分布式系统在遇到任何网络分区故障时,仍然需要能够保证对外提供满足一致性和可用性的服务
可用性(Availability):系统提供的服务必须一直处于可用的状态,对于用户的每一个操作请求总是能够在有限的时间内返回结果
有限时间内
返回正常结果
一致性(Consistency):数据更新操作成功并返回客户端完成后,所有节点在同一时间的数据完全一致,不能存在中间状态
强一致性
弱一致性
最终一致性
为什么一致性(C)和可用性(A)之间进行取舍?
如果保证了一致性(C):对于节点N1和N2,当往N1里写数据时,N2上的操作必须被暂停,只有当N1同步数据到N2时才能对N2进行读写请求,在N2被暂停操作期间客户端提交的请求会收到失败或超时。显然,这与可用性是相悖的
如果保证了可用性(A):那就不能暂停N2的读写操作,但同时N1在写数据的话,这就违背了一致性的要求。
BASE定理:CAP理论中AP方案的延伸,对于C我们采用的方式和策略就是保证最终一致性
名词解释
Basically Available(基本可用):分布式系统在出现不可预知的故障的时候,允许损失部分可用性,但不等于系统不可用
响应时间上的损失
功能上的损失(服务降级)
Soft state(软状态):指允许系统中的数据存在中间状态,并认为该中间状态的存在不会影响系统的整体可用性
Eventually consistent(最终一致性):强调系统中所有的数据副本,在经过一段时间的同步后,最终能够达到一个一致的状态
ACID 和 BASE 的区别与联系
一致性
ACID 是传统数据库常用的设计理念,追求强一致性模型
BASE 支持的是大型分布式系统,提出通过牺牲强一致性获得高可用性
ACID 和 BASE 代表了两种截然相反的设计哲学,在分布式系统设计的场景中,系统组件对一致性要求是不同的,因此 ACID 和 BASE 又会结合使用
分布式事务分类?
本地事务
ACID实现
分布式事务(CAP)
AP架构
柔性事务:满足BASE理论
应用场景
不要求强一致性,而是要求最终一致性,允许有中间状态,也就是Base理论,换句话说,就是AP状态
解决方案
补偿型(同步)
概念:补偿模式使用一个额外的协调服务来协调各个需要保证一致性的业务服务,协调服务按顺序调用各个业务微服务,如果某个业务服务调用异常(包括业务异常和技术异常)就取消之前所有已经调用成功的业务服务
实现
TCC
核心组成部分
主业务服务:主业务服务为整个业务活动的发起方,服务的编排者,负责发起并完成整个业务活动
从业务服务:从业务服务是整个业务活动的参与方,负责提供 TCC 业务操作,实现初步操作(Try)、确认操作(Confirm)、取消操作(Cancel)三个接口,供主业务服务调用
业务活动管理器:业务活动管理器管理控制整个业务活动,包括记录维护 TCC 全局事务的事务状态和每个从业务服务的子事务状态,并在业务活动提交时调用所有从业务服务的 Confirm 操作,在业务活动取消时调用所有从业务服务的 Cancel 操作
流程
Try 阶段: 调用 Try 接口,尝试执行业务,完成所有业务检查,预留业务资源。
Confirm 或 Cancel 阶段: 两者是互斥的,只能进入其中一个,并且都满足幂等性,允许失败重试。
Confirm 操作: 对业务系统做确认提交,确认执行业务操作,不做其他业务检查,只使用 Try 阶段预留的业务资源。
Cancel 操作: 在业务执行错误,需要回滚的状态下执行业务取消,释放预留资源
Confirm 或 Cancel 阶段: 两者是互斥的,只能进入其中一个,并且都满足幂等性,允许失败重试。
Confirm 操作: 对业务系统做确认提交,确认执行业务操作,不做其他业务检查,只使用 Try 阶段预留的业务资源。
Cancel 操作: 在业务执行错误,需要回滚的状态下执行业务取消,释放预留资源
Try 阶段失败可以 Cancel,如果 Confirm 和 Cancel 阶段失败了怎么办?
TCC 中会添加事务日志,如果 Confirm 或者 Cancel 阶段出错,则会进行重试,所以这两个阶段需要支持幂等;如果重试失败,则需要人工介入进行恢复和处理等
TCC与2PC对比
相似之处
阶段1:
1、在XA中,各个RM准备提交各自的事务分支,事实上就是准备提交资源的更新操作(insert、delete、update等);
2、在TCC中,是主业务活动请求(try)各个从业务服务预留资源。
在阶段2:
1、XA根据第一阶段每个RM是否都prepare成功,判断是要提交还是回滚。如果都prepare成功,那么就commit每个事务分支,反之则rollback每个事务分支。
2、TCC中,如果在第一阶段所有业务资源都预留成功,那么confirm各个从业务服务,否则取消(cancel)所有从业务服务的资源预留请求。
1、在XA中,各个RM准备提交各自的事务分支,事实上就是准备提交资源的更新操作(insert、delete、update等);
2、在TCC中,是主业务活动请求(try)各个从业务服务预留资源。
在阶段2:
1、XA根据第一阶段每个RM是否都prepare成功,判断是要提交还是回滚。如果都prepare成功,那么就commit每个事务分支,反之则rollback每个事务分支。
2、TCC中,如果在第一阶段所有业务资源都预留成功,那么confirm各个从业务服务,否则取消(cancel)所有从业务服务的资源预留请求。
不同之处
1、XA是资源层面的分布式事务,强一致性,在两阶段提交的整个过程中,一直会持有资源的锁。基于数据库锁实现,需要数据库支持XA协议,由于在执行事务的全程都需要对相关数据加锁,一般高并发性能会比较差
2、TCC是业务层面的分布式事务,最终一致性,不会一直持有资源的锁,性能较好。但是对微服务的侵入性强,微服务的每个事务都必须实现try、confirm、cancel等3个方法,开发成本高,今后维护改造的成本也高为了达到事务的一致性要求,try、confirm、cancel接口必须实现幂等性操作由于事务管理器要记录事务日志,必定会损耗一定的性能,并使得整个TCC事务时间拉长
概念:TCC(Try-Confirm-Cancel)分布式事务模型相对于 XA 等传统模型,其特征在于它不依赖资源管理器(RM)对分布式事务的支持,而是通过对业务逻辑的分解来实现分布式事务
Saga
概念:Saga模型是把一个分布式事务拆分为多个本地事务,每个本地事务都有相应的执行模块和补偿模块(对应TCC中的Confirm和Cancel),当Saga事务中任意一个本地事务出错时,可以通过调用相关的补偿方法恢复之前的事务,达到事务最终一致性。SAGA可以看做一个异步的、利用队列实现的补偿事务
核心组成部分
LLT(Long Live Transaction):由一个个本地事务组成的事务链
本地事务:事务链由一个个子事务(本地事务)组成,LLT = T1+T2+T3+...+Ti
补偿:每个本地事务 Ti 有对应的补偿 Ci
Saga 两种恢复策略
向后恢复(Backward Recovery):撤销掉之前所有成功子事务。如果任意本地子事务失败,则补偿已完成的事务
向前恢复(Forward Recovery):即重试失败的事务,适用于必须要成功的场景,该情况下不需要Ci
异步确保型
MQ事务消息方案(RocketMQ,ActiveMQ支持半消息)
原理
依靠MQ的半消息机制来实现投递消息和参与者自身本地事务的一致性保障
流程
1、事务发起方首先发送半消息到MQ;
2、MQ通知发送方消息发送成功;
3、在发送半消息成功后执行本地事务;
4、根据本地事务执行结果返回commit或者是rollback;
5、如果消息是rollback, MQ将丢弃该消息不投递;如果是commit,MQ将会消息发送给消息订阅方;
6、订阅方根据消息执行本地事务;
7、订阅方执行本地事务成功后再从MQ中将该消息标记为已消费;
8、如果执行本地事务过程中,执行端挂掉,或者超时,MQ服务器端将不停的询问producer来获取事务状态;
9、Consumer端的消费成功机制有MQ保证;
2、MQ通知发送方消息发送成功;
3、在发送半消息成功后执行本地事务;
4、根据本地事务执行结果返回commit或者是rollback;
5、如果消息是rollback, MQ将丢弃该消息不投递;如果是commit,MQ将会消息发送给消息订阅方;
6、订阅方根据消息执行本地事务;
7、订阅方执行本地事务成功后再从MQ中将该消息标记为已消费;
8、如果执行本地事务过程中,执行端挂掉,或者超时,MQ服务器端将不停的询问producer来获取事务状态;
9、Consumer端的消费成功机制有MQ保证;
设计思想
1.producer(本例中指A系统)发送半消息到broker,这个半消息不是说消息内容不完整, 它包含完整的消息内容, 在producer端和普通消息的发送逻辑一致
2.broker存储半消息,半消息存储逻辑与普通消息一致,只是属性有所不同,topic是固定的RMQ_SYS_TRANS_HALF_TOPIC,queueId也是固定为0,这个tiopic中的消息对消费者是不可见的,所以里面的消息永远不会被消费。这就保证了在半消息提交成功之前,消费者是消费不到这个半消息的
3.broker端半消息存储成功并返回后,A系统执行本地事务,并根据本地事务的执行结果来决定半消息的提交状态为提交或者回滚
4.A系统发送结束半消息的请求,并带上提交状态(提交 or 回滚)
5.broker端收到请求后,首先从RMQ_SYS_TRANS_HALF_TOPIC的queue中查出该消息,设置为完成状态。如果消息状态为提交,则把半消息从RMQ_SYS_TRANS_HALF_TOPIC队列中复制到这个消息原始topic的queue中去(之后这条消息就能被正常消费了);如果消息状态为回滚,则什么也不做。
6.producer发送的半消息结束请求是 oneway 的,也就是发送后就不管了,只靠这个是无法保证半消息一定被提交的,rocketMq提供了一个兜底方案,这个方案叫消息反查机制,Broker启动时,会启动一个TransactionalMessageCheckService 任务,该任务会定时从半消息队列中读出所有超时未完成的半消息,针对每条未完成的消息,Broker会给对应的Producer发送一个消息反查请求,根据反查结果来决定这个半消息是需要提交还是回滚,或者后面继续来反查
7.consumer(本例中指B系统)消费消息,执行本地数据变更(至于B是否能消费成功,消费失败是否重试,这属于正常消息消费需要考虑的问题)
注意:在rocketMq中,不论是producer收到broker存储半消息成功返回后执行本地事务,还是broker向producer反查消息状态,都是通过回调机制完成
2.broker存储半消息,半消息存储逻辑与普通消息一致,只是属性有所不同,topic是固定的RMQ_SYS_TRANS_HALF_TOPIC,queueId也是固定为0,这个tiopic中的消息对消费者是不可见的,所以里面的消息永远不会被消费。这就保证了在半消息提交成功之前,消费者是消费不到这个半消息的
3.broker端半消息存储成功并返回后,A系统执行本地事务,并根据本地事务的执行结果来决定半消息的提交状态为提交或者回滚
4.A系统发送结束半消息的请求,并带上提交状态(提交 or 回滚)
5.broker端收到请求后,首先从RMQ_SYS_TRANS_HALF_TOPIC的queue中查出该消息,设置为完成状态。如果消息状态为提交,则把半消息从RMQ_SYS_TRANS_HALF_TOPIC队列中复制到这个消息原始topic的queue中去(之后这条消息就能被正常消费了);如果消息状态为回滚,则什么也不做。
6.producer发送的半消息结束请求是 oneway 的,也就是发送后就不管了,只靠这个是无法保证半消息一定被提交的,rocketMq提供了一个兜底方案,这个方案叫消息反查机制,Broker启动时,会启动一个TransactionalMessageCheckService 任务,该任务会定时从半消息队列中读出所有超时未完成的半消息,针对每条未完成的消息,Broker会给对应的Producer发送一个消息反查请求,根据反查结果来决定这个半消息是需要提交还是回滚,或者后面继续来反查
7.consumer(本例中指B系统)消费消息,执行本地数据变更(至于B是否能消费成功,消费失败是否重试,这属于正常消息消费需要考虑的问题)
注意:在rocketMq中,不论是producer收到broker存储半消息成功返回后执行本地事务,还是broker向producer反查消息状态,都是通过回调机制完成
本地消息表方案
原理
将分布式事务拆分成本地事务进行处理
流程
发送消息方:
1、需要有一个消息表,记录着消息状态相关信息。
2、业务数据和消息表在同一个数据库,要保证它俩在同一个本地事务。直接利用本地事务,将业务数据和事务消息直接写入数据库。
3、在本地事务中处理完业务数据和写消息表操作后,通过写消息到 MQ 消息队列。使用专门的投递工作线程进行事务消息投递到MQ,根据投递ACK去删除事务消息表记录
4、消息会发到消息消费方,如果发送失败,即进行重试。
消息消费方:
1、处理消息队列中的消息,完成自己的业务逻辑。
2、如果本地事务处理成功,则表明已经处理成功了。
3、如果本地事务处理失败,那么就会重试执行。
4、如果是业务层面的失败,给消息生产方发送一个业务补偿消息,通知进行回滚等操作。
注意:生产方和消费方定时扫描本地消息表,把还没处理完成的消息或者失败的消息再发送一遍。如果有靠谱的自动对账补账逻辑,这种方案还是非常实用的
1、需要有一个消息表,记录着消息状态相关信息。
2、业务数据和消息表在同一个数据库,要保证它俩在同一个本地事务。直接利用本地事务,将业务数据和事务消息直接写入数据库。
3、在本地事务中处理完业务数据和写消息表操作后,通过写消息到 MQ 消息队列。使用专门的投递工作线程进行事务消息投递到MQ,根据投递ACK去删除事务消息表记录
4、消息会发到消息消费方,如果发送失败,即进行重试。
消息消费方:
1、处理消息队列中的消息,完成自己的业务逻辑。
2、如果本地事务处理成功,则表明已经处理成功了。
3、如果本地事务处理失败,那么就会重试执行。
4、如果是业务层面的失败,给消息生产方发送一个业务补偿消息,通知进行回滚等操作。
注意:生产方和消费方定时扫描本地消息表,把还没处理完成的消息或者失败的消息再发送一遍。如果有靠谱的自动对账补账逻辑,这种方案还是非常实用的
最大努力通知型
基于MQ自身的事务消息方案
原理
要实现最大努力通知,可以采用 MQ 的 ACK 机制
流程
1、业务活动的主动方,在完成业务处理之后,向业务活动的被动方发送消息,允许消息丢失。
2、主动方可以设置时间阶梯型通知规则,在通知失败后按规则重复通知,直到通知N次后不再通知。
3、主动方提供校对查询接口给被动方按需校对查询,用于恢复丢失的业务消息。
4、业务活动的被动方如果正常接收了数据,就正常返回响应,并结束事务。
5、如果被动方没有正常接收,根据定时策略,向业务活动主动方查询,恢复丢失的业务消息
2、主动方可以设置时间阶梯型通知规则,在通知失败后按规则重复通知,直到通知N次后不再通知。
3、主动方提供校对查询接口给被动方按需校对查询,用于恢复丢失的业务消息。
4、业务活动的被动方如果正常接收了数据,就正常返回响应,并结束事务。
5、如果被动方没有正常接收,根据定时策略,向业务活动主动方查询,恢复丢失的业务消息
基于DB的本地事务消息表方案
原理
要实现最大努力通知,可以采用 定期检查本地消息表的机制
流程
发送消息方:
1、需要有一个消息表,记录着消息状态相关信息。
2、业务数据和消息表在同一个数据库,要保证它俩在同一个本地事务。直接利用本地事务,将业务数据和事务消息直接写入数据库。
3、在本地事务中处理完业务数据和写消息表操作后,通过写消息到 MQ 消息队列。使用专门的投递工作线程进行事务消息投递到MQ,根据投递ACK去删除事务消息表记录
4、消息会发到消息消费方,如果发送失败,即进行重试。
5、生产方定时扫描本地消息表,把还没处理完成的消息或者失败的消息再发送一遍。如果有靠谱的自动对账补账逻辑,这种方案还是非常实用的。
最大努力通知事务在于第三方系统的对接,所以最大努力通知事务有几个特性:
1、业务主动方在完成业务处理后,向业务被动方(第三方系统)发送通知消息,允许存在消息丢失。
2、业务主动方提供递增多挡位时间间隔(5min、10min、30min、1h、24h),用于失败重试调用业务被动方的接口;在通知N次之后就不再通知,报警+记日志+人工介入。
3、业务被动方提供幂等的服务接口,防止通知重复消费。
4、业务主动方需要有定期校验机制,对业务数据进行兜底;防止业务被动方无法履行责任时进行业务回滚,确保数据最终一致性。
1、需要有一个消息表,记录着消息状态相关信息。
2、业务数据和消息表在同一个数据库,要保证它俩在同一个本地事务。直接利用本地事务,将业务数据和事务消息直接写入数据库。
3、在本地事务中处理完业务数据和写消息表操作后,通过写消息到 MQ 消息队列。使用专门的投递工作线程进行事务消息投递到MQ,根据投递ACK去删除事务消息表记录
4、消息会发到消息消费方,如果发送失败,即进行重试。
5、生产方定时扫描本地消息表,把还没处理完成的消息或者失败的消息再发送一遍。如果有靠谱的自动对账补账逻辑,这种方案还是非常实用的。
最大努力通知事务在于第三方系统的对接,所以最大努力通知事务有几个特性:
1、业务主动方在完成业务处理后,向业务被动方(第三方系统)发送通知消息,允许存在消息丢失。
2、业务主动方提供递增多挡位时间间隔(5min、10min、30min、1h、24h),用于失败重试调用业务被动方的接口;在通知N次之后就不再通知,报警+记日志+人工介入。
3、业务被动方提供幂等的服务接口,防止通知重复消费。
4、业务主动方需要有定期校验机制,对业务数据进行兜底;防止业务被动方无法履行责任时进行业务回滚,确保数据最终一致性。
整体解决方案对比
CP架构
刚性事务:满足CAP的CP理论
应用场景
无业务改造,强一致性,原生支持回滚/隔离性,低并发,适合短事务
解决方案(XA 协议:XA协议是数据库层面的一套分布式事务管理的规范)
实现方式
2PC/3PC
组成成员
参与者角色(资源管理器Participant):接收协调者的指令执行事务操作,向协调者反馈操作结果,并继续执行协调者发送的最终指令
协调者角色(事务管理器Coordinator):负责向参与者发送指令,收集参与者反馈,做出提交或者回滚决策
Seata AT 模式(增强型2pc模式)
优点
没有一直锁表
执行步骤
一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源
二阶段:提交异步化,非常快速地完成。或回滚通过一阶段的回滚日志进行反向补偿
流程
1、TM 请求 TC,开始一个新的全局事务,TC 会为这个全局事务生成一个 XID
2、XID 通过微服务的调用链传递到其他微服务
3、RM 把本地事务作为这个XID的分支事务注册到TC
4、TM 请求 TC 对这个 XID 进行提交或回滚
5、TC 指挥这个 XID 下面的所有分支事务进行提交、回滚
springboot整合参考资料
springboot集成:https://cloud.tencent.com/developer/article/1923686
踩坑解决:https://blog.csdn.net/RookiexiaoMu_a/article/details/119426595
不生效排查?https://blog.csdn.net/godkzz/article/details/122398887
LCN(采用2PC)
集成springboot:https://dandelioncloud.cn/article/details/1469353539419086849/
主要组成
TC(Tx-Client):由业务系统集成,事务发起方、参与方都由TxClient端来控制
TM (Tx-Manager):是独立的服务,是分布式事务的控制方,协调分布式事务的提交,回滚
流程
1、事务发起方也是一个事务模块,它去调用TM创建一个事务组,这个事务组的数据就会在TM下存储(目前TM数据是存在Redis下),目前框架中TM数据是存在Redis下。
2、为了简化流程,参与方A是先执行业务再加入事务组,这样当参与方A在执行业务过程中出现异常就没有必要再加入事务组了,此时事务发起方会捕获该异常然后直接通知事务回滚。如果参与方A先加入事务组再执行业务,则A出现异常时再通过事务组去通知它,会多一个来回。执行完“加入事务组”之后,TM就把参与方A的状态记录下来。参与方A会给事务发起方返回数据(response)
3、参与方B与参与方A类似,也是由事务发起方调用,执行完业务再加入事务组,最后给事务发起方返回数据。
4、只要没有抛异常,事务发起方就视为要提交本次事务,此时通知事务管理器,让其知道是要提交还是回滚。TM会遍历这个事务组下关联的事务参与方信息,去通知各参与方要执行的状态(提交还是回滚)。
5、当TM拿到各参与方返回的信息时,才会响应给事务发起方,告知事务已处理完毕。事务发起方拿到TM的这个响应之后,就可以响应给调用者了。
2、为了简化流程,参与方A是先执行业务再加入事务组,这样当参与方A在执行业务过程中出现异常就没有必要再加入事务组了,此时事务发起方会捕获该异常然后直接通知事务回滚。如果参与方A先加入事务组再执行业务,则A出现异常时再通过事务组去通知它,会多一个来回。执行完“加入事务组”之后,TM就把参与方A的状态记录下来。参与方A会给事务发起方返回数据(response)
3、参与方B与参与方A类似,也是由事务发起方调用,执行完业务再加入事务组,最后给事务发起方返回数据。
4、只要没有抛异常,事务发起方就视为要提交本次事务,此时通知事务管理器,让其知道是要提交还是回滚。TM会遍历这个事务组下关联的事务参与方信息,去通知各参与方要执行的状态(提交还是回滚)。
5、当TM拿到各参与方返回的信息时,才会响应给事务发起方,告知事务已处理完毕。事务发起方拿到TM的这个响应之后,就可以响应给调用者了。
2PC带来的问题
单点故障导致高可用(HA)问题
同步阻塞导致性能问题
丢失消息导致的数据不一致问题
3PC作为2PC的改进版,3PC将原有的两阶段过程,重新划分为CanCommit、PreCommit和do Commit三个阶段
相对于2PC,3PC主要解决的单点故障问题,并减少阻塞(引入 超时机制)
JTA/JTS规范
原理:JTA是基于XA架构上建模的,在JTA 中,事务管理器抽象为javax.transaction.TransactionManager接口,并通过底层事务服务(即JTS)实现,JTA更多的是从框架的角度来约定程序角色的接口,而JTS则是从具体实现的角度来约定程序角色之间的接口
实现方式
J2EE容器所提供的JTA实现(JBoss)
独立的JTA(Java-Transaction-API)实现:如JOTM,Atomikos
spring事务整合
DataSourceTransactionManager:用于实现本地事务
JTATransactionManager:用于实现分布式事务
学习地址:https://github.com/cicadasmile/middle-ware-parent
源码地址:https://gitee.com/cicadasmile/middle-ware-parent
XA缺点
数据锁定:数据在事务未结束前,为了保障一致性,根据数据隔离级别进行锁定
协议阻塞:本地事务在全局事务 没 commit 或 callback前都是阻塞等待的
性能损耗高:主要体现在事务协调增加的RT成本,并发事务数据使用锁进行竞争阻塞
Seata 是什么?
Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案
0 条评论
下一页
为你推荐
查看更多