分布式事务

分布式事务

    概要

    随着业务规模不断扩大,单台服务器或单个数据库可能无法满足系统的性能和业务需求,因此系统通常会采用分布式架构,将不同的业务模块拆分到不同的服务中,甚至部署在不同的服务器和数据库上。

    在这种架构下,一个完整的业务操作可能需要跨越多个服务或数据库。例如,用户下单时,可能涉及订单服务创建订单、库存服务扣减库存、账户服务扣减余额等操作。

   由于这些操作分布在不同的服务和数据库中,传统的数据库本地事务只能保证单个数据库内部的数据一致性,无法直接保证整个业务流程中多个服务的数据一致性。因此,我们需要引入分布式事务,协调不同服务或数据库之间的事务操作,保证业务数据的一致性。

   目前,分布式事务有多种实现方案,其中比较经典的是基于 XA 协议的两阶段提交(2PC)。 

   一、XA事务

   1、什么是XA?

   XA是由X/Open组织提出的分布式事务的规范。 XA规范主要定义了(全局)事务管理器(TM)和(局 部)资源管理器(RM)之间的接口。主流的关系型数据库产品都是实现了XA接口的。

   2、XA事务的组成

   XA事务由一个或多个资源管理器、一个事务管理器以及一个应用程序组成。

   资源管理器(Resource Managers):提供访问事务资源的方法。通常一个数据库就是一个资源管理器。

   事务管理器(Transaction Manager):协调参与全局事务中的各个事务。需要和参与全局事务的所有资源管理器进行通信。

   应用程序(Application Program):定义事务的边界,指定全局事务中的操作。

   XA协议包含两阶段提交(2PC)和三阶段提交(3PC)两种实现,后面重点介绍两阶段提交的具体过程。

   二、两阶段提交(two-phase commit)

   1.  两阶段提交的实现过程

   第一阶段为准备(prepare)阶段。

   所有参与全局事务的节点都开始准备(PREPARE)并锁住需要的资源,告诉事务管理器它们准备好提交了。

   2. 第二阶段为提交阶段(commit)。

   事务管理器告诉资源管理器执行ROLLBACK还是COMMIT。如果任何一个节点显示不能提交,则所有的节点都被告知需要回滚。可见与本地事务不同的是,分布式事务需要多一次的PREPARE操作,待收到所有节点的同意信息后,再进行COMMIT或者ROLLBACK操作。

   3. XA两阶段提交成功情况的处理流程

   第一阶段(prepare phase):

     

   1)作为事务协调者的节点会首先向所有的参与者节点发送Prepare请求。

   2)在接到Prepare请求之后,每一个参与者节点会各自执行与事务有关的数据更新,写入Undo Log和Redo Log。如果参与者执行成功,暂时不提交事务,而是向事务协调节点返回“完成”消息。

   3)当事务协调者接收到了所有参与者的返回消息,整个分布式事务将会进入第二阶段。

   第二阶段(commit phase):

     

   如果事务协调节点在之前所收到都是正向返回,那么它将会向所有事务参与者发出Commit请求。接到Commit请求之后,事务参与者节点会各自进行本地的事务提交,并释放锁资源。当本地事务完成提交后,将会向事务协调者返回“完成”消息。并释放事务处理过程中使用的锁资源。

   当事务协调者接收到所有事务参与者的“完成”反馈,整个分布式事务完成。

   4. XA两阶段提交失败情况的处理流程 

   第一阶段

     

   如果某个事务参与者反馈失败消息,说明该节点的本地事务执行不成功,必须回滚。

   第二阶段

      

    事务协调节点向所有的事务参与者发送Abort请求。接收到Abort请求之后,各个事务参与者节点需要在本地进行事务的回滚操作,回滚操作依照Undo Log来进行。并释放事务处理过程中使用的锁资源。

    5. XA两阶段提交的不足

    XA 两阶段提交虽然能够保证分布式事务的强一致性,但也存在一些明显的缺点:

    1)性能问题

    在 XA 协议中,事务参与者在第一阶段完成准备后,需要等待协调者的最终决策。在此期间,数据库资源通常需要持续锁定,直到第二阶段提交或回滚完成。这种方式会增加资源占用时间,降低系统并发能力,尤其在网络延迟较高或参与者较多的场景下,性能影响更加明显。

    2)协调者单点故障问题

   事务协调者负责协调所有参与者执行提交或回滚操作。如果协调者在事务执行过程中发生故障,参与者可能无法及时收到最终决策,从而处于阻塞状态,等待协调者恢复或执行事务恢复流程。

    3)可用性问题

   在第二阶段,如果提交或回滚消息因网络故障而丢失,部分参与者可能已经完成事务,其他参与者却仍处于等待状态。

   虽然 XA 可以通过事务日志和恢复机制处理这类问题,但在故障恢复期间,事务参与者可能无法及时释放资源,影响系统的可用性。

   总结:XA 两阶段提交通过协调者统一管理事务参与者,实现了分布式事务的强一致性,但也带来了资源锁定时间长、协调者故障影响事务执行、故障恢复复杂等问题,因此在高并发、对系统可用性要求较高的场景下存在一定局限性。

   三、其它分布式事务方案

   如何避免XA两阶段提交的种种问题呢?实际上有许多其他的分布式事务方案可供选择:

   1. 三阶段提交(three-phase commit)

      在两阶段提交中,第一个阶段是询问所有参与者是否可以提交事务。在 XA 三阶段提交中,这一阶段被拆分成 CanCommit 阶段和 PreCommit 阶段。

   1)CanCommit 阶段

   协调者向所有参与者发送 CanCommit 消息,询问它们是否可以提交事务。参与者在收到 CanCommit 消息后,会执行一些检查,确保自己的事务可以提交。如果检查通过,参与者会回复 "Yes",表示可以提交;否则,回复 "No",表示不能提交。 

  • 如果在 CanCommit 阶段协调者没有在规定的时间内收到所有参与者的反馈,协调者会启动超时处理。
  • 协调者可以根据具体的实现,在超时后执行不同的动作,例如中断事务、选择性地中断一部分事务等。

    2)PreCommit 阶段 

    如果 CanCommit 阶段的所有参与者都回复 "Yes",那么协调者向所有参与者发送 PreCommit 消息,告诉它们准备好提交事务。参与者在收到 PreCommit 消息后,会在本地执行事务的预提交操作,但不会真正提交。预提交操作可能包括写日志、锁定资源等。 

  • 如果在 PreCommit 阶段发生了超时,协调者可能会认为参与者无法正常提交事务,因此协调者会发送中断事务的请求。
  • 参与者在收到中断请求后,需要进行相应的回滚操作。

   3)DoCommit 阶段和DoAbort 阶段

   如果在 PreCommit 阶段没有发生错误,协调者向所有参与者发送 DoCommit 消息,表示可以最终提交事务。

   如果在 CanCommit 或 PreCommit 阶段的任何时候有参与者回复 "No",或者在规定时间内没有收到某个参与者的回复,协调者会发送 DoAbort 消息,表示事务中止。
   参与者在收到 DoCommit 或 DoAbort 消息后,根据消息执行最终的提交或回滚操作。

  分析:XA三阶段提交在两阶段提交的基础上增加了CanCommit阶段,并且引入了超时机制。一旦事务参与者迟迟没有接到协调者的commit请求,会自动进行本地commit。这样有效解决了协调者单点故障的问题。但是性能问题和不一致的问题仍然没有根本解决。

   2. MQ事务

   本地消息表/可靠消息最终一致性。利用消息中间件来异步完成事务的后一半更新,实现系统的最终一致性。这个方式避免了像XA协议那样的性能问题。

   举个栗子:

   跨行转账可通过本地消息表来实现。

   用户 A 向用户 B 发起转账,首先系统会扣掉用户 A 账户中的金额,将该转账消息写入消息表中,如果事务执行失败则转账失败,如果转账成功,系统中会有定时轮询消息表,往 mq 中写入转账消息,失败重试。mq 消息会被实时消费并往用户 B 中账户增加转账金额,执行失败会不断重试。

      

   商城用户订单数据状态变更,会将变更状态记录消息表中,脚本将订单状态消息写入 mq,最终消费 mq 给用户发送邮件、短信、push 等。

   3. TCC事务

   TCC事务是Try、Commit、Cancel三种指令的缩写,其逻辑模式类似于XA两阶段提交,但是实现方式是在代码层面来人为实现。

   1)Try 阶段

   每个参与者执行一些预备工作,如资源的保留等,但不做实际的业务处理。

   2)Confirm 阶段

   所有参与者都执行正式的业务操作,将资源的状态正式提交,通常是数据的持久化。

   3)Cancel 阶段

   如果事务执行过程中出现了问题,或者某些参与者不能完成 Confirm 阶段,就会调用 Cancel 阶段来回滚操作,撤销 Try 阶段的操作。

   在 Cancel 阶段,每个参与者应该撤销 Try 阶段的操作,也就是执行取消的操作,以保证数据的一致性。如果 Cancel 操作失败,会产生一定的风险,可能导致数据不一致或资源未能正确释放。

   Cancel失败的处理策略:重试机制、补偿机制(确保系统最终达到一致性)

   PS:目前已经有比较成熟的分布式事务解决方案,请参考文章《分布式事务框架Seata》

   4. Saga 事务 

   Saga 最初由 Hector Garcia-Molina 和 Kenneth Salem 于 1987 年提出,最初用于解决长时间运行事务的问题。其核心思想是将一个大的事务拆分成多个独立的本地事务,每个本地事务执行成功后立即提交。如果后续事务执行失败,则通过执行之前事务对应的补偿操作,撤销或修正已经完成的业务操作,从而实现最终一致性。随着分布式系统的发展,Saga 逐渐成为一种常见的分布式事务解决方案。

   1)本地事务执行阶段

   将一个完整的分布式事务拆分成多个本地事务,每个参与者执行自己的业务操作,并提交本地事务。

   例如,用户下单涉及创建订单、扣减库存、扣减账户余额等操作,每个操作都由对应的服务独立完成。

   2)补偿阶段

   如果某个本地事务执行失败,则需要按照相反的顺序,依次执行之前已成功事务对应的补偿操作,撤销或修正之前的业务操作。

   例如,订单创建成功、库存扣减成功,但账户余额扣减失败,此时需要执行库存恢复和订单取消等补偿操作。

   在补偿阶段,每个参与者都需要提供对应的补偿逻辑,以便在事务失败时恢复业务状态。补偿失败的处理策略:通常采用重试机制、异常告警和人工介入等方式,确保补偿操作最终完成,使系统达到最终一致性。 

   需要注意的是,Saga 的补偿操作并不是真正意义上的数据库事务回滚,而是通过执行新的业务操作来抵消之前操作产生的影响。

   四、分布式事务的替代方案

   分布式事务在使用中往往会遇到一些问题,包括:性能开销、实现复杂性、死锁、长时间锁定及网络不稳定等。这些问题使得确保各个参与者的数据一致性变得更加困难。

   为了解决这些问题,常用的替代方案包括:

   1.  消息队列:通过异步消息传递解耦服务,降低对事务的依赖。

   2. 分布式锁:使用分布式锁管理资源访问,确保操作的顺序性。

   3. 最终一致性:通过异步处理和补偿机制,实现系统在一定时间内达到一致状态。 

   这些方案能够提高系统的可用性和性能,同时减少分布式事务带来的复杂性。

 

  参考链接:

  https://xiaomi-info.github.io/2020/01/02/distributed-transaction/

  https://blog.csdn.net/bjweimengshu/article/details/79607522

posted @ 2021-09-12 23:00  欢乐豆123  阅读(187)  评论(0)    收藏  举报