1.事务

事务,指的是在一些一系列的操作中,每一步要么全部成功,要么全部失败。即事务特性中的原子性
事务理论上具有以下特性

  • A:原子性,事务中的每一步要么全部成功,要么全部失败
  • I:隔离性,并发事务之间互不影响
  • D:持久性,事务产生的数据影响应该永久存在,不能出现因为宕机等原因而导致的数据丢失情况发生
  • C:一致性,事务开始与事务提交后,整体的数据需要保证一致,不能出现多数据或少数据的情况

    2.什么是分布式事务

    在传统的单体服务中,通常所有数据都存储在一个数据库中。那么此时事务可以通过数据库本身提供的事务机制(undolog,redolog,mvcc,间隙锁,行锁,表锁,next-key-lock)来实现。
    但是随着业务的发展,首先可能出现的情况就是,数据存储在了不同的数据库中。例如一个下订单功能,
    加入订单表与物品库存表存储在不同的数据库中,那么下订单的操作就需要生成订单,同时扣减物品库存。因为此时数据并不是存储在一个数据库中,传统依赖于数据库机制实现的事务就无法满足场景
    同时,如今微服务项目已经成为了主流应用架构。那么还是以生成订单为例,订单的创建以及物品库存的扣减,分别由订单服务以及库存服务共同完成。因为订单服务和库存服务分别有自己的数据库,那么此时,也无法依赖传统数据库机制实现的事务
    本质上来说,分布式事务出现根源是因为数据落在了不同的数据库内,单一数据库的机制已经无法满足。
    因此分布式事务应运而生。
    分布式事务是指,单个应用或多个应用,在数据存储在不同数据库的情况下,通过某种方案,实现了事务的ACID特性。

    3.分布式事务的场景

  • 跨数据库事务

  • 跨服务事务
  • 混合事务(跨数据库、跨服务)

image.png

4.CAP理论

  • Consistency(强一致性):在分布式系统中的所有数据副本,在同一时刻是否同样的值。
  • Availability(高可用性):保证每个请求不管成功或者失败都有响应。系统每时每刻都处于可用状态
  • Partition tolerance(分区容错性):系统中任意信息的丢失或失败不会影响系统的继续运作。

理论上来说,在分布式系统中,是无法同时满足强一致性以及高可用性。但是分区容错性是必须要具备的特性。因为如果每个服务都部署的是单体应用,同时不去考虑网络以及节点宕机问题,那就不是一个标准的微服务系统。因此,我们需要在强一致性和高可用性之间做一个取舍

  • CP 放弃高可用性,追求强一致性。比如银行系统中跨行转账,可以不可用,但不能有错账。
  • AP 放弃强一致性,追求高可用性。这是很多分布式系统设计时的选择,通常实现 AP 都会保证最终一致性。

    5.BASE理论

    BASE 理论指的是基本可用 Basically Available,软状态 Soft State,最终一致性 Eventual Consistency,核心思想是即便无法做到强一致性,但应该采用适合的方式保证最终一致性。
    BASE,Basically Available Soft State Eventual Consistency 的简写:

  • BA:Basically Available 基本可用,分布式系统在出现故障的时候,允许损失部分可用性,即保证核心可用。

  • S:Soft State 软状态,同一数据的不同副本的状态,可以不需要实时一致。
  • E:Consistency 最终一致性,系统中的所有数据副本经过一定时间后,最终能够达到一致的状态。

BASE 理论本质上是对 CAP 理论的延伸,在CA之间进行了平衡,是对 CAP 中 AP 方案的一个补充。

6.XA分布式事务理论

定义

XA 规范 是 X/Open 组织定义的分布式事务处理(DTP,Distributed Transaction Processing)标准。
XA 规范 描述了全局的事务管理器与局部的资源管理器之间的接口。 XA规范 的目的是允许的多个资源(如数据库,应用服务器,消息队列等)在同一事务中访问,这样可以使 ACID 属性跨越应用程序而保持有效。
XA 规范 使用两阶段提交(2PC,Two-Phase Commit)来保证所有资源同时提交或回滚任何特定的事务。

