本地事务

本地事务介绍

本地事务,是指传统的单机数据库事务,必须具备ACID原则

  • 原子性(A)

    所谓的原子性就是说,在整个事务中的所有操作,要么全部完成,要么全部不做,没有中间状态。对于事务在执行中发生错误,所有的操作都会被回滚,整个事务就像从没被执行过一样。

  • 一致性(C)

    事务的执行必须保证系统的一致性,在事务开始之前和事务结束以后,数据库的完整性没有被破坏,就拿转账为例,A有500元,B有500元,如果在一个事务里A成功转给B50元,那么不管发生什么,那么最后A账户和B账户的数据之和必须是1000元。

  • 隔离性(I)

    所谓的隔离性就是说,事务与事务之间不会互相影响,一个事务的中间状态不会被其他事务感知。数据库保证隔离性包括四种不同的隔离级别:

    • Read Uncommitted(读取未提交内容)

    • Read Committed(读取提交内容)

    • Repeatable Read(可重读)

    • Serializable(可串行化)

  • 持久性(D)

    所谓的持久性,就是说一旦事务提交了,那么事务对数据所做的变更就完全保存在了数据库中,即使发生停电,系统宕机也是如此。

因为在传统项目中,项目部署基本是单点式:即单个服务器和单个数据库。这种情况下,数据库本身的事务机制就能保证ACID的原则,这样的事务就是本地事务。

事务日志undo和redo

单个服务与单个数据库的架构中,产生的事务都是本地事务。其中原子性和持久性其实是依靠undo和redo 日志来实现。

InnoDB的事务日志主要分为:

  • undo log(回滚日志,提供回滚操作)

  • redo log(重做日志,提供前滚操作)

1) undo log日志介绍

Undo Log的原理很简单,为了满足事务的原子性,在操作任何数据之前,首先将数据备份到Undo Log。然后进行数据的修改。如果出现了错误或者用户执行了ROLLBACK语句,系统可以利用Undo Log中的备份将数据恢复到事务开始之前的状态。

Undo Log 记录了此次事务「开始前」 的数据状态,记录的是更新之 「前」的值

undo log 作用:

  1. 实现事务原子性,可以用于回滚

  2. 实现多版本并发控制(MVCC), 也即非锁定读

Undo log 产生和销毁

  1. Undo Log在事务开始前产生

  2. 当事务提交之后,undo log 并不能立马被删除,而是放入待清理的链表

  3. 会通过后台线程 purge thread 进行回收处理

Undo Log属于逻辑日志,记录一个变化过程。例如执行一个delete,undolog会记录一个insert;执行一个update,undolog会记录一个相反的update。

2) redo log日志介绍

和Undo Log相反,Redo Log记录的是新数据的备份。在事务提交前,只要将Redo Log持久化即可,不需要将数据持久化,减少了IO的次数。

Redo Log: 记录了此次事务「完成后」 的数据状态,记录的是更新之 「后」的值

Redo log的作用:

  • 比如MySQL实例挂了或宕机了,重启时,InnoDB存储引擎会使用redo log恢复数据,保证数据的持久性与完整性。

Redo Log 的工作原理

Undo + Redo事务的简化过程

假设有A、B两个数据,值分别为1,2

 A. 事务开始.
 B. 记录A=1到undo log buffer.
 C. 修改A=3.
 D. 记录A=3到redo log buffer.
 E. 记录B=2到undo log buffer.
 F. 修改B=4.
 G. 记录B=4到redo log buffer.
 H. 将undo log写入磁盘
 I. 将redo log写入磁盘
 J. 事务提交
1. 写入 Undo Log: 在数据库执行写操作前,先将原始数据写入 Undo Log,以备回滚需要。这确保了在事务回滚时,可以撤销事务对数据的修改。
2. 执行数据页的更新: 实际的数据修改发生在数据页上。数据库引擎将事务执行的修改应用到相关的数据页中,这可能包括插入、更新或删除操作。
3. 写入 Redo Log: 在事务执行期间,所有对数据库的修改都会写入 Redo Log。Redo Log 记录了事务对数据库进行的修改操作。这样可以在数据库发生故障时,通过 Redo Log 重新执行事务,确保事务的提交结果。
4. 提交事务: 当事务成功执行,并且数据库引擎确认所有修改操作已经写入了 Redo Log 和 Undo Log,事务会被提交。提交操作将会持久化将这些变更应用到数据库。

