【分布式事务 001】七种分布式事务(两阶段提交、三阶段提交、TCC编程模型、最终一致性)(好文001)

    科技2022-07-10  134

    文章目录

    一、前言二、强一致性、弱一致性、最终一致性三、从事务到分布式事务3.1 从事务到分布式事务3.2 分布式事务的实现方式3.2.1 两阶段提交 实现分布式事务3.2.2 三阶段提交 实现分布式事务3.2.3 TCC(Try尝试、Confirm确认、Cancel取消)实现分布式事务3.2.4 最大努力通知 实现分布式事务(略,有单独博客)3.2.5 MySQL基于XA 实现分布式事务(略,有单独博客)3.2.6 ebay本地消息表(ebay研发出的)实现分布式事务3.2.7 半消息/最终一致性(RocketMQ)实现分布式事务 四、面试金手指4.1 面试问题:强一致性、弱一致性、最终一致性?4.2 面试问题:解释一下事务和分布式事务?4.3 面试问题:分布式事务七种实现方式?4.3.1 分布式事务:两阶段提交4.3.2 分布式事务:三阶段提交4.3.3 分布式事务: TCC(Try尝试、Confirm确认、Cancel取消)实现分布式事务4.3.4 分布式事务:最大努力通知(单独博客)4.3.5 分布式事务:MySQL基于XA实现分布式事务(单独博客)4.3.6 分布式事务:ebay本地消息表(单独博客)4.3.7 分布式事务:半消息/最终一致性4.3.8 小结:6种分布式事务实现方式(类比学习法,java多线程的原子性操作与分布式事务的原子性操作) 五、小结

    一、前言

    二、强一致性、弱一致性、最终一致性

    附:CAP原则定义?为什么CAP不能同时满足?还是BASE原则?有单独博客,自己脑补,这里只上强一致性、弱一致性、最终一致性。

    强一致性: 定义:当更新操作完成之后,任何多个后续进程或者线程的访问都会返回最新的更新过的值。 优点:这种是对用户最友好的,就是用户上一次写什么,下一次就保证能读到什么。 缺点:根据 CAP 理论,这种实现需要牺牲可用性,如Mysql Oracle自带XA事务。

    弱一致性(弱一致性 仅仅是一个理论,没有实用价值,有实用价值的是特殊的弱一致性,最终一致性): 定义:系统并不保证续进程或者线程的访问都会返回最新的更新过的值。用户读到某一操作对系统特定数据的更新需要一段时间,我们称这段时间为“不一致性窗口”。系统在数据写入成功之后,不承诺立即可以读到最新写入的值,也不会具体的承诺多久之后可以读到。在没有故障发生的前提下,不一致窗口的时间主要受通信延迟,系统负载和复制副本的个数影响。

    最终一致性(弱一致性的一种特例): 定义/特殊性:最终一致性是一种特殊的弱一致性。那么它特殊在哪里?我们说,“弱一致性是系统在数据写入成功之后,不承诺立即可以读到最新写入的值,也不会具体的承诺多久之后可以读到”,最终一致性的特殊在于,系统保证在没有后续更新的前提下,系统最终返回上一次更新操作的值。DNS 是一个典型的最终一致性系统。

    特别注意,数据库的一致性只有唯二两个,强一致性和弱一致性,最终一致性是一种特殊的弱一致性,是弱一致性的一个子集合,不是和强一致性、弱一致性单独并列的第三种一致性。 最终一致性的特殊在于,系统保证在没有后续更新的前提下,系统最终返回上一次更新操作的值。DNS 是一个典型的最终一致性系统。

    三、从事务到分布式事务

    3.1 从事务到分布式事务

    问题:一句话将清楚 “事务和分布式事务”? 标准答案: 定义:事务是保证单数据库的数据一致性,分布式事务是保证多数据库的数据一致性。 实现方式: 前者用mysql自带的事务Transaction就可以实现,用自带的事务Transaction,将要原子化的操作包一层就好; 后者有不同实现方式,可以用主流数据库自带的XA 两段式分布式事务实现(XA强一致性,牺牲一定的可用性、性能、伸缩性),可以自己写sql语句、自己写后端代码来实现(ebay本地消息表、最大努力通知、TCC编程、最终一致性)(非XA最终一致性)。

    事务定义 一般是指要做的或所做的事情。 在计算机术语中是指访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。 事务通常由高级数据库操纵语言或编程语言(如SQL,C++或Java)书写的用户程序用户程序的执行所引起,并用形如begin transaction和end transaction语句(或函数调用)来界定。 事务由事务开始(begin transaction)和事务结束(end transaction)之间执行的全体操作组成。 特性: 事务是恢复和并发控制的基本单位。 事务应该具有4个属性:原子性、一致性、隔离性、持久性。这四个属性通常称为ACID特性。 原子性(atomicity):一个事务是一个不可分割的工作单位,事务中包括的操作要么都做,要么都不做。 一致性(consistency):事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。 隔离性(isolation):一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。 持久性(durability):持久性也称永久性(permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。 金手指:事务就是一系列操作,要么同时成功,要么同时失败。事务的 ACID 特性(原子性、一致性、隔离性、持久性)为了保证一系列操作可以正常执行。

    大家可以想一下,你下单流程可能涉及到10多个环节,你下单付钱都成功了,但是你优惠券扣减失败了,积分新增失败了,前者公司会被薅羊毛,后者用户会不开心,但是这些都在不同的服务怎么保证大家都成功呢? 回答:分布式事务(一个业务的一组服务要么同时成功,要么同时失败,分布式事务;一个服务中的操作update insert,要么同时成功,要么同时失败,数据库单机事务)。

    分布式事务定义: 分布式事务是指会涉及到操作多个数据库的事务,在分布式系统中,各个节点之间在物理上相互独立,通过网络进行沟通和协调。 常见的分布式事务大概分为(七种): 1、2pc(两段式提交) 2、3pc(三段式提交) 3、TCC(Try、Confirm、Cancel) 4、最大努力通知 5、XA 6、本地消息表(ebay研发出的) 7、半消息/最终一致性(RocketMQ)

    分布式事务的优点:保证分布式部署情况下,一个业务的一组后端服务要么同时成功,要么同时失败。 分布式事务的缺点: 1、并发效率低:长时间锁定数据库资源,导致系统的响应不快,并发上不去。 2、网络抖动出现脑裂问题,导致事物参与者,不能很好地执行协调者的指令,导致数据不一致。 3、单点故障:例如事物协调者,在某一时刻宕机,虽然可以通过选举机制产生新的Leader,但是这过程中,必然出现问题,而TCC,只有强悍的技术团队,才能支持开发,成本太高。

    金手指:从事务到分布式事务(重点) 事务, 对于单体架构,仅保证一个数据库数据一致性,是保证一个服务中若个接口原子性操作,要么一起执行,要么一起不执行; 分布式事务,对于分布式/微服务架构,保证多个数据库数据一致性,是保证多个服务间的若干接口原子性操作,要么一起执行,要么一起不执行; 所以,分布式事务是跨服务间的,从单体架构到微服务架构,要保证需要业务的上的原子性,就从事务到了分布式事务。

    3.2 分布式事务的实现方式

    3.2.1 两阶段提交 实现分布式事务

    两阶段提交 解释上图:在两个系统操作事务的时候都锁定资源但是都不提交事务,一定要消息队列接收到两个系统都锁定好了资源,然后再分别提交事务。

    定义:二阶段提交(Two-phaseCommit)是指,在计算机网络以及数据库领域内,为了使基于分布式系统架构下的所有节点在进行事务提交时保持一致性而设计的一种算法(Algorithm)。通常,二阶段提交也被称为是一种协议(Protocol))。 本质和原理和流程: 本质:通过消息中间件协调多个系统;

    二阶段提交的两类参与者(各个节点 + 协调者): 1、各个节点:在分布式系统中,每个节点虽然可以知晓自己的操作时成功或者失败,却无法知道其他节点的操作的成功或失败。 2、协调者:当一个事务跨越多个节点时,这个事务就是一个分布式事务,为了保持这个分布式事务的 ACID 特性,需要引入一个作为协调者的组件来统一掌控所有节点(称作参与者)的操作结果并最终指示这些节点是否要把操作结果进行真正的提交(比如将更新后的数据写入磁盘等等)。

    两阶段提交的两个阶段 两阶段提交作为一种算法/协议,算法思路可以概括为:第一步,参与者将自己的操作成败通知协调者,第二步,由协调者根据所有参与者的反馈情报决定各参与者是否要提交操作还是中止操作。 1、准备阶段(协调者给每一个参与者发送Prepare消息) 事务协调者(事务管理器)给每个参与者(资源管理器)发送 Prepare 消息,每个参与者要么直接返回失败(如权限验证失败),要么在本地执行事务,写本地的 redo 和 undo 日志,但不提交,到达一种“万事俱备,只欠东风”的状态。 2、提交阶段(协调者给每一个参与者发送rollback/commit消息) 如果协调者收到了其中任何一个参与者的失败消息或者超时,直接给所有参与者发送回滚(Rollback)消息; 如果协调者没有收到其中任何一个参与者的失败消息或者超时,则给所有参与者发送提交(Commit)消息; 所有参与者根据协调者的指令执行提交或者回滚操作,完事之后,释放所有事务处理过程中使用的锁资源。(注意:必须在最后阶段释放锁资源)

    优点:在锁定资源过程中,不会因为一个系统锁定资源失败而导致提交失败:只要有一个没有系统没有锁定资源,根本就不会触发提交。 缺点:在提交过程中,存在多个系统中一个系统的事务提交失败的隐患:如果A系统事务提交成功了,但是B系统在提交的时候网络波动或者各种原因提交失败了,其实还是会失败的。 缺点1:参与者:同步阻塞问题 执行过程中,所有参与节点都是事务阻塞型的。 缺点2:协调者:单点故障隐患 由于协调者的重要性,一旦协调者发生故障。参与者会一直阻塞下去。 缺点3:参与者:数据不一致 (脑裂问题) 在二阶段提交的阶段二中,当协调者向参与者发送 commit 请求之后,发生了局部网络异常或者在发送 commit 请求过程中协调者发生了故障,导致只有一部分参与者接受到了commit 请求。于是整个分布式系统便出现了数据不一致性的现象(脑裂现象)。 缺点4:协调者:二阶段无法解决的问题 (数据状态不确定) 在二阶段提交的阶段二中,协调者发出 commit 消息之后宕机,而唯一接收到这条消息的参与者同时也宕机了。那么即使协调者通过选举协议产生了新的协调者,这条事务的状态也是不确定的,没人知道事务是否被已经提交(因为之前的协调者和唯一的接收者都宕机了,其内存空间的数据丢失了)。 小结:将事务分为锁定资源和提交两个阶段,一定要所有系统都锁定完资源后才提交,所以命名为两阶段提交,在锁定资源过程中,保证了分布式事务要么一起成功,要么一起失败,但是,在提交过程中,没有保证。

    3.2.2 三阶段提交 实现分布式事务

    三 阶 段 提 交 ( Three-phase commit ) , 也 叫 三 阶 段 提 交 协 议 ( Three-phase commit protocol),是二阶段提交(2PC)的改进版本。

    与两阶段提交不同的是,三阶段提交有两个改动点。 1、引入超时机制。同时在协调者和参与者中都引入超时机制。 2、在第一阶段和第二阶段中插入一个准备阶段。保证了在最后提交阶段之前各参与节点的状态是一致的,即都是yes。 小结:一是引入超时机制,二是 3PC 把 2PC 的准备阶段再次一分为二,这样三阶段提交就有 CanCommit、PreCommit、DoCommit 三个阶段。

    三阶段提交过程 1.CanCommit阶段(协调者主动给每一个参与者发送commit请求,和二阶段提交发送prepare请求是一样的,对应两阶段提交的阶段一前半段) 3PC的CanCommit阶段其实和2PC的准备阶段很像。协调者向参与者发送commit请求,参与者如果可以提交就返回Yes响应,否则返回No响应。 2.PreCommit阶段(协调者收到所有的参与者的请求,超时视为参与者返回No,这是参与者的超时机制的使用,对应两阶段提交的阶段一后半段) Coordinator根据Cohort的反应情况来决定是否可以继续事务的PreCommit操作。 根据响应情况,有以下两种可能。 A.假如Coordinator从所有的Cohort获得的反馈都是Yes响应,那么就会进行事务的预执行: a. 发送预提交请求。Coordinator向Cohort发送PreCommit请求,并进入Prepared阶段。 b. 事务预提交。Cohort接收到PreCommit请求后,会执行事务操作,并将undo和redo信息记录到事务日志中。 c. 响应反馈。如果Cohort成功的执行了事务操作,则返回ACK响应,同时开始等待最终指令。 B.假如有任何一个Cohort向Coordinator发送了No响应,或者等待超时之后,Coordinator都没有接到Cohort的响应,那么就中断事务: a. 发送中断请求。Coordinator向所有Cohort发送abort请求。 b. 中断事务。Cohort收到来自Coordinator的abort请求之后(或超时之后,仍未收到Cohort的请求),执行事务的中断。 3.DoCommit阶段(协调者主动给所以参与发送rollback/commit消息,对应两阶段提交的阶段二) 该阶段进行真正的事务提交,也可以分为以下两种情况: 第一种,执行提交 A.协调者发送提交请求。Coordinator接收到Cohort发送的ACK响应,那么他将从预提交状态进入到提交状态。并向所有Cohort发送doCommit请求。 B.参与者提交事务。Cohort接收到doCommit请求之后,执行正式的事务提交。并在完成事务提交之后释放所有事务资源。 C.参与者响应反馈。事务提交完之后,向协调者Coordinator发送ACK响应,告诉协调者自己提交完了。 D.协调者确定完成事务。Coordinator接收到所有Cohort的ACK响应之后,完成事务。 第二种,中断事务 Coordinator没有接收到Cohort发送的ACK响应(可能是接受者发送的不是ACK响应,也可能响应超时),那么就会执行中断事务。

    为什么三阶段提交解决了两阶段提交的问题?(参与者协调者的超时机制 + 分割准备阶段) 第一,对于协调者(Coordinator)和参与者(Cohort)都设置了超时机制(在2PC中,只有协调者拥有超时机制,即如果在一定时间内没有收到cohort的消息则默认失败)。 第二,在2PC的准备阶段和提交阶段之间,插入预提交阶段,使3PC拥有CanCommit、PreCommit、DoCommit三个阶段。新插入的PreCommit是一个缓冲,它的作用是,保证了在最后提交阶段之前各参与节点的状态是一致的。

    三阶段提交是“非阻塞”协议(因为参与者加上了超时时间,过时不候)。 三阶段提交在两阶段提交的第一阶段与第二阶段之间插入了一个准备阶段,使得原先在两阶段提交中,参与者在投票之后,由于协调者发生崩溃或错误(即第二个单点故障问题),而导致参与者处于无法知晓是否提交或者中止的“不确定状态”所产生的可能相当长的延时的问题得以解决。 解释(三阶段提交处理协调者单点故障):假设有一个决策小组由一个主持人负责与多位组员以电话联络方式协调是否通过一个提案, 以两阶段提交来说,主持人(协调者)收到一个提案请求,打电话跟每个组员询问是否通过并统计回复(第一阶段),然后将最后决定打电话通知各组员(第二阶段)。 要是主持人在跟第一位组员通完电话后失忆(协调者单点故障,协调者内存中数据丢失),而第一位组员在得知结果并执行(执行可能是rollback可能是commit)后老人痴呆(唯一收到消息的参与者宕机,内存中数据丢失),那么即使重新选出主持人,也没人知道最后的提案决定是什么(两个内存数据都没了),也许是通过,也许是驳回,不管大家选择哪一种决定,都有可能与第一位组员已执行过的真实决定不一致(第一个组员已经提交了,再也没有办法实现分布式的一致性),老板就会不开心认为决策小组沟通有问题而解雇。 三阶段提交即是引入了另一个步骤,主持人打电话跟组员通知请准备通过提案,以避免没人知道真实决定而造成决定不一致的失业危机。 为什么能够解决二阶段提交的问题呢? 回到刚刚提到的状况, 第二阶段通知完第一组员两人一起宕机(没关系,第一组员没有提交):在主持人通知完第一位组员请准备通过后两人意外失忆,即使没人知道全体在第一阶段的决定为何,全体决策组员仍可以重新协调过程或直接否决(和上面二阶段区别,此时第一组员还没有提交),所以,不会有不一致决定而失业。 第二阶段通知完所有组员两人一起宕机(没关系,提案是否通过大家都知道了):那么当主持人通知完全体组员请准备通过并得到大家的再次确定后进入第三阶段,当主持人通知第一位组员请通过提案后两人意外失忆,这时候其他组员再重新选出主持人后,其他未宕机的组员仍可以知道目前至少是处于准备通过提案阶段,表示第一阶段大家都已经决定要通过了,此时便可以直接通过。

    黄金金手指1:三阶段提交的两个优点: 1、引入参与者过时机制,在第二阶段,如果参与者过时,协调者就认为他失败(不要让协调者老是等着这个参与者,而其他所有参与者又等着这个协调者),第三阶段向所有参与者发送rollback请求。 2、两阶段是每个人收到了commit/rollback就自己执行,不管别人,但是三阶段是每个人收到了commit/rollback记录到redo/undo日志中,保存下来,再次和协调者确认一次,第三阶段才提交,避免第四个问题数据状态丢失问题。 黄金金手指2:三阶段解决了哪些问题 1、解决了参与的同步阻塞问题,给它加一个超时时间,协调者过期不候,情景:一般是第二阶段参与者老是不发送ACK,影响第三阶段; 2、解决了协调者单点故障问题,给协调者加一个超时时间,参与者过期不候,这个两阶段也可以做,情景:一般是协调者第二阶段执行期间宕机,然后第三阶段有参与者收不到 commit/rollback ,任务协调者宕机; 3、解决了数据不一致 (脑裂问题),加了一个PreCommit,一定要PreCommit都返回ACK,才会第三阶段提交,不会再有这个问题。 4、解决了因为协调者和唯一参与者宕机的数据丢失问题,分为两种,如果第二阶段通知完唯一的参与者后,两者宕机,其他的参与者在第三阶段无法收到commit/rollback,超时不再等待协调者,任务协调者失败(这是协调者超时时间的使用),重新选举协调者,从阶段一再次开始;如果第二阶段结束后两者宕机,没关系,继续第三阶段,此时大家都已经决定要通过了,其余没宕机的可以直接提交。

    三阶段提交的缺陷:如果进入第二阶段PreCommit后,如果协调者Coordinator发出的是abort请求,假设只有一个Cohort收到并进行了abort操作,而其他对于系统状态未知的Cohort会根据3PC选择继续Commit,此时系统状态发生不一致性。

    3.2.3 TCC(Try尝试、Confirm确认、Cancel取消)实现分布式事务

    再次重复:实现分布式就是实现多个服务提交到多个数据库,要么一起提交,要么一起取消。

    TCC 型事务(Try/Confirm/Cancel)可以归为补偿型。

    WS-BusinessActivity 提供了一种基于补偿的 long-running 的事务处理模型。

    TCC实现分布式事务流程(服务器A和服务器B,要么一起执行,要么一起取消) 第一步,try尝试:服务器 A 发起事务,服务器 B 参与事务,服务器 A 的事务如果执行顺利,那么事务 A 就先行提交; 第二步,confirm确认:如果事务 B 也执行顺利,则事务 B 也提交,整个事务就算完成; 第三步,cancel取消:但是如果事务 B 执行失败,事务 B 本身回滚,这时事务 A 已经被提交,所以需要执行一个补偿操作,将已经提交的事务 A 执行的操作作反操作(这种回滚是通过undo日志来实现的,《MySQL 日志系统》),恢复到未执行前事务 A 的状态。

    这样的 SAGA 事务模型,是牺牲了一定的隔离性和一致性的,但是提高了 long-running 事务的可用性。

    3.2.4 最大努力通知 实现分布式事务(略,有单独博客)

    再次重复:实现分布式就是实现多个服务提交到多个数据库,要么一起提交,要么一起取消。

    最大努力通知是分布式事务中实时性要求最低的一种, 也可以通过消息队列实现, 与前面异步确保型操作不同的一点是, 在消息由 MQ Server 投递到消费者之后, 允许在达到最大重试次数之后正常结束事务。

    3.2.5 MySQL基于XA 实现分布式事务(略,有单独博客)

    两段式事务也就是著名的XA事务,XA是由X/Open组织提出的分布式事务的规范,也是目前使用最为广泛的多数据库分布式事务规范。 一般情况下,我们在使用XA规范编写多数据库分布式事务代码时,不用自己去实现两段提交代码,而是使用atomikos等开源的分布式事务工具。 下面是一个使用atomikos实现简单分布式事务(XA事务)的源码: github.com/liangyanghe/xa-transaction-demo

    XA定义:XA 就是 X/Open DTP 定义的交易中间件与数据库之间的接口规范(即接口函数),交易中间件用它来通知数据库事务的开始、结束以及提交、回滚等。 XA 接口函数由数据库厂商提供。

    3.2.6 ebay本地消息表(ebay研发出的)实现分布式事务

    略,有单独博客。

    3.2.7 半消息/最终一致性(RocketMQ)实现分布式事务

    半消息/最终一致性(RocketMQ) 最终一致性: 对于上图的解释(业务主动方+业务被动方): 第一,如果业务主动方本地事务提交失败(即订单系统执行成功),业务被动方不会收到消息的投递。 第二,只要业务主动方本地事务执行成功(即订单系统执行失败),那么消息服务一定会投递消息给下游的业务被动方,并最终保证业务被动方一定能成功消费该消息(消费成功或失败,即最终一定会有一个最终态)。

    关于RocketMQ对TCC的性能改进,实现核心功能TCC编程模型,非核心功能 消息队列+定时任务,待核心功能执行完成之后再执行,有单独博客《分布式事务的五大实现》

    四、面试金手指

    4.1 面试问题:强一致性、弱一致性、最终一致性?

    强一致性: 定义:当更新操作完成之后,任何多个后续进程或者线程的访问都会返回最新的更新过的值。 优点:这种是对用户最友好的,就是用户上一次写什么,下一次就保证能读到什么。 缺点:根据 CAP 理论,这种实现需要牺牲可用性,如Mysql Oracle自带XA事务。

    弱一致性(弱一致性 仅仅是一个理论,没有实用价值,有实用价值的是特殊的弱一致性,最终一致性): 定义:系统并不保证续进程或者线程的访问都会返回最新的更新过的值。用户读到某一操作对系统特定数据的更新需要一段时间,我们称这段时间为“不一致性窗口”。系统在数据写入成功之后,不承诺立即可以读到最新写入的值,也不会具体的承诺多久之后可以读到。在没有故障发生的前提下,不一致窗口的时间主要受通信延迟,系统负载和复制副本的个数影响。

    最终一致性(弱一致性的一种特例): 定义/特殊性:最终一致性是一种特殊的弱一致性。那么它特殊在哪里?我们说,“弱一致性是系统在数据写入成功之后,不承诺立即可以读到最新写入的值,也不会具体的承诺多久之后可以读到”,最终一致性的特殊在于,系统保证在没有后续更新的前提下,系统最终返回上一次更新操作的值。DNS 是一个典型的最终一致性系统。

    特别注意,数据库的一致性只有唯二两个,强一致性和弱一致性,最终一致性是一种特殊的弱一致性,是弱一致性的一个子集合,不是和强一致性、弱一致性单独并列的第三种一致性。 最终一致性的特殊在于,系统保证在没有后续更新的前提下,系统最终返回上一次更新操作的值。DNS 是一个典型的最终一致性系统。

    4.2 面试问题:解释一下事务和分布式事务?

    问题:一句话将清楚 “事务和分布式事务”? 标准答案: 定义:事务是保证单数据库的数据一致性,分布式事务是保证多数据库的数据一致性。 实现方式: 前者用mysql自带的事务Transaction就可以实现,用自带的事务Transaction,将要原子化的操作包一层就好; 后者有不同实现方式,可以用主流数据库自带的XA 两段式分布式事务实现(XA强一致性,牺牲一定的可用性、性能、伸缩性),可以自己写sql语句、自己写后端代码来实现(ebay本地消息表、最大努力通知、TCC编程、最终一致性)(非XA最终一致性)。

    4.3 面试问题:分布式事务七种实现方式?

    因为用了消息队列让分布式数据不一致问题暴露得比较严重一点 数据不一致是分布式服务本身就存在的一个问题,并不是消息队列导致了数据不一致,只是因为使用了消息队列让分布式数据不一致问题暴露得比较严重一点(类似java多线程,使用Thread.sleep(1000L),并不是导致了线程安全问题,只是让线程安全问题暴露的更加明显)(Redis也存在这个数据更新和数据一致性问题,在《高并发-缓存》博客中讲到过,就是缓存数据和mysql中的数据)。

    情景问题:比如,下单的服务自己保证自己的逻辑成功处理了,你成功发了消息,但是优惠券系统,积分系统等等这么多系统,他们成功还是失败你就不管了?所有的服务都成功才能算这一次下单是成功的,那怎么才能保证数据一致性呢? 解决方式:分布式事务。使用分布式事务,将下单,优惠券,积分。。。都放在一个事务里面一样,要成功一起成功,要失败一起失败。

    分布式事务的优点:保证分布式部署情况下,一个业务的一组后端服务要么同时成功,要么同时失败。 分布式事务的缺点: 1、并发效率低:长时间锁定数据库资源,导致系统的响应不快,并发上不去。 2、网络抖动出现脑裂问题,导致事物参与者,不能很好地执行协调者的指令,导致数据不一致。 3、单点故障:例如事物协调者,在某一时刻宕机,虽然可以通过选举机制产生新的Leader,但是这过程中,必然出现问题,而TCC,只有强悍的技术团队,才能支持开发,成本太高。

    金手指:从事务到分布式事务(重点) 事务, 对于单体架构,仅保证一个数据库数据一致性,是保证一个服务中若个接口原子性操作,要么一起执行,要么一起不执行; 分布式事务,对于分布式/微服务架构,保证多个数据库数据一致性,是保证多个服务间的若干接口原子性操作,要么一起执行,要么一起不执行; 所以,分布式事务是跨服务间的,从单体架构到微服务架构,要保证需要业务的上的原子性,就从事务到了分布式事务。

    4.3.1 分布式事务:两阶段提交

    二阶段提交的两类参与者(各个节点 + 协调者): 1、各个节点:在分布式系统中,每个节点虽然可以知晓自己的操作时成功或者失败,却无法知道其他节点的操作的成功或失败。 2、协调者:当一个事务跨越多个节点时,这个事务就是一个分布式事务,为了保持这个分布式事务的 ACID 特性,需要引入一个作为协调者的组件来统一掌控所有节点(称作参与者)的操作结果并最终指示这些节点是否要把操作结果进行真正的提交(比如将更新后的数据写入磁盘等等)。

    两阶段提交的两个阶段 两阶段提交作为一种算法/协议,算法思路可以概括为:第一步,参与者将自己的操作成败通知协调者,第二步,由协调者根据所有参与者的反馈情报决定各参与者是否要提交操作还是中止操作。 1、准备阶段(协调者给每一个参与者发送Prepare消息) 事务协调者(事务管理器)给每个参与者(资源管理器)发送 Prepare 消息,每个参与者要么直接返回失败(如权限验证失败),要么在本地执行事务,写本地的 redo 和 undo 日志,但不提交,到达一种“万事俱备,只欠东风”的状态。 2、提交阶段(协调者给每一个参与者发送rollback/commit消息) 如果协调者收到了其中任何一个参与者的失败消息或者超时,直接给所有参与者发送回滚(Rollback)消息; 如果协调者没有收到其中任何一个参与者的失败消息或者超时,则给所有参与者发送提交(Commit)消息; 所有参与者根据协调者的指令执行提交或者回滚操作,完事之后,释放所有事务处理过程中使用的锁资源。(注意:必须在最后阶段释放锁资源)

    优点:在锁定资源过程中,不会因为一个系统锁定资源失败而导致提交失败:只要有一个没有系统没有锁定资源,根本就不会触发提交。 缺点:在提交过程中,存在多个系统中一个系统的事务提交失败的隐患:如果A系统事务提交成功了,但是B系统在提交的时候网络波动或者各种原因提交失败了,其实还是会失败的。 缺点1:参与者:同步阻塞问题 执行过程中,所有参与节点都是事务阻塞型的。 缺点2:协调者:单点故障隐患 由于协调者的重要性,一旦协调者发生故障。参与者会一直阻塞下去。 缺点3:参与者:数据不一致 (脑裂问题) 在二阶段提交的阶段二中,当协调者向参与者发送 commit 请求之后,发生了局部网络异常或者在发送 commit 请求过程中协调者发生了故障,导致只有一部分参与者接受到了commit 请求。于是整个分布式系统便出现了数据不一致性的现象(脑裂现象)。 缺点4:协调者:二阶段无法解决的问题 (数据状态不确定) 在二阶段提交的阶段二中,协调者发出 commit 消息之后宕机,而唯一接收到这条消息的参与者同时也宕机了。那么即使协调者通过选举协议产生了新的协调者,这条事务的状态也是不确定的,没人知道事务是否被已经提交(因为之前的协调者和唯一的接收者都宕机了,其内存空间的数据丢失了)。 小结:将事务分为锁定资源和提交两个阶段,一定要所有系统都锁定完资源后才提交,所以命名为两阶段提交,在锁定资源过程中,保证了分布式事务要么一起成功,要么一起失败,但是,在提交过程中,没有保证。

    4.3.2 分布式事务:三阶段提交

    黄金金手指:三阶段提交的两个优点: 1、引入参与者过时机制,在第二阶段,如果参与者过时,协调者就认为他失败(不要让协调者老是等着这个参与者,而其他所有参与者又等着这个协调者),第三阶段像所有参与者发送rollback请求。 2、两阶段是每个人收到了commit/rollback就自己执行,不管别人,但是三阶段是每个人收到了commit/rollback记录到redo/undo日志中,保存下来,再次和协调者确认一次,第三阶段才提交,避免第四个问题数据状态丢失问题。 黄金金手指:三阶段解决了哪些问题 1、解决了参与的同步阻塞问题,给它加一个超时时间,协调者过期不候,情景:一般是第二阶段参与者老是不发送ACK,影响第三阶段; 2、解决了协调者单点故障问题,给协调者加一个超时时间,参与者过期不候,这个两阶段也可以做,情景:一般是协调者第二阶段执行期间宕机,然后第三阶段有参与者收不到 commit/rollback ,任务协调者宕机; 3、解决了数据不一致 (脑裂问题),加了一个PreCommit,一定要PreCommit都返回ACK,才会第三阶段提交,不会再有这个问题。 4、解决了因为协调者和唯一参与者宕机的数据丢失问题,分为两种,如果第二阶段通知完唯一的参与者后,两者宕机,其他的参与者在第三阶段无法收到commit/rollback,超时不再等待协调者,任务协调者失败(这是协调者超时时间的使用),重新选举协调者,从阶段一再次开始;如果第二阶段结束后两者宕机,没关系,继续第三阶段,此时大家都已经决定要通过了,其余没宕机的可以直接提交。

    三阶段提交的缺陷:如果进入第二阶段PreCommit后,如果协调者Coordinator发出的是abort请求,假设只有一个Cohort收到并进行了abort操作,而其他对于系统状态未知的Cohort会根据3PC选择继续Commit,此时系统状态发生不一致性。

    4.3.3 分布式事务: TCC(Try尝试、Confirm确认、Cancel取消)实现分布式事务

    再次重复:实现分布式就是实现多个服务提交到多个数据库,要么一起提交,要么一起取消。

    TCC实现分布式事务流程(服务器A和服务器B,要么一起执行,要么一起取消) 第一步,try尝试:服务器 A 发起事务,服务器 B 参与事务,服务器 A 的事务如果执行顺利,那么事务 A 就先行提交; 第二步,confirm确认:如果事务 B 也执行顺利,则事务 B 也提交,整个事务就算完成; 第三步,cancel取消:但是如果事务 B 执行失败,事务 B 本身回滚,这时事务 A 已经被提交,所以需要执行一个补偿操作,将已经提交的事务 A 执行的操作作反操作(这种回滚是通过undo日志来实现的,《MySQL 日志系统》),恢复到未执行前事务 A 的状态。

    4.3.4 分布式事务:最大努力通知(单独博客)

    4.3.5 分布式事务:MySQL基于XA实现分布式事务(单独博客)

    4.3.6 分布式事务:ebay本地消息表(单独博客)

    4.3.7 分布式事务:半消息/最终一致性

    4.3.8 小结:6种分布式事务实现方式(类比学习法,java多线程的原子性操作与分布式事务的原子性操作)

    再次重复:实现分布式就是实现多个服务提交到多个数据库,要么一起提交,要么一起取消。

    类比java多线程 两阶段提交和三阶段提交就是类似synchronized和lock包裹着要执行的代码,一定要原子性执行。 TCC的回滚操作是依赖于MySQL日志系统实现的,java多线程没有日志系统,无法提供回滚机制的支持,没有没有对应的。 最大努力通知类似于java种cas非阻塞同步,在硬件层面原子化操作,不断尝试,直到成功。

    五、小结

    七种分布式事务,完成了。

    天天打码,天天进步!!

    Processed: 0.021, SQL: 8