总结

关于XA规范需要记住以下两点

  1. 引入全局事务的控制节点,事务的协调者,来进行整体事务的调配
  2. 多个本地事务划分多阶段提交,具体的阶段由事务协调者进行调配
  3. XA规范是从资源层面(数据库层面)实现分布式事务

    7.基于XA的分布式事务的协议

    二阶段提交(2PC)

    image.png
    2阶段提交最终的追求是强一致性
    如图,2阶段提交过程中,共有两个角色参与
  • 事务协调器(协调者):负责调配整体事务,根据每个本地资源管理器的反馈进行整体事务的调度
  • 本地资源管理器(参与者):通常由数据库实现,是具体的事务执行单元

    概述

  • 协调者角度:在发起投票之后就进入了 WAIT 状态,等待所有参与者回复各自事务执行状态,并在收到所有参与者的回复后决策下一步是发送 commit 或 rollback 信息。

  • 参与者的角度:当回复完协调者的投票请求之后便进入 READY 状态(能够正常执行事务),接下去就是等待协调者最终的决策通知,一旦收到通知便可依据决策执行 commit 或 rollback 操作。

    第一阶段-投票阶段

  1. 事务协调器向所有的参与者发起询问,是否可以执行提交操作,并开始阻塞等待各个本地资源参与者应答
  2. 每个本地资源参与者执行所有事务操作,但不提交。在这一步实际上的事务操作就已经执行完成了
  3. 每个本地资源参与者响应事务协调器发起的询问,执行成功则回复同意,执行失败则回复终止
  4. 每个本地资源参与者阻塞等待协调者的下一步调度

    第二阶段-提交阶段(全部参与者响应信息都为成功)

  5. 事务协调器获取到每个参与者的响应,全部为成功

  6. 事务协调器发送提交命令给每个参与者
  7. 每个参与各获取到命令后提交事务,释放资源。并响应给事务协调器提交结果、
  8. 事务协调器接收到参与者信息,完成事务
  9. 不管最后结果如何,第二阶段都会结束当前事务。

    第二阶段-提交阶段(存在参与者响应信息失败、存在参与者响应超时)

  10. 事务协调器获取到每个参与者的响应,存在失败情况

  11. 事务协调器发送回滚命令给每个参与者
  12. 每个参与各获取到命令后回滚事务,释放资源。并响应给事务协调器回滚结果、
  13. 事务协调器接收到参与者信息,取消事务
  14. 不管最后结果如何,第二阶段都会结束当前事务。

    二阶段提交的不足

  15. 单点问题:二阶段提交过程中,事务协调者扮演了极其重要的角色,一旦事务协调者出现宕机或者因为网络异常而无法接收到消息,那么就无法及时的发出调度命令。因而会导致参与者长期处于阻塞状态,引发整个服务链路崩溃

  16. 阻塞问题:二阶段提交过程中,所有的参与者都必须接收到协调者的指令后,才能继续下一步操作。在这之间的过程中,事务的参与者长期处于一个阻塞状态,无法进行其他操作
  17. 数据不一致:因为分布式事务,每个参与者与协调者都是远程通过网络进行沟通,那么当出现网络异常,进而导致协调者或参与者无法收到彼此的指令或应答,很可能会出现数据不一致的问题。
  18. 例如,在第二阶段,协调者发出提交命令。此时因为网络问题只有部分参与者收到消息,其他参与者没有收到消息。那么在这一时刻,就出现了数据不一致的情况。

    三阶段提交(3PC)

    三阶段提交相比较二阶段提交,做了以下优化

  19. 对于协调者以及参与者,都实现了超时机制。协调者在规定时间内没有收到参与者的应答ACK,默认会执行对应的回滚操作。参与者如果在规定时间内没收到协调者的消息,会默认执行提交操作

  20. 添加了预准备阶段,在事务开始时,协调者会询问所有参与者是否已经准备好执行事务(例如锁的获取等),而不是直接让所有参与者直接执行事务。

    第一阶段-canCommit阶段

    这个阶段所做的事很简单,就是协调者询问事务参与者,你是否有能力完成此次事务。
  • 如果都返回yes,则进入第二阶段
  • 有一个返回no或等待响应超时,则中断事务,并向所有参与者发送abort请求

    第二阶段-preCommit阶段

    「PreCommit阶段」此时协调者会向所有的参与者发送PreCommit请求,参与者收到后开始执行事务操作,并将Undo和Redo信息记录到事务日志中。参与者执行完事务操作后(此时属于未提交事务的状态),就会向协调者反馈“Ack”表示我已经准备好提交了,并等待协调者的下一步指令。

    第三阶段-doCommit阶段

    「DoCommit阶段」在阶段二中如果所有的参与者节点都可以进行PreCommit提交,那么协调者就会从“预提交状态”转变为“提交状态”。然后向所有的参与者节点发送”doCommit”请求,参与者节点在收到提交请求后就会各自执行事务提交操作,并向协调者节点反馈“Ack”消息,协调者收到所有参与者的Ack消息后完成事务。相反,如果有一个参与者节点未完成PreCommit的反馈或者反馈超时,那么协调者都会向所有的参与者节点发送abort请求,从而中断事务。
    三阶段提交因为引入了预准备阶段,多出了一次网络IO,因此相比较于2阶段提交,性能会有所下降。同时三阶段提交也没有从根本上解决数据不一致的问题。所以三阶段提交即便安全性有所提高,但是相对来说应用少于二阶段提交

    8.TCC分布式事务理论

    定义

    TCC(try-confirm/cancel) 二阶段补偿型事务
    相对于XA分布式事务理论来说,TCC分布式事务是基于业务层面的分布式事务,追求的是最终一致性。包括事务发起者(也可以是事务协调器)以及事务参与者两个角色,与此同时,事务参与者需要实现try、confirm、cancel三种类型的接口。也就是说,具体事务的准备,执行,回滚操作交给了应用去使用代码实现,而不是用数据库本身的事务机制去实现

    Try预处理阶段

    从业务上来讲,所有的参与者需要完成对应的业务检查,预留出相应资源(冻结库存,生成订单号等)。因为是基于应用层实现的逻辑,所以参与者无需阻塞住等待事务发起者的响应,而是由事务发起者主动调用事务参与者提供的try类型接口。它和后续的 confirm 一起才能真正构成一个完整的业务逻辑。

    Confirm提交阶段

    Try阶段所有参与者执行成功后,事务发起者调用所有参与者的confirm接口,完成整个事务的一个操作。通常情况下,只要 Try成功,Confirm一定成功。若Confirm阶段真的出错了,需要引入重试机制会人工处理。

    Cancel补偿阶段

    Try阶段存在参与者执行失败或响应超时,此时需要事务发起者调用所有参与者的cancel接口进行补偿(说白了在业务层面对try阶段的数据进行回滚)。通常情况下,Cancel一定成功。若Cancel 阶段真的出错了,需要引入重试机制会人工处理。

    总结

    TCC理论实际上与2阶段提交大致流程类似,但是TCC理论的根本是将数据库里面事务的提交回滚等操作,交给了业务层去具体编码实现。与此同时TCC分布式事务中也会存在响应超时的情况,那么此时就需要引入重试机制。因此,事务参与者提供的try、confirm、cancel接口必须满足幂等性需求

  • 优点:TCC理论最大的优点在于解决了XA理论中的阻塞问题,因此性能上要远远优于XA理论

  • 缺点:因为事务参与者需要实现try、confirm、cancel接口,因此对于代码的侵入性以及编码难度都是巨大的提升,会极大提高整体项目的复杂度,对开发人员的能力要求较高