安全和性能问题

  • 如何保证原子性?

    如果在事务提交前故障,通过undo log日志恢复数据。如果undo log都还没写入,那么数据就尚未持久化,无需回滚

  • 如何保证持久化?

    大家会发现,这里并没有出现数据的持久化。因为数据已经写入redo log,而redo log持久化到了硬盘,因此只要到了步骤I以后,事务是可以提交的。

  • 内存中的数据库数据何时持久化到磁盘?

    因为redo log已经持久化,因此数据库数据写入磁盘与否影响不大,不过为了避免出现脏数据(内存中与磁盘不一致),事务提交后也会将内存数据刷入磁盘(也可以按照固设定的频率刷新内存数据到磁盘中)。

  • redo log何时写入磁盘

    redo log会在事务提交之前,或者redo log buffer满了的时候写入磁盘

总结一下:

  • undo log 记录更新前数据,用于保证事务原子性

  • redo log 记录更新后数据,用于保证事务的持久性

  • redo log有自己的内存buffer,先写入到buffer,事务提交时写入磁盘

  • redo log持久化之后,意味着事务是可提交

分布式事务

分布式事务介绍

分布式事务,就是指不是在单个服务或单个数据库架构下,产生的事务:

  • 跨数据源的分布式事务

  • 跨服务的分布式事务

  • 综合情况

1)跨数据源

随着业务数据规模的快速发展,数据量越来越大,单库单表逐渐成为瓶颈。所以我们对数据库进行了水平拆分,将原单库单表拆分成数据库分片,于是就产生了跨数据库事务问题。

2)跨服务

在业务发展初期,“一块大饼”的单业务系统架构,能满足基本的业务需求。但是随着业务的快速发展,系统的访问量和业务复杂程度都在快速增长,单系统架构逐渐成为业务发展瓶颈,解决业务系统的高耦合、可伸缩问题的需求越来越强烈。

如下图所示,按照面向服务(SOA)的架构的设计原则,将单业务系统拆分成多个业务系统,降低了各系统之间的耦合度,使不同的业务系统专注于自身业务,更有利于业务的发展和系统容量的伸缩。

3)分布式系统的数据一致性问题

在数据库水平拆分、服务垂直拆分之后,一个业务操作通常要跨多个数据库、服务才能完成。在分布式网络环境下,我们无法保障所有服务、数据库都百分百可用,一定会出现部分服务、数据库执行成功,另一部分执行失败的问题。

当出现部分业务操作成功、部分业务操作失败时,业务数据就会出现不一致。

例如电商行业中比较常见的下单付款案例,包括下面几个行为:

  • 创建新订单

  • 扣减商品库存

  • 从用户账户余额扣除金额

完成上面的操作需要访问三个不同的微服务和三个不同的数据库。

在分布式环境下,肯定会出现部分操作成功、部分操作失败的问题,比如:订单生成了,库存也扣减了,但是 用户账户的余额不足,这就造成数据不一致。

订单的创建、库存的扣减、账户扣款在每一个服务和数据库内是一个本地事务,可以保证ACID原则。

但是当我们把三件事情看做一个事情事,要满足保证“业务”的原子性,要么所有操作全部成功,要么全部失败,不允许出现部分成功部分失败的现象,这就是分布式系统下的事务了。

此时ACID难以满足,这是分布式事务要解决的问题.

概念

CAP 理论

CAP 定理,又被叫作布鲁尔定理。

CAP 指的是:一致性(Consistency)、可用性(Availability)、分区容错性(Partition tolerance)。

CAP 定律说的是,在一个分布式系统中,最多只能满足 C、A、P 中的两个,不可能三个同时满足。 而在分布式系统中,网络无法 100% 可靠,分区其实是一个必然现象。

如果我们选择了 CA 而放弃了 P,那么当发生分区现象时,为了保证一致性,这个时候必须拒绝请求,但是 A 又不允许,所以分布式系统理论上 不可能选择 CA 架构,只能选择 CP 或者 AP 架构。而且,显然,任何横向扩展策略都要依赖于数据分区。因此,设计人员必须在一致性与可用性之间做出选择。

  • 一致性(C):在分布式系统中的所有数据备份,在同一时刻是否同样的值。(等 同于所有节点访问同一份最新的数据副本)

  • 可用性(A):在集群中一部分节点故障后,集群整体是否还能响应客户端的读写请求。(对数据更新具备高可用性)

  • 分区容错性(P):分布式系统中各个节点会因为网络问题分区,如果某些数据值存储在一个节点上,那如果这个节点出现问题是不能被容忍的,所以数据会存储到多个节点上,如果出现网络分区,这个数据可能存储在多个节点上,容忍性提高。

