分布式事务的概念与实现方式 心已赠人 2023-10-18 23:41 62阅读 0赞 #### 文章目录 #### * 1. 概念 * 2. 分布式事务的实现 * * * 2.1 数据库层面的实现 * * 2.1.1 两阶段提交(2PC) * * * 2.1.1.1 两阶段提交的流程 * 2.1.1.2 两阶段提交协议中的问题 * 2.1.2 三阶段提交(3PC) * * * 2.1.2.1 三阶段提交的流程 * 2.1.2.2 3PC相对于2PC的优化 * 2.2 应用层面的实现 * * 2.2.1 补偿事务(TCC) * 2.2.2 本地消息表 ## 1. 概念 ## **分布式事务是为了解决分布式系统中不同节点之间的数据一致性问题**. 本质上分布式事务解决的也是传统事务需要解决的问题,即一个请求在多个微服务调用链中,所有服务的数据处理要么全部成功,要么全部回滚. ## 2. 分布式事务的实现 ## #### 2.1 数据库层面的实现 #### 分布式事务的实现方式有很许多,最具有代表性的是由Oracle Tuxedo系统提出的XA分布式事务协议.XA协议包括两阶段提交(2PC)和三阶段提交(3PC)两种实现, 二者都是基于 数据库层面 的实现 > 所谓数据库层面的实现,也就是一个服务内如果同时操作多个位于不同数据库的库表,可以使用这种方式达到事务效果 ##### 2.1.1 两阶段提交(2PC) ##### 两阶段提交又称2PC(two-phase commit protocol),2pc是一个非常经典的 强一致、中心化的原子提交协议.中心化是指协议中有两类节点:一个是中心化协调者节点(Coordinator)和N个参与者节点(Partcipant). ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80NTUwNTMxMw_size_16_color_FFFFFF_t_70] ###### 2.1.1.1 两阶段提交的流程 ###### 一个简易的分布式事务模型如上图所示, 两阶段提交的过程如下. * **第一阶段: 请求/表决阶段** 1. 分布式事务的发起者在向分布式事务协调者(Coordinator)发起请求后,Coordinator分别向参与者节点A,节点B发送事务预处理请求,称为Prepare,其实这个步骤就是询问参与者节点是否能完成事务处理. 2. 参与者节点接收到事务预处理请求就会打开本地数据库事务,开始执行数据库本地事务,但在执行完成后并不会立马提交数据库本地事务,而是向协调者报告是否能够完成事务处理. 3. 如果所有参与者节点都向协调者作了“Vote Commit”反馈, 那么流程就会进入第二个阶段了. * **第二阶段:提交/执行阶段** 1. 如果所有参与者节点都向协调者反馈“可以处理”,此时协调者就会向所有参与者节点发送“全局提交确认通知(global\_commit)”,即参与者都可以进行本地事务提交了.参与者节点完成自身本地数据库事务的提交,并最终将提交结果回复“ack”消息给Coordinator,然后Coordinator就会向调用方返回分布式事务处理完成的结果. 2. 如果有节点反馈“不能处理”的情况发生, 此时参与者节点就会向协调者节点反馈“Vote\_Abort”的消息.分布式事务协调者节点将向所有的参与者节点发起事务回滚的消息(“global\_rollback”),各个参与者节点就会回滚本地事务,释放资源,并且向协调者节点发送“ack”确认消息,协调者节点则向调用方返回分布式事务处理失败的结果. -------------------- ###### 2.1.1.2 两阶段提交协议中的问题 ###### 1. **性能问题** 从流程上可以看出,其最大缺点在于执行过程中间节点都处于阻塞状态.各个操作数据库的节点此时都占用着数据库资源,只有当所有节点准备完毕,事务协调者才会通知进行全局提交,参与者进行本地事务提交后才会释放资源.这样的过程会比较漫长,对性能影响比较大. 2. **协调者单点故障问题** 事务协调者是整个XA模型的核心,一旦事务协调者节点挂掉,会导致参与者收不到提交或回滚的通知,从而导致参与者节点始终处于事务无法完成的中间状态. 3. **丢失消息导致的数据不一致问题** 在第二个阶段,如果发生局部网络问题,一部分事务参与者收到了提交消息,另一部分事务参与者没收到提交消息,那么就会导致节点间数据的不一致问题. -------------------- ##### 2.1.2 三阶段提交(3PC) ##### 三阶段提交又称3PC,它是在两阶段提交的基础上增加了一个CanCommit阶段,并引入了参与者超时机制.一旦事务参与者迟迟没有收到协调者的Commit请求,就会自动进行本地commit,这样相对有效地解决了协调者单点故障的问题. ###### 2.1.2.1 三阶段提交的流程 ###### 基于开始的模型图, 三阶段提交的过程如下. * **第一阶段: CanCommit 阶段** 这个阶段是一种事务询问操作, 事务协调者节点发起询问, 参与者节点进行本地数据库事务检查(比如尝试获取数据库锁),如果参与者节点认为自身可以完成事务就返回“YES”,否则“NO”. * **第二阶段: PreCommit 阶段** 1. 阶段一中所有参与者返回可进行事务处理, 协调者节点发送 PreCommit 请求, 参与者节点进行事务操作,并将Undo和Redo信息记录到事务日志, 但并不提交,而是向协调者反馈是否准备好提交了,等待协调者的下一步指令. 2. 如果阶段一中有任一个参与者节点返回的结果是No响应,或者协调者在等待参与者节点反馈的过程中超时(2PC中只有协调者可以超时,参与者没有超时机制),整个分布式事务就会中断,协调者将向所有参与者发送“abort”请求. * **第三阶段: DoCommit 阶段** 1. 如果阶段二中所有参与者节点都可以进行 PreCommit 提交,协调者就会向所有的参与者节点发送"doCommit"请求,参与者节点在收到提交请求后各自执行事务提交操作,并向协调者节点反馈“Ack”消息,协调者收到所有参与者的Ack消息后完成事务. 2. 如果阶段二中有一个参与者节点未完成 PreCommit 的反馈或者反馈超时,协调者就会向所有的参与者节点发送abort请求,从而中断事务. -------------------- ###### 2.1.2.2 3PC相对于2PC的优化 ###### * **3PC对于协调者和参与者都设置了超时时间,而2PC只有协调者才拥有超时机制.** 这个优化点避免了参与者在长时间无法与协调者节点通讯(协调者挂掉了)的情况下,无法释放资源的问题. 因为参与者自身拥有超时机制后,会在超时后自动进行本地 commit 从而释放资源.这就侧面降低了整个事务的阻塞时间和范围. * **多设置了一个CanCommit 缓冲阶段** 三段设计中, 在第一阶段未造成任何数据库开销时就检查参与者节点的状态,实现了fail-fast机制,一定程度上避免了未经检查直接进行事务操作最后又失败的性能损耗. -------------------- #### 2.2 应用层面的实现 #### ##### 2.2.1 补偿事务(TCC) ##### `TCC(Try-Confirm-Cancel)`又称补偿事务, 是基于 应用层面 的分布式事务的实现, 由应用层进行逻辑控制完成事务操作. ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80NTUwNTMxMw_size_16_color_FFFFFF_t_70 1] > * 核心思想 > 针对每个操作都要注册一个与其对应的确认和补偿(撤销操作), 共分为三个操作: > > 1. Try阶段:主要是对业务系统做检测及资源预留 > 2. Confirm阶段:确认执行业务操作 > 3. Cancel阶段:取消执行业务操作 TCC事务的处理需要通过业务逻辑来实现, 这种分布式事务的实现方式的优缺点如下: > 1. 优点 > 可以让应用自己定义数据库操作的粒度,使得降低锁冲突、提高吞吐量成为可能 > 2. 缺点 > 不足之处则在于对应用的侵入性非常强,业务逻辑的每个分支都需要实现try、confirm、cancel三个操作。此外实现难度也比较大,需要按照网络状态、系统故障等不同的失败原因实现不同的回滚策略 > > -------------------- > > TCC 实现的注意点 > > 1. 幂等问题 > 因为网络调用无法保证请求一定能到达,所以都会有重试机制,因此对于 Try、Confirm、Cancel 三个操作都需要幂等,避免重复执行产生错误 > 2. 空回滚问题 > Try 操作由于网络问题没收到从业务的响应而超时了,此时事务管理器会发出 Cancel 命令,这就需要支持在未执行 Try 的情况下能正常 Cancel > 3. 悬挂问题 > 这个问题也是指 Try 操作由于网络超时触发了事务管理器发出 Cancel 命令,但是如果执行了 Cancel 命令之后 Try 请求到了,对于事务管理器来说这个事务已经是结束了的,这个 Try 操作就被“悬挂”了。所以从业务服务空回滚之后还得记录一下,防止 Try 的再调用 ##### 2.2.2 本地消息表 ##### 本地消息表的核心思想是将分布式事务拆分成本地事务进行处理,保证最终一致性,其基本组成如下图所示: ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80NTUwNTMxMw_size_16_color_FFFFFF_t_70 2] 本地消息表与业务数据表处于同一个数据库中,这样就能利用本地事务来保证在对这两个表的操作满足事务特性,并且使用消息队列来保证最终一致性,其大致的处理流程如下: > 1. 在分布式事务操作的一方完成写业务数据的操作之后向本地消息表插入一条记录,本地事务能保证业务数据操作和插消息的事务特性。不过因为多了一次插入的DB操作,所以性能会有损耗 > 2. 后台任务定时将本地消息表中的消息发送到消息队列中,如果发送成功则将消息从本地消息表记录状态,否则下次重发,这就决定了最终一致性的实现依赖于定时任务的间隔时间 > 3. 分布式事务操作的另一方从消息队列中读取一个消息,并执行消息对应的操作。需注意消费前需要进行消息幂等判断,防止重复消费,业务操作执行完毕后才对 MQ 返回 ACK [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80NTUwNTMxMw_size_16_color_FFFFFF_t_70]: https://img-blog.csdnimg.cn/20190815134437403.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80NTUwNTMxMw==,size_16,color_FFFFFF,t_70 [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80NTUwNTMxMw_size_16_color_FFFFFF_t_70 1]: https://img-blog.csdnimg.cn/20210403122207879.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80NTUwNTMxMw==,size_16,color_FFFFFF,t_70 [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80NTUwNTMxMw_size_16_color_FFFFFF_t_70 2]: https://img-blog.csdnimg.cn/20210403125123924.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80NTUwNTMxMw==,size_16,color_FFFFFF,t_70
还没有评论,来说两句吧...