具体参考:TCC 补偿式事务柔性事务 :TCC两阶段补偿型

9.SAGA分布式事务理论

定义

Saga是一种“长事务的解决方案”,更适合于“业务流程长、业务流程多”的场景。特别是针对参与事务的服务是遗留系统服务,此类服务无法提供TCC模式下的三个接口,就可以采用Saga模式。
Saga模式中所有的事务参与者,都需要实现两个功能,事务执行以及事务补偿,同时为了应对重试机制,功能必须满足幂等性
Saga模式的分布式事务也是基于应用层实现的,因此也不需要阻塞等待,性能优于XA

执行模式

编排模式(Choreography

去中心化执行模式
参与者(子事务)之间的调用、分配、决策和排序,通过交换事件进行进行。是一种去中心化的模式,参与者之间通过消息机制进行沟通,通过监听器的方式监听其他参与者发出的消息,从而执行后续的逻辑处理。由于没有中间协调点,靠参与者自己进行相互协调。

控制模式(Orchestration)

中心化执行模式
Saga提供一个控制类,进行整体分布式事务的调度。事务执行的命令从控制类发起,按照逻辑顺序请求Saga的参与者,从参与者那里接受到反馈以后,控制类在发起向其他参与者的调用。所有Saga的参与者都围绕这个控制类进行沟通和协调工作。

数据恢复模式

向前恢复

image.png
对于执行不通过的事务,会尝试重试事务,这里有一个假设就是每个子事务最终都会成功。这种方式适用于必须要成功的场景,是一种勇往直前的事务执行模式

向后恢复

image.png
在执行事务失败时,按照事务链路,补偿所有已完成的事务,是“一退到底”的方式。

执行步骤(编排模式,向后恢复为例)

  1. 初始事务发起者,执行本地事务方法,发送消息给下一个事务参与者
  2. 如果链路调用过程,某个事务节点出现异常,那么异常事务节点反方向调用上一个节点的补偿事务。以此类推,直到全局事务补偿完毕,事务结束
  3. 如果调用链路一直正常,那么按照事务链路顺序进行调用,知道全部链路执行完成,事务结束

    10.AT分布式事务理论

    参考:seata文档

    11.消息事务理论

    定义

    将多个事务通过消息中间件进行解耦,通过半事务消息以及队列回查机制,实现分布式事务。
    消息事务理论上追求的是最终一致性
    半事务消息:默认对消费者不可见的消息。只有当生产者执行完本地事务,发送commit确认结果给消息中间件以后(或消息中间件通过反查机制获取到生产者本地事务的执行结果为commit以后),才会投递给消息的消费者。

    执行步骤

    image.png

  4. 生产者将半事务消息发送至消息队列RocketMQ版服务端。

  5. 消息队列RocketMQ版服务端将消息持久化成功之后,向生产者返回Ack确认消息已经发送成功,此时消息为半事务消息。
  6. 生产者开始执行本地事务逻辑。
  7. 生产者根据本地事务执行结果向服务端提交二次确认结果(Commit或是Rollback),服务端收到确认结果后处理逻辑如下:
    • 二次确认结果为Commit:服务端将半事务消息标记为可投递,并投递给消费者。
    • 二次确认结果为Rollback:服务端将回滚事务,不会将半事务消息投递给消费者。
  8. 在断网或者是生产者应用重启的特殊情况下,若服务端未收到发送者提交的二次确认结果,或服务端收到的二次确认结果为Unknown未知状态,经过固定时间后,服务端将对消息生产者即生产者集群中任一生产者实例发起消息回查。
  9. 消费端接收到消息进行消费,如果消费失败,则不断重试

事务消息回查步骤如下:

  1. 生产者收到消息回查后,需要检查对应消息的本地事务执行的最终结果。
  2. 生产者根据检查得到的本地事务的最终状态再次提交二次确认,服务端仍按照步骤4对半事务消息进行处理。