实际上架构师会在A和C之前权衡,P一般是不会放弃

BASE 理论

往往在分布式系统中无法实现完全一致性:

  • Basically Available(基本可用) : 分布式系统在出现故障时,允许损失部分可用功能,保证核心功能可用

    • 部分用户双十一高峰期淘宝页面卡顿或降级处理;

  • Soft state(软状态) : 允许系统中存在中间状态,这个状态不影响系统可用性;

    • 12306 网站卖火车票,请求会进入排队队列;

  • Eventually consistent(最终一致性) : 经过一段时间后,所有节点数据都将会达到一致

    • 理财产品首页充值总金额短时不一致;

BASE 理论是对 CAP 中的一致性和可用性进行一个权衡的结果

BASE 理论核心思想就是:我们无法做到强一致,但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性

原子性和持久性必须从根本上保障,为了可用性、性能和服务降级的需要,只有降低一致性和隔离性的要求。BASE 解决了 CAP 理论中没有考虑到的网络延迟问题,在BASE中用软状态和最终一致,保证了延迟后的一致性。

我们以下单减库存和扣款为例:

订单服务、库存服务、用户服务及他们对应的数据库就是分布式应用中的三个部分。

  • CP方式:现在如果要满足事务的强一致性,就必须在订单服务数据库锁定的同时,对库存服务、用户服务数据资源同时锁定。等待三个服务业务全部处理完成,才可以释放资源。此时如果有其他请求想要操作被锁定的资源就会被阻塞,这样就是满足了CP。

    这就是强一致,弱可用

  • AP方式:三个服务的对应数据库各自独立执行自己的业务,执行本地事务,不要求互相锁定资源。但是这个中间状态下,我们去访问数据库,可能遇到数据不一致的情况,不过我们需要做一些后补措施,保证在经过一段时间后,数据最终满足一致性。

    这就是高可用,但弱一致(最终一致)。

由上面的两种思想,延伸出了很多的分布式事务解决方案:

  • XA

  • TCC

  • 可靠消息最终一致

  • AT

分布式事务模式

了解了分布式事务中的强一致性和最终一致性理论,下面介绍几种常见的分布式事务的解决方案。

DTP模型与XA协议

1) DTP介绍

X/Open DTP(Distributed Transaction Process)是一个分布式事务模型。这个模型主要使用了两段提交(2PC - Two-Phase-Commit)来保证分布式事务的完整性。

1994 年,X/Open 组织(即现在的 Open Group )定义了分布式事务处理的DTP 模型。该模型包括这样几个角色:

  • 应用程序( AP ):我们的微服务

  • 事务管理器( TM ):全局事务管理者

  • 资源管理器( RM ):一般是数据库

  • 通信资源管理器( CRM ):是TM和RM间的通信中间件

在该模型中,一个分布式事务(全局事务)可以被拆分成许多个本地事务,运行在不同的AP和RM上。每个本地事务的ACID很好实现,但是全局事务必须保证其中包含的每一个本地事务都能同时成功,若有一个本地事务失败,则所有其它事务都必须回滚。但问题是,本地事务处理过程中,并不知道其它事务的运行状态。因此,就需要通过CRM来通知各个本地事务,同步事务执行的状态。

因此,各个本地事务的通信必须有统一的标准,否则不同数据库间就无法通信。XA就是 X/Open DTP中通信中间件与TM间联系的接口规范,定义了用于通知事务开始、提交、终止、回滚等接口,各个数据库厂商都必须实现这些接口。

2) XA介绍

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

XA之所以需要引入事务管理器,是因为在分布式系统中,从理论上讲两台机器理论上无法达到一致的状态,需要引入一个单点进行协调。由全局事务管理器管理和协调的事务,可以跨越多个资源(数据库)和进程。

事务管理器用来保证所有的事务参与者都完成了准备工作(第一阶段)。如果事务管理器收到所有参与者都准备好的消息,就会通知所有的事务都可以提交了(第二阶段)。MySQL 在这个XA事务中扮演的是参与者的角色,而不是事务管理器。

2PC模式 (强一致性)

