分布式事务 面试专题
- 分布式事务与分布式锁的区别
- 分布式事务场景
- 核心理论
- 分布式事务分类
- 2PC(标准XA模型)
- 3PC(CanCommit、PreCommit、doCommit )
- 通知型事务
- 异步确保型事务
- 最大努力通知事务
- MQ事务消息方案
- 本地消息表方案
- 补偿型
- TCC(Try-Confirm / Cancel)事务模型
- SAGA长事务模型
- Seata
- Seata AT模式
- Seata TCC 模式
- Seata Saga 模式
- Seata XA 模式
分布式事务与分布式锁的区别
分布式锁解决的是分布式资源抢占的问题;
分布式事务和本地事务是解决流程化提交问题。
分布式事务场景
- 跨库事务
⼀个应⽤某个功能需要操作多个库,不同的库中存储不同的业务数据。
- 分库分表
⼀个库数据量⽐较⼤或者预期未来的数据量⽐较⼤,都会进⾏⽔平拆分,也就是分库分表。
- 微服务化
独⽴服务之间通过RPC框架来进⾏远程调⽤,实现彼此的通信。
核心理论
- CAP定理:分布式系统最多同时满足一致性(C)、可用性(A)、分区容忍性(P)中的两项。
- BASE理论(CAP理论中AP⽅案的延伸):
- 基本可用(Basically Available):允许部分功能降级。
- 软状态(Soft State):允许中间状态存在。
- 最终一致性(Eventual Consistency):数据最终达成一致。
分布式事务分类
- 刚性事务
- 刚性事务满⾜CAP的CP理论
- 常⽆业务改造,强⼀致性,原⽣⽀持回滚/隔离性,低并发,适合短事务。
- XA 协议(2PC、 JTA、 JTS)、 3PC,但由于同步阻塞,处理效率低,不适合⼤型⽹站分布式场景。
- 柔性事务
- 补偿型
- 异步确保型
- 最大努力通知型
- 柔性事务满⾜BASE理论(基本可⽤,最终⼀致)
- TCC/FMT、 Saga(状态机模式、 Aop模式)、本地事务消息、消息事务(半消息)
- 总体的⽅案对⽐:
属性 | 2PC | TCC | Saga | 异步确保型事务 | 尽最大努力通知 |
---|---|---|---|---|---|
事务⼀致性 | 强 | 弱 | 弱 | 弱 | 弱 |
复杂性 | 中 | 高 | 中 | 低 | 低 |
业务侵入性 | 小 | 大 | 小 | 中 | 中 |
使用局限性 | 大 | 大 | 中 | 小 | 中 |
性能 | 低 | 中 | 高 | 高 | 高 |
维护成本 | 低 | 高 | 中 | 低 | 中 |
2PC(标准XA模型)
- XA/2PC(两阶段提交)
- 阶段一:提交事务请求(Prepare)
- 事务询问:协调者向所有参与者发送事务内容,询问是否可以执⾏提交操作,并开始等待各参与者进⾏响应。
- 执行事务:各参与者节点,执⾏事务操作,并将Undo和Redo操作计⼊本机事务⽇志。
- 各参与者向协调者反馈事务问询的响应:成功执⾏返回Yes,否则返回No。
- 阶段二:执行事务提交(Commit/Rollback)
- 执行事务提交
所有参与者reply Yes,那么执⾏事务提交。- 发送提交请求:协调者向所有参与者发送Commit请求。
- 事务提交:参与者收到Commit请求后,会
正式执⾏事务提交操作
,并在完成提交操作之后,释放在整个事务执⾏期间占⽤的资源。 - 反馈事务提交结果:参与者在完成事务提交后,写协调者发送Ack消息。
- 完成事务:协调者在收到所有参与者的Ack后,完成事务。
确认
- 中断事务
当存在某⼀参与者向协调者发送No响应,或者等待超时。协调者只要⽆法收到所有参与者的Yes响应,就会中断事务。- 发送回滚请求:协调者向所有参与者发送Rollback请求。
- 回滚:参与者收到请求后,利⽤本机Undo信息,执⾏Rollback操作。并在回滚结束后释放该事务所占⽤的系统资源。
- 反馈回滚结果:参与者在完成回滚操作后,向协调者发送Ack消息。
- 中断事务:协调者收到所有参与者的回滚Ack消息后,完成事务中断。
- 执行事务提交
- 缺点:
- 同步阻塞:执行过程中,所有参与节点都是事务阻塞型的。
- 单点故障:由于协调者的重要性,一旦协调者发生故障。参与者会一直阻塞下去。
- 数据不一致(脑裂问题):在二阶段提交的阶段二中,当协调者向参与者发送 commit 请求之后,发生了局部网络异常或者在发送 commit 请求过程中协调者发生了故障,导致只有一部分参与者接受到了commit 请求。于是整个分布式系统便出现了数据部一致性的现象(脑裂现象)。
- 二阶段无法解决的问题(数据状态不确定):
协调者再发出 commit 消息之后宕机, 而唯一接收到这条消息的参与者同时也宕机了。那么即使协调者通过选举协议产生了新的协调者, 这条事务的状态也是不确定的,没人知道事务是否被已经提交。
- 阶段一:提交事务请求(Prepare)
3PC(CanCommit、PreCommit、doCommit )
- CanCommit
- 事务询问:协调者向所有参与者发送包含事务内容的canCommit的请求,询问是否可以执⾏事务提交,并等待应答。
- 各参与者反馈事务询问:正常情况下,如果参与者认为可以顺利执⾏事务,则返回Yes,否则返回No。
- PreCommit
- 执行事务预提交
- 协调者发送预提交请求:协调者向所有节点发出PreCommit请求,并进⼊prepared阶段。
- 事务参与者事务预提交:参与者收到PreCommit请求后,会执⾏事务操作,并将Undo和Redo⽇志写⼊本机事务⽇志。
- 各参与者成功执⾏事务操作,同时将反馈以Ack响应形式发送给协调者,同事等待最终的Commit或Abort指令。
- 中断事务
任意⼀个参与者向协调者发送No响应,或者等待超时,协调者在没有得到所有参与者响应时,即可以中断事务。- 协调者发送中断请求:协调者向所有参与者发送Abort请求。
- 协调者中断事务:⽆论是收到协调者的Abort请求,还是等待协调者请求过程中出现超时,参与者都会中断事务。
- 执行事务预提交
- doCommit
- 协调者发送提交请求:假如协调者收到了所有参与者的Ack响应,那么将从预提交转换到提交状态,并向所有参与者,发送doCommit请求。
- 事务参与者提交事务:参与者收到doCommit请求后,会正式执⾏事务提交操作,并在完成提交操作后释放占⽤资源。
- 事务参与者反馈提交结果:参与者将在完成事务提交后,向协调者发送Ack消息。
- 协调者完成事务:协调者接收到所有参与者的Ack消息后,完成事务。
通知型事务
通知型事务的主流实现是通过MQ(消息队列)来通知其他事务参与者⾃⼰事务的执⾏状态,引⼊MQ组件,有效的将事务参与者进⾏解耦,各参与者都可以异步执⾏,所以通知型事务⼜被称为异步事务:
- 异步确保型事务:主要适⽤于
内部系统
的数据最终⼀致性保障,因为内部相对⽐较可控,如订单和购物⻋、收货与清算、⽀付与结算等等场景。 - 最大努力通知事务:主要⽤于
外部系统
,因为外部的⽹络环境更加复杂和不可信,所以只能尽最⼤努⼒去通知实现数据最终⼀致性,⽐如充值平台与运营商、⽀付对接等等跨⽹络系统级别对接。
最大努力通知事务 VS 异步确保型事务
最⼤努⼒通知事务在我认知中,其实是基于异步确保型事务发展⽽来适⽤于外部对接的⼀种业务实现。他们主要有的是业务差别,如下:
- 参与者
- 最大努力通知事务:适⽤于跨平台、跨企业的系统间业务交互。
- 异步确保型事务:更适⽤于同⽹络体系的内部服务交付。
- 消息层面
- 最大努力通知事务:需要主动推送并提供多档次时间的重试机制来保证数据的通知。
- 异步确保型事务:只需要消息消费者主动去消费。
- 数据层面
- 最大努力通知事务:还需额外的定期校验机制对数据进⾏兜底,保证数据的最终⼀致性。
- 异步确保型事务:只需保证消息的可靠投递即可,⾃身⽆需对数据进⾏兜底处理。
通知型事务的问题
无法解决本地事务执行和消息发送的一致性问题:
因为消息发送是⼀个⽹络通信的过程,发送消息的过程就有可能出现发送
失败、或者超时的情况。超时有可能发送成功了,有可能发送失败了,消
息的发送⽅是⽆法确定的,所以此时消息发送⽅⽆论是提交事务还是回滚
事务,都有可能不⼀致性出现。
-
消息发送一致性
消息中间件在分布式系统中的核⼼作⽤就是异步通讯、应⽤解耦和并发缓冲(也叫作流量削峰)。在分布式环境下,需要通过⽹络进⾏通讯,就引⼊了数据传输的不确定性,也就是CAP理论中的分区容错性。消息发送⼀致性是指
产⽣消息的业务动作与消息发送动作⼀致
,也就是说如果业务操作成功,那么由这个业务操作所产⽣的消息⼀定要发送出去,否则就丢失。- 常规MQ消息处理流程和特点
常规的MQ队列处理流程⽆法实现消息的⼀致性。所以,需要借助半消息、本地消息表,保障⼀致性。
- 常规MQ消息处理流程和特点
异步确保型事务
指将⼀系列同步的事务操作修改为基于消息队列异步执⾏的操作,来避免
分布式事务中同步阻塞带来的数据操作性能的下降。
-
MQ事务消息方案
基于MQ的事务消息⽅案主要依靠MQ的半消息机制
来实现投递消息和参与者⾃身本地事务的⼀致性保障。半消息机制实现原理其实借鉴的2PC的思路,是⼆阶段提交的⼴义拓展。
半消息:在原有队列消息执⾏后的逻辑,如果后⾯的本地逻辑出错,
则不发送该消息,如果通过则告知MQ发送.
- 事务发起⽅⾸先发送半消息到MQ。
- MQ通知发送⽅消息发送成功。
- 在发送半消息成功后执⾏本地事务。
- 根据本地事务执⾏结果返回commit或者是rollback。
- rollback:MQ将丢弃该消息不投。
- commit:MQ将会消息发送给消息订阅方。
- 订阅⽅根据消息执⾏本地事务。
- 订阅⽅执⾏本地事务成功后再从MQ中将该消息标记为已消费。
- 如果执⾏本地事务过程中,执⾏端挂掉,或者超时, MQ服务器端将不停的询问producer来获取事务状态。
- Consumer端的消费成功机制有MQ保证。
-
基于阿⾥ RocketMQ实现MQ异步确保型事务
- producer发送半消息到broker,这个半消息不是说消息内容不完整, 它包含完整的消息内容, 在producer端和普通消息的发送逻辑⼀致。
- broker存储半消息,半消息存储逻辑与普通消息⼀致,只是属性有所不同, topic是固定的RMQ_SYS_TRANS_HALF_TOPIC, queueId也是固定为0,这个tiopic中的消息对消费者是不可⻅的,所以⾥⾯的消息永远不会被消费。这就保证了在半消息提交成功之前,消费者是消费不到这个半消息的。
- broker端半消息存储成功并返回后, A系统执⾏本地事务,并根据本地事务的执⾏结果来决定半消息的提交状态为提交或者回滚。
- A系统发送结束半消息的请求,并带上提交状态(提交 or 回滚)。
- broker端收到请求后,⾸先从RMQ_SYS_TRANS_HALF_TOPIC的
queue中查出该消息,设置为完成状态。- 消息状态为提交,则把半消息从RMQ_SYS_TRANS_HALF_TOPIC队列中复制到这个消息原始topic的queue中去(之后这条消息就能被正常消费了)。
- 消息状态为回滚,则什么也不做。
- producer发送的半消息结束请求是 oneway 的,也就是发送后就不管了,只靠这个是⽆法保证半消息⼀定被提交的, rocketMq提供了⼀个兜底⽅案,这个⽅案叫消息反查机制, Broker启动时,会启动⼀个TransactionalMessageCheckService 任务,该任务会定时从半消息队列中读出所有超时未完成的半消息,针对每条未完成的消息, Broker会给对应的Producer发送⼀个消息反查请求,根据反查结果来决定这个半消息是需要提交还是回滚,或者后⾯继续来反查。
- consumer(本例中指B系统)消费消息,执⾏本地数据变更(⾄于B是否能消费成功,消费失败是否重试,这属于正常消息消费需要考虑的问题)。
-
本地消息表方案
⽣产⽅和消费⽅定时扫描本地消息表,把还没处理完成的消息或者失败的消息再发送⼀遍。 -
发送消息方:
- 需要有⼀个消息表,记录着消息状态相关信息。
- 业务数据和消息表在同⼀个数据库,要保证它俩在同⼀个本地事务。直接利⽤本地事务,将业务数据和事务消息直接写⼊数据库。
- 在本地事务中处理完业务数据和写消息表操作后,通过写消息到 MQ 消息队列。使⽤专⻔的投递⼯作线程进⾏事务消息投递到MQ,根据投ACK去删除事务消息表记录。
- 消息会发到消息消费⽅,如果发送失败,即进⾏重试。
-
消息消费方:
- 处理消息队列中的消息,完成⾃⼰的业务逻辑。
- 如果本地事务处理成功,则表明已经处理成功了。
- 如果本地事务处理失败,那么就会重试执⾏。
- 如果是业务层⾯的失败,给消息⽣产⽅发送⼀个业务补偿消息,通进⾏回滚等操作。
-
本地消息表优缺点:
- 优点:
- 本地消息表建设成本⽐较低,实现了可靠消息的传递确保了分布事务的最终⼀致性。
- ⽆需提供回查⽅法,进⼀步减少的业务的侵⼊。
- 在某些场景下,还可以进⼀步利⽤注解等形式进⾏解耦,有可能实现⽆业务代码侵⼊式的实现。
- 缺点:
- 本地消息表与业务耦合在⼀起,难于做成通⽤性,不可独⽴伸缩。
- 本地消息表是基于数据库来做的,⽽数据库是要读写磁盘IO的,因此在⾼并发下是有性能瓶颈的。
- 优点:
最大努力通知事务
- 最大努力通知型的最终⼀致性:
本质是通过引⼊定期校验机制
实现最终⼀致性,对业务的侵⼊性较低,适合于对最终⼀致性敏感度⽐较低、业务链路较短的场景。 - 最大努力通知事务主要⽤于
外部系统
,因为外部的⽹络环境更加复杂和不可信,所以只能尽最⼤努⼒去通知实现数据最终⼀致性, ⽐如充值平台与运营商、⽀付对接、商户通知等等跨平台、跨企业的系统间业务交互场景。 - 异步确保型事务主要适⽤于
内部系统
的数据最终⼀致性保障,因为内部相对⽐较可控,⽐如订单和购物⻋、收货与清算、⽀付与结算等等场景。
通知型事务的难度在于:投递消息和参与者本地事务的⼀致性保障
。
因为核⼼要点⼀致,都是为了保证消息的⼀致性投递
,所以,最⼤努⼒通知事务在投递流程上跟异步确保型是⼀样的,因此也有两个分⽀
: - 基于MQ自身的事务消息方案
- 基于DB的本地事务消息表方案
最⼤努⼒通知事务在于第三⽅系统的对接,所以最⼤努⼒通知事务有⼏个特性:
- 业务主动⽅在完成业务处理后,向业务被动⽅(第三⽅系统)发送通知消息,允许存在消息丢失。
- 业务主动⽅提供递增多挡位时间间隔(5min、 10min、 30min、 1h、24h),⽤于失败重试调⽤业务被动⽅的接⼝;在通知N次之后就不再通知,报警+记⽇志+⼈⼯介⼊。
- 业务被动⽅提供幂等的服务接⼝,防⽌通知重复消费。
- 业务主动⽅需要有定期校验机制,对业务数据进⾏兜底;防⽌业务被动⽅⽆法履⾏责任时进⾏业务回滚,确保数据最终⼀致性。
MQ事务消息方案
要实现最⼤努⼒通知,可以采⽤ MQ 的 ACK 机制。
最⼤努⼒通知事务在投递之前,跟异步确保型流程都差不多,关键在于投
递后的处理。
流程:
- 业务活动的主动⽅,在完成业务处理之后,向业务活动的被动⽅发送消息,允许消息丢失。
- 主动⽅可以设置时间阶梯型通知规则,在通知失败后按规则重复通知,直到通知N次后不再通知。
- 主动⽅提供校对查询接⼝给被动⽅按需校对查询,⽤于恢复丢失的业务消息。
- 业务活动的被动⽅如果正常接收了数据,就正常返回响应,并结束事务。
- 如果被动⽅没有正常接收,根据定时策略,向业务活动主动⽅查询,恢复丢失的业务消息。
特点:
- ⽤到的服务模式:可查询操作、幂等操作。
- 被动⽅的处理结果不影响主动⽅的处理结果。
- 适⽤于对业务最终⼀致性的时间敏感度低的系统。
- 适合跨企业的系统间的操作,或者企业内部⽐较独⽴的系统间的操作,⽐如银⾏通知、商户通知等。
本地消息表方案
要实现最⼤努⼒通知,可以采⽤ 定期检查本地消息表
的机制。
发送消息方:
- 需要有⼀个消息表,记录着消息状态相关信息。
- 业务数据和消息表在同⼀个数据库,要保证它俩在同一个本地事务。直接利用本地事务,将业务数据和事务消息直接写⼊数据库。
- 在本地事务中处理完业务数据和写消息表操作后,通过写消息到 MQ 消息队列。使用专门的投递工作线程进⾏事务消息投递到MQ,根据投递ACK去删除事务消息表记录。
- 消息会发到消息消费⽅,如果发送失败,即进⾏重试。
- 生产方定时扫描本地消息表,把还没处理完成的消息或者失败的消息再发送⼀遍。如果有靠谱的自动对账补账逻辑,这种⽅案还是非常实⽤的。
补偿型
- 补偿模式
使⽤⼀个额外的协调服务
来协调各个需要保证⼀致性的业务服务,协调服务按顺序调⽤各个业务微服务,如果某个业务服务调⽤异常(包括业务异常和技术异常)就取消之前所有已经调⽤成功的业务服务。- TCC
- ⼀种新的事务模型,基于业务层⾯的事务定义,锁粒度完全由业务⾃⼰控制,⽬的是解决复杂业务中,跨表跨库等⼤颗粒度资源锁定的问题。
- 把事务运⾏过程分成 Try、 Confirm / Cancel 两个阶段,每个阶段的逻辑由业务代码控制,避免了⻓事务,可以获取更⾼的性能。
- Saga
- 把⼀个分布式事务拆分为多个本地事务,每个本地事务都有相应的执⾏模块和补偿模块(对应TCC中的Confirm和Cancel),当Saga事务中任意⼀个本地事务出错时,可以通过调⽤相关的补偿⽅法恢复之前的事务,达到事务最终⼀致性。
- 缺点:⽆法保证隔离性。
- 优点:
- ⼀阶段提交本地事务,⽆锁,⾼性能。
- 事件驱动模式,参与者可异步执⾏,⾼吞吐。
- Saga 对业务侵⼊较⼩,只需要提供⼀个逆向操作的Cancel即可;⽽TCC需要对业务进⾏全局性的流程改造。
- TCC
TCC(Try-Confirm / Cancel)事务模型
- TCC 分布式事务模型包括三部分:
- 主业务服务:整个业务活动的
发起⽅
,服务的编排者,负责发起并完成整个业务活动。 - 从业务服务:整个业务活动的
参与⽅
,负责提供TCC 业务操作
,实现初步操作(Try)、确认操作(Confirm)、取消操作(Cancel)
三个接⼝,供主业务服务调⽤。 - 业务活动管理器:管理控制整个业务活动,包括
记录维护 TCC 全局事务的事务状态
和每个从业务服务的⼦事务状态
,并在业务活动提交时调⽤所有从业务服务的 Confirm 操作
,在业务活动取消时调⽤所有从业务服务的 Cancel 操作
。
- 主业务服务:整个业务活动的
- TCC的工作作流程
它不依赖资源管理器(RM)对分布式事务的⽀持,⽽是通过对业务逻辑
的分解来实现分布式事务。
TCC 分布式事务模型需要业务系统提供三段业务逻辑
:
- TCC 分布式事务模型包括三部分:
- Try 阶段: 调⽤ Try 接⼝,尝试执⾏业务,完成所有业务检查,预留业务资源。
- Confirm 或 Cancel 阶段: 两者是互斥的,只能进⼊其中⼀个,并且都满⾜幂等性,允许失败重试。
- Confirm 操作: 对业务系统做确认提交,确认执⾏业务操作,不做其他业务检查,只使⽤ Try 阶段预留的业务资源。
- Cancel 操作: 在业务执⾏错误,需要回滚的状态下执⾏业务取消,释放预留资源。
- TCC事务模型的要求:
- 可查询操作:服务操作具有全局唯⼀的标识,操作唯⼀的确定的时间。
- 幂等操作:重复调用多次产⽣的业务结果与调⽤⼀次产⽣的结果相同。⼀是通过业务操作实现幂等性,⼆是系统缓存所有请求与处理的结果,最后是检测到重复请求之后,⾃动返回之前的处理结果。
- TCC操作:TCC与2PC(两阶段提交)协议的区别: TCC位于业务服务层⽽不是资源层, TCC没有单独准备阶段, Try操作兼备资源操作与准备的能⼒, TCC中Try操作可以灵活的选择业务资源,锁定粒度。 TCC的开发成本⽐2PC⾼。实际上TCC也属于两阶段操作,但是TCC不等同于2PC操作。
- 可补偿操作:
- Do阶段:真正的执行业务处理,业务处理结果外部可见。
- Compensate阶段:抵消或者部分撤销正向业务操作的业务结果,补偿操作满⾜幂等性。
- 约束:补偿操作在业务上可行,由于业务执行结果未隔离或者补偿不完整带来的⻛险与成本可控。实际上, TCC的Confirm和Cancel操作可以看做是补偿操作。
SAGA长事务模型
- SAGA可以看做⼀个异步的、利⽤队列实现的补偿事务。
- 把⼀个分布式事务拆分为多个本地事务,每个本地事务都有相应的执⾏模块和补偿模块(对应TCC中的Confirm和Cancel),当Saga事务中任意⼀个本地事务出错时,可以通过调⽤相关的补偿⽅法恢复之前的事务,达到事务最终⼀致性。
- Saga 模型由三部分组成:
- LLT(Long Live Transaction):由⼀个个本地事务组成的事务链。
- 本地事务:事务链由⼀个个⼦事务(本地事务)组成, LLT =T1+T2+T3+…+Ti。
- 补偿:每个本地事务 Ti 有对应的补偿 Ci。
- Saga的执⾏顺序有两种:
- T1, T2, T3, …, Tn
- T1, T2, …, Tj, Cj,…, C2, C1,其中0 < j < n
- Saga 两种恢复策略
- 向后恢复(Backward Recovery):撤销掉之前所有成功⼦事务。如果任意本地⼦事务失败,则补偿已完成的事务。如异常情况的执⾏顺序T1,T2,T3,…Ti,Ci,…C3,C2,C1。
- 向前恢复(Forward Recovery):即重试失败的事务,适⽤于必须要成功的场景,该情况下不需要Ci。执⾏顺序: T1,T2,…,Tj(失败) ,Tj(重试) ,…,Ti。
- Saga的使⽤条件
- Saga只允许
两个层次的嵌套
,顶级的Saga和简单⼦事务。 - 在外层,全原⼦性不能得到满⾜。也就是说, sagas可能会看到其他sagas的部分结果。
- 每个⼦事务应该是独⽴的原⼦⾏为。
- 在我们的业务场景下,各个业务环境(如:航班预订、租⻋、酒店预订和付款)是⾃然独⽴的⾏为,⽽且每个事务都可以⽤对应服务的数据库保证原⼦操作。
- Saga只允许
- 对于ACID的保证:
- 原⼦性(Atomicity):正常情况下保证。
- ⼀致性(Consistency),在某个时间点,会出现A库和B库的数据违反⼀致性要求的情况,但是最终是⼀致的。
- 隔离性(Isolation),在某个时间点, A事务能够读到B事务部分提交的结果。
- 持久性(Durability),和本地事务⼀样,只要commit则数据被持久。
- SAGA模型的解决⽅案
- 半消息模式
- 向MQ发送半消息
- 向DB插⼊数据
- 向MQ发送确认消息
- 问题:第2或3步失败,MQ中的消息就会⼀直是半消息状态,也就不会被消费者消费。
- 解决方案:MQ引⼊了⼀个
扫描机制
。即MQ会每隔⼀段时间,对所有的半消息进⾏扫描,并就扫描到的存在时间过⻓的半消息,向发送者进⾏询问,询问如果得到确认回复
,则将消息改为确认状态
,如得到失败回复
,则将消息删除
。
- 本地消息表
- 在DB业务表中插⼊数据
- 在DB消息表中插⼊数据
- 异步将消息表中的消息发送到MQ,收到ack后,删除消息表中的消息。
- 半消息模式
Seata
- Seata 分三大模块:
TM 和 RM 是作为 Seata 的客户端与业务系统集成在⼀起, TC 作为 Seata 的服务端独⽴部署。- TC:
事务协调者
负责我们的事务ID的⽣成,事务注册、提交、回滚等。 - TM:
事务发起者
定义事务的边界,负责告知 TC,分布式事务的开始,提交,回滚。 - RM:
资源管理者
管理每个分⽀事务的资源,每⼀个 RM 都会作为⼀个分⽀事务注册在 TC。
- TC:
- Seata 的分布式事务的执行流程:
- TM 开启分布式事务(TM 向 TC 注册全局事务记录)。
- 按业务场景,编排数据库、服务等事务内资源(RM 向 TC 汇报资源准备状态 )。
- TM 结束分布式事务,事务⼀阶段结束(TM 通知 TC 提交/回滚分布式事务)。
- TC 汇总事务信息,决定分布式事务是提交还是回滚。
- TC 通知所有 RM 提交/回滚 资源,事务⼆阶段结束。
- 4 种分布式事务解决方案
- AT 模式
- TCC模式
- Saga模式
- XA模式
Seata AT模式
指Automatic (Branch) Transaction Mode⾃动化分⽀事务。增强型2pc模式,或者说是增强型的XA模型。不会⼀直锁表。
- Seata AT模式的使用前提
- 基于⽀持本地 ACID 事务的关系型数据库。
- Java 应⽤,通过 JDBC 访问数据库。
- Seata AT模型图
- ⼀阶段:业务数据和回滚⽇志记录在同⼀个本地事务中提交,释放本地锁和连接资源 。
- ⼆阶段:
- 提交异步化,⾮常快速地完成。
- 或回滚通过⼀阶段的回滚⽇志进⾏反向补偿。
- Seata AT模式的示例
有个充值业务,现在有两个服务,⼀个负责管理⽤户的余额,另外⼀个负责管理⽤户的积分。
当⽤户充值的时候,⾸先增加⽤户账户上的余额,然后增加⽤户的积分。
Seata AT分为两阶段,主要逻辑全部在第⼀阶段,第⼆阶段主要做回滚或⽇志清理的⼯作。- 第⼀阶段流程
- 余额服务中的TM,向TC申请开启⼀个全局事务, TC会返回⼀个全局的事务ID。
- 余额服务在执⾏本地业务之前, RM会先向TC注册分⽀事务。
- 余额服务依次⽣成undo log、执⾏本地事务、⽣成redo log,最后直接提交本地事务。
- 余额服务的RM向TC汇报,事务状态是成功的。
- 余额服务发起远程调⽤,把事务ID传给积分服务。
- 积分服务在执⾏本地业务之前,也会先向TC注册分⽀事务。
- 积分服务次⽣成undo log、执⾏本地事务、⽣成redo log,最后直接提交本地事务。
- 积分服务的RM向TC汇报,事务状态是成功的。
- 积分服务返回远程调⽤成功给余额服务。
- 余额服务的TM向TC申请全局事务的提交/回滚。
- 第二阶段流程
- 正常全局提交:TC通知多个RM异步清理掉本地的redo和undo log。
- 回滚:TC通知每个RM回滚数据。
- 每个事务从本地提交到通知回滚这段时间⾥,可能这条数据已经被别的事务修改,如果直接⽤undo log回滚,会导致数据不⼀致的情况。RM会⽤redo log进⾏校验,对⽐数据是否⼀样,从⽽得知数据是否有别的事务修改过。注意: undo log是被修改前的数据,可以⽤于回滚;redo log是被修改后的数据,⽤于回滚校验。如果数据未被其他事务修改过,则可以直接回滚;如果是脏数据,再根据不同策略处理。
- 第⼀阶段流程
- Seata的数据隔离性
- 写隔离
- ⼀阶段本地事务提交前,需要确保先拿到
全局锁
。 - 拿不到
全局锁
,不能提交本地事务。 - 拿
全局锁
的尝试被限制在⼀定范围内,超出范围将放弃,并回滚本地事务,释放本地锁。
- ⼀阶段本地事务提交前,需要确保先拿到
- 读的隔离级别
在数据库本地事务隔离级别读已提交
(Read Committed) 或以上的基础上, Seata(AT 模式)的默认全局隔离级别是读未提交
(Read Uncommitted) 。
- 写隔离
Seata TCC 模式
- 简介
TCC 与 Seata AT 事务⼀样都是两阶段事务
,它与 AT 事务的主要区别:- TCC 对业务代码侵⼊严重
每个阶段的数据操作都要⾃⼰进⾏编码来实现,事务框架⽆法⾃动处理。 - TCC 性能更高
不必对数据加全局锁,允许多个事务同时操作数据。
- TCC 对业务代码侵⼊严重
Seata Saga 模式
Saga模式是SEATA提供的⻓事务解决⽅案,在Saga模式中,业务流程中
每个参与者都提交本地事务,当出现某⼀个参与者失败则补偿前⾯已经成
功的参与者,⼀阶段正向服务和⼆阶段补偿服务都由业务开发实现。
- 适用场景:
- 业务流程⻓、业务流程多。
- 参与者包含其它公司或遗留系统服务,⽆法提供 TCC 模式要求的三个接⼝。
- 优势:
- ⼀阶段提交本地事务,⽆锁,⾼性能。
- 事件驱动架构,参与者可异步执⾏,⾼吞吐。
- 补偿服务易于实现。
- 缺点:
- 不保证隔离性
- 基于状态机引擎的 Saga 实现
- 通过状态图来定义服务调⽤的流程并⽣成 json 状态语⾔定义⽂件。
- 状态图中⼀个节点可以是调⽤⼀个服务,节点可以配置它的补偿节点。
- 状态图 json 由状态机引擎驱动执⾏,当出现异常时状态引擎反向执⾏已成功节点对应的补偿节点将事务回滚。
- 可以实现服务编排需求,⽀持单项选择、并发、⼦流程、参数转换、参数映射、服务执⾏状态判断、异常捕获等功能。
- 状态机引擎原理:
- 图中的状态图是先执行stateA, 再执行stateB,然后执行stateC。
- "状态"的执行是基于事件驱动的模型,stateA执行完成后,会产生路由消息放入EventQueue,事件消费端从EventQueue取出消息,执行stateB。
- 在整个状态机启动时会调用Seata Server开启分布式事务,并生产xid, 然后记录"状态机实例"启动事件到本地数据库。
- 当执行到一个"状态"时会调用Seata Server注册分支事务,并生产branchId, 然后记录"状态实例"开始执行事件到本地数据库。
- 当一个"状态"执行完成后会记录"状态实例"执行结束事件到本地数据库, 然后调用Seata Server上报分支事务的状态。
- 当整个状态机执行完成, 会记录"状态机实例"执行完成事件到本地数据库, 然后调用Seata Server提交或回滚分布式事务。
- 状态机引擎设计:
状态机引擎的设计主要分成三层, 上层依赖下层,从下往上分别是:- Eventing 层
- 实现事件驱动架构, 可以压入事件, 并由消费端消费事件, 本层不关心事件是什么消费端执行什么,由上层实现。
- ProcessController 层
- 由于上层的Eventing驱动一个“空”流程引擎的执行,"state"的行为和路由都未实现, 由上层实现。
- StateMachineEngine 层
- 实现状态机引擎每种state的行为和路由逻辑。
- 提供 API、状态机语言仓库。
- Eventing 层
- 状态机的高可用设计:
状态机引擎是无状态的,它是内嵌在应用中。
当应用正常运行时(图中上半部分):
- 状态机引擎会上报状态到Seata Server。
- 状态机执行日志存储在业务的数据库中。
当一台应用实例宕机时(图中下半部分):
- Seata Server 会感知到,并发送事务恢复请求到还存活的应用实例。
- 状态机引擎收到事务恢复请求后,从数据库里装载日志,并恢复状态机上下文继续执行。
Seata XA 模式
- 使⽤Seata XA 模式的前提
- 支持XA 事务的数据库。
- Java 应用,通过 JDBC 访问数据库。
- 整体机制
在 Seata 定义的分布式事务框架内,利用事务资源(数据库、消息服务等)对 XA 协议的支持,以 XA 协议的机制来管理分支事务的一种 事务模式。
- 执行阶段:
- 可回滚:业务 SQL 操作放在 XA 分支中进行,由资源对 XA 协议的支持来保证 可回滚。
- 持久化:XA 分支完成后,执行 XA prepare,同样,由资源对 XA 协议的支持来保证 持久化(即,之后任何意外都不会造成无法回滚的情况)。
- 完成阶段:
- 分支提交:执行 XA 分支的 commit。
- 分支回滚:执行 XA 分支的 rollback。
- 执行阶段:
- 工作机制
- 整体运行机制
XA 模式 运行在 Seata 定义的事务框架内:
- 执行阶段(E xecute)
- XA start/XA end/XA prepare + SQL + 注册分支。
- 完成阶段(F inish)
- XA commit/XA rollback
- 执行阶段(E xecute)
- 数据源代理
XA 模式需要 XAConnection。获取 XAConnection 两种方式:- 要求开发者配置 XADataSource。
- 根据开发者的普通 DataSource 来创建。
- 分支注册
XA start 需要 Xid 参数。
这个 Xid 需要和 Seata 全局事务的 XID 和 BranchId 关联起来,以便由 TC 驱动 XA 分支的提交或回滚。
目前 Seata 的 BranchId 是在分支注册过程,由 TC 统一生成的,所以 XA 模式分支注册的时机需要在 XA start 之前。
将来一个可能的优化方向:
把分支注册尽量延后。类似 AT 模式在本地事务提交之前才注册分支,避免分支执行失败情况下,没有意义的分支注册。
这个优化方向需要 BranchId 生成机制的变化来配合。BranchId 不通过分支注册过程生成,而是生成后再带着 BranchId 去注册分支。
- 整体运行机制