二阶提交协议就是根据这一思想衍生出来的,将全局事务拆分为两个阶段来执行:

  • 阶段一:准备阶段,各个本地事务完成本地事务的准备工作。

  • 阶段二:执行阶段,各个本地事务根据上一阶段执行结果,进行提交或回滚。

这个过程中需要一个协调者(coordinator),还有事务的参与者(voter)。

1)正常情况

投票阶段:协调组询问各个事务参与者,是否可以执行事务。每个事务参与者执行事务,写入redo和undo日志,然后反馈事务执行成功的信息(agree

提交阶段:协调组发现每个参与者都可以执行事务(agree),于是向各个事务参与者发出commit指令,各个事务参与者提交事务。

2)异常情况

当然,也有异常的时候:

投票阶段:协调组询问各个事务参与者,是否可以执行事务。每个事务参与者执行事务,写入redo和undo日志,然后反馈事务执行结果,但只要有一个参与者返回的是Disagree,则说明执行失败。

提交阶段:协调组发现有一个或多个参与者返回的是Disagree,认为执行失败。于是向各个事务参与者发出abort指令,各个事务参与者回滚事务。

3)二阶段提交的缺陷

缺陷1: 单点故障问题

  • 2PC的缺点在于不能处理fail-stop形式的节点failure. 比如下图这种情况.

  • 假设coordinator和voter3都在Commit这个阶段crash了, 而voter1和voter2没有收到commit消息. 这时候voter1和voter2就陷入了一个困境. 因为他们并不能判断现在是两个场景中的哪一种:

    (1)上轮全票通过然后voter3第一个收到了commit的消息并在commit操作之后crash了

    (2)上轮voter3反对所以干脆没有通过.

缺陷2: 阻塞问题

  • 在准备阶段、提交阶段,每个事物参与者都会锁定本地资源,并等待其它事务的执行结果,阻塞时间较长,资源锁定时间太久,因此执行的效率就比较低了。

3)二阶段提交的使用场景

  • 对事务有强一致性要求,对事务执行效率不敏感,并且不希望有太多代码侵入。

面对二阶段提交的上述缺点,后来又演变出了三阶段提交,但是依然没有完全解决阻塞和资源锁定的问题,而且引入了一些新的问题,因此实际使用的场景较少。

TCC模式 (最终一致性)

TCC(Try-Confirm-Cancel)的概念,最早是由 Pat Helland 于 2007 年发表的一篇名为《Life beyond Distributed Transactions:an Apostate’s Opinion》的论文提出。

TCC 是服务化的两阶段编程模型,其 Try、Confirm、Cancel 3 个方法均由业务编码实现:

1) TCC的基本原理

它本质是一种补偿的思路。事务运行过程包括三个方法,

  • Try:资源的检测和预留;

  • Confirm:执行的业务操作提交;要求 Try 成功 Confirm 一定要能成功;

  • Cancel:预留资源释放。

执行分两个阶段:

  • 准备阶段(try):资源的检测和预留;

  • 执行阶段(confirm/cancel):根据上一步结果,判断下面的执行方法。如果上一步中所有事务参与者都成功,则这里执行confirm。反之,执行cancel

粗看似乎与两阶段提交没什么区别,但其实差别很大:

  • try、confirm、cancel都是独立的事务,不受其它参与者的影响,不会阻塞等待它人

  • try、confirm、cancel由程序员在业务层编写,锁粒度有代码控制

2) TCC的具体实例

我们以之前的下单业务中的扣减余额为例来看下三个不同的方法要怎么编写,假设账户A原来余额是100,需要余额扣减30元。如图:

  • 一阶段(Try):余额检查,并冻结用户部分金额,此阶段执行完毕,事务已经提交

    • 检查用户余额是否充足,如果充足,冻结部分余额

    • 在账户表中添加冻结金额字段,值为30,余额不变

  • 二阶段

    • 提交(Confirm):真正的扣款,把冻结金额从余额中扣除,冻结金额清空

      • 修改冻结金额为0,修改余额为100-30 = 70元

    • 补偿(Cancel):释放之前冻结的金额,并非回滚

      • 余额不变,修改账户冻结金额为0

3) TCC模式的优势和缺点

  • 优势

    TCC执行的每一个阶段都会提交本地事务并释放锁,并不需要等待其它事务的执行结果。而如果其它事务执行失败,最后不是回滚,而是执行补偿操作。这样就避免了资源的长期锁定和阻塞等待,执行效率比较高,属于性能比较好的分布式事务方式。

  • 缺点

    • 代码侵入:需要人为编写代码实现try、confirm、cancel,代码侵入较多

    • 开发成本高:一个业务需要拆分成3个步骤,分别编写业务实现,业务编写比较复杂

    • 安全性考虑:cancel动作如果执行失败,资源就无法释放,需要引入重试机制,而重试可能导致重复执行,还要考虑重试时的幂等问题

4) TCC使用场景

  • 对事务有一定的一致性要求(最终一致)

  • 对性能要求较高

  • 开发人员具备较高的编码能力和幂等处理经验

消息队列模式(最终一致性)

消息队列的方案最初是由 eBay 提出,基于TCC模式,消息中间件可以基于 Kafka、RocketMQ 等消息队列。

此方案的核心是将分布式事务拆分成本地事务进行处理,将需要分布式处理的任务通过消息日志的方式来异步执行。消息日志可以存储到本地文本、数据库或MQ中间件,再通过业务规则人工发起重试。

1) 事务的处理流程:

  • 步骤1:事务主动方处理本地事务。

    事务主动方在本地事务中处理业务更新操作和MQ写消息操作。例如: A用户给B用户转账,主动方先执行扣款操作

  • 步骤 2:事务发起者A通过MQ将需要执行的事务信息发送给事务参与者B。例如: 告知被动方生增加银行卡金额

    事务主动方主动写消息到MQ,事务消费方接收并处理MQ中的消息。

  • 步骤 3:事务被动方通过MQ中间件,通知事务主动方事务已处理的消息,事务主动方根据反馈结果提交或回滚事务。例如: 订单生成成功,通知主动方法,主动放即可以提交.

为了数据的一致性,当流程中遇到错误需要重试,容错处理规则如下:

  • 当步骤 1 处理出错,事务回滚,相当于什么都没发生。

  • 当步骤 2 处理出错,由于未处理的事务消息还是保存在事务发送方,可以重试或撤销本地业务操作。

  • 如果事务被动方消费消息异常,需要不断重试,业务处理逻辑需要保证幂等。

  • 如果是事务被动方业务上的处理失败,可以通过MQ通知事务主动方进行补偿或者事务回滚。

那么问题来了,我们如何保证消息发送一定成功?如何保证消费者一定能收到消息?

2) 本地消息表

为了避免消息发送失败或丢失,我们可以把消息持久化到数据库中。实现时有简化版本和解耦合版本两种方式。

  • 事务发起者:

    • 开启本地事务

    • 执行事务相关业务

    • 发送消息到MQ

    • 把消息持久化到数据库,标记为已发送

    • 提交本地事务

  • 事务接收者:

    • 接收消息

    • 开启本地事务

    • 处理事务相关业务

    • 修改数据库消息状态为已消费

    • 提交本地事务

  • 额外的定时任务

    • 定时扫描表中超时未消费消息,重新发送

3) 消息事务的优缺点

总结上面的几种模型,消息事务的优缺点如下:

  • 优点:

    • 业务相对简单,不需要编写三个阶段业务

    • 是多个本地事务的结合,因此资源锁定周期短,性能好

  • 缺点:

    • 代码侵入

    • 依赖于MQ的可靠性

    • 消息发起者可以回滚,但是消息参与者无法引起事务回滚

    • 事务时效性差,取决于MQ消息发送是否及时,还有消息参与者的执行情况

针对事务无法回滚的问题,有人提出说可以再事务参与者执行失败后,再次利用MQ通知消息服务,然后由消息服务通知其他参与者回滚。那么,恭喜你,你利用MQ和自定义的消息服务再次实现了2PC 模型,又造了一个大轮子

AT模式 (最终一致性)

2019年 1 月份,Seata 开源了 AT 模式。AT 模式是一种无侵入的分布式事务解决方案。可以看做是对TCC或者二阶段提交模型的一种优化,解决了TCC模式中的代码侵入、编码复杂等问题。

在 AT 模式下,用户只需关注自己的“业务 SQL”,用户的 “业务 SQL” 作为一阶段,Seata 框架会自动生成事务的二阶段提交和回滚操作。

可以参考Seata的官方文档

1) AT模式基本原理

先来看一张流程图:

有没有感觉跟TCC的执行很像,都是分两个阶段:

  • 一阶段:执行本地事务,并返回执行结果

  • 二阶段:根据一阶段的结果,判断二阶段做法:提交或回滚

但AT模式底层做的事情可完全不同,而且第二阶段根本不需要我们编写,全部有Seata自己实现了。也就是说:我们写的代码与本地事务时代码一样,无需手动处理分布式事务。

那么,AT模式如何实现无代码侵入,如何帮我们自动实现二阶段代码的呢?

一阶段

  • 在一阶段,Seata 会拦截“业务 SQL”,首先解析 SQL 语义,找到“业务 SQL”要更新的业务数据,在业务数据被更新前,将其保存成“before image”,然后执行“业务 SQL”更新业务数据,在业务数据更新之后,再将其保存成“after image”,最后获取全局行锁,提交事务。以上操作全部在一个数据库事务内完成,这样保证了一阶段操作的原子性。

  • 这里的before imageafter image类似于数据库的undo和redo日志,但其实是用数据库模拟的。

update t_stock set stock = stock - 2 where id = 1

select * from t_stock where id = 1 ,保存元快照 before image ,类似undo日志.

放行执行真实SQL,执行完成,再次查询,获取到最新的库存数据,再将数据保存到镜像after image 类似redo.

提交业务如果成功,就清楚快照信息,失败,则根据redo 中的数据与数据库的数据进行对比,如果一直就回滚,如果不一致 出现脏数据,就需要人工介入.

AT模式最重要的一点就是 程序员只需要关注业务处理的本身即可,不需要考虑回滚补偿等问题.代码写的跟以前一模一杨.

二阶段提交

  • 二阶段如果是提交的话,因为“业务 SQL”在一阶段已经提交至数据库, 所以 Seata 框架只需将一阶段保存的快照数据和行锁删掉,完成数据清理即可。

二阶段回滚

  • 二阶段如果是回滚的话,Seata 就需要回滚一阶段已经执行的“业务 SQL”,还原业务数据。回滚方式便是用“before image”还原业务数据;但在还原前要首先要校验脏写,对比“数据库当前业务数据”和 “after image”,如果两份数据完全一致就说明没有脏写,可以还原业务数据,如果不一致就说明有脏写,出现脏写就需要转人工处理。

不过因为有全局锁机制,所以可以降低出现脏写的概率。

AT 模式的一阶段、二阶段提交和回滚均由 Seata 框架自动生成,用户只需编写“业务 SQL”,便能轻松接入分布式事务,AT 模式是一种对业务无任何侵入的分布式事务解决方案。

AT模式优缺点

优点:

  • 与2PC相比:每个分支事务都是独立提交,不互相等待,减少了资源锁定和阻塞时间

  • 与TCC相比:二阶段的执行操作全部自动化生成,无代码侵入,开发成本低

缺点:

  • 与TCC相比,需要动态生成二阶段的反向补偿操作,执行性能略低于TCC

Saga模式(最终一致性)

Saga 模式是 Seata 即将开源的长事务解决方案,将由蚂蚁金服主要贡献。

其理论基础是Hector & Kenneth 在1987年发表的论文Sagas

Seata官网对于Saga的指南:https://seata.io/zh-cn/docs/user/saga.html

1) 基本模型

在分布式事务场景下,我们把一个Saga分布式事务看做是一个由多个本地事务组成的事务,每个本地事务都有一个与之对应的补偿事务。在Saga事务的执行过程中,如果某一步执行出现异常,Saga事务会被终止,同时会调用对应的补偿事务完成相关的恢复操作,这样保证Saga相关的本地事务要么都是执行成功,要么通过补偿恢复成为事务执行之前的状态。(自动反向补偿机制)。

每个 Ti 都有对应的幂等补偿动作 Ci,补偿动作用于撤销 Ti 造成的结果。

Saga是一种补偿模式,它定义了两种补偿策略:

  • 向前恢复(forward recovery):对应于上面第一种执行顺序,发生失败进行重试,适用于必须要成功的场景(一定会成功)。

  • 向后恢复(backward recovery):对应于上面提到的第二种执行顺序,发生错误后撤销掉之前所有成功的子事务,使得整个 Saga 的执行结果撤销。

2) 适用场景

  • 业务流程长、业务流程多

  • 参与者包含其它公司或遗留系统服务,无法提供 TCC 模式要求的三个接口

3) 优势

  • 一阶段提交本地事务,无锁,高性能

  • 事件驱动架构,参与者可异步执行,高吞吐

  • 补偿服务易于实现

3) 缺点

  • 不保证隔离性