type
status
date
slug
summary
tags
category
icon
password

1、概述

分布式系统会把一个应用系统拆分为可独立部署的多个服务。服务之间通过远程通信来互相调用。服务内部依然可以通过本地事务来做事务控制,但服务之间的操作则无法再依赖本地事务来控制了,这就需要用到分布式事务。
具体一点而言,分布式事务是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。
notion image
上图中包含了库存和订单两个独立的微服务,每个微服务维护了自己的数据库。在交易系统的业务逻辑中,一个商品在下单之前需要先调用库存服务,进行扣除库存,再调用订单服务,创建订单记录。
notion image
可以看到,如果多个数据库之间的数据更新没有保证事务,将会导致出现子系统数据不一致,业务出现问题。

2、分布式系统理论

为了解决分布式系统的一致性问题,我们需要先了解分布式系统的一些基本理论。

2.1 CAP理论

CAP 理论是分布式事务处理的理论基础。CAP 理论是在一个分布式系统中,只能保证一致性(Consistence)、可用性(Availability)、分区容错性(PartitionTolerance)三者中的两个,另外一个必须被牺牲
notion image
CAP 理论包含下面核心要素:
  • C(Consistency)数据一致性:数据一致性是指各个节点的状态如何同步,又分为强一致性、弱一致性、最终一致性:
    • 强一致性:数据更新会立刻同步到其它节点,平常说的一致性通常指强一致性,强一致性也是 ACID 理论所强调的。
    • 弱一致性:在数据更新后就无法知道其它节点是否都同步成功
    • 最终一致性:数据更新后,可能一段时间不一致,但最后过一段时间后一定是一致的。
  • A(Availability)可用性:可用性是指系统提供的服务必须一直处于可用的状态,在任何时候客户端对集群进行读写操作时,请求能够正常响应,并在一定的延时内完成。这里强调的是合理的响应,不能超时,不能出错。注意并没有说“正确”的结果,例如,应该返回 100 但实际上返回了 90,肯定是不正确的结果,但可以是一个合理的结果。
  • P(Partition Tolerance)分区容错性:分区容错性要求分布式系统在遇到任何网络分区故障的时候,集群仍然能够对外提供服务。网络分区也就是俗称的“脑裂”,是指在分布式系统中,不同的节点分布在不同的子网络中,由于一些特殊的原因导致这些子网络之间出现网络不连通的状况,但各个子网络的内部网络是正常的,从而导致整个系统的网络环境被切分成了若干个孤立的区域。
分布式系统中,网络条件相对不可控,分区容错无法避免,因此可以认为 CAP 的 P 总是成立,剩下的 C 和 A 无法同时做到,所以只能在 AP 和 CP 之间进行选择
  • CP - Consistency + Partition Tolerance (一致性 + 分区容忍性)。如下图所示,因为 Node1 节点和 Node2 节点连接中断导致分区现象。Node2 出现故障,这时客户端 C 访问 Node2 时,Node2 返回 Error,提示客户端 “系统现在发生了错误”,这种处理方式违背了可用性(Availability)的要求,因此 CAP 三者只能满足 CP。
    • notion image
  • AP - Availability + Partition Tolerance (可用性 + 分区容忍性)。如下图所示,因为 Node1 节点和 Node2 节点连接中断导致分区现象,Node1节点的数据已经更新到 y,但是Node1 和 Node2 之间的复制通道中断,数据 y 无法同步到 Node2,Node2 节点上的数据还是旧数据 x。这时客户端C 访问 Node2 时,Node2 返回 x,这就不满足一致性(Consistency)的要求了,因此 CAP 三者只能满足 AP。
    • 注意:这里 Node2 节点返回 x,虽然不是一个“正确”的结果,但是一个“合理”的结果,因为 x 是旧的数据,并不是一个错乱的值,只是不是最新的数据。
      notion image
常见的 AP 或者 CP 系统:
  • CP:zookeeper、mongodb、hbase,保证数据一致性。当出现数据无法同步时,此时就会牺牲掉 A,客户端可能就会收到异常,但不会让你请求到不一致的数据。
  • AP:Eureka、Redis、RocketMQ、以及大多数业务类系统都是满足 AP,比如 12306、电商网站等,它们主要是满足系统的高可用性,数据一般都是最终一致。

2.2 BASE理论

BASE 是对 CAP 中 理论的扩展,相当于对 CAP 中一致性和可用性权衡的结果,BASE 是希望 CAP 基本都可以实现,但不要求全部100%实现。其核心思想是每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到基本可用、最终一致性。
notion image
BASE 包含下面核心要素要素:
  • BA(Basically Available)基本可用:基本可用是指分布式系统在出现不可预知故障的时候,允许损失部分可用性,比如响应时间可能会延迟,某些节点不可用的时候通过降级或限流来保证基本的可用性等。
  • S(Soft state)软状态:指允许系统中的数据存在中间状态,并认为该中间状态的存在不会影响系统的整体可用性,即允许系统在不同节点的数据副本之间进行数据同步的过程存在延时。那么各个节点之间的数据在一段时间内可能就会不一致,这个不一致的状态就是软状态。
  • E(Eventually consistent)最终一致性:最终一致性强调的是,虽然存在软状态,但系统中所有的数据副本最终还是能够达到一个一致的状态。因此,最终一致性的本质是需要系统保证最终数据能够达到一致,而不需要实时保证系统数据的强一致性。
这里的关键词是“一定时间” 和 “最终”
  • “一定时间”和数据的特性是强关联的,不同业务不同数据能够容忍的不一致时间是不同的。例如支付类业务是要求秒级别内达到一致,因为用户时时关注;用户发的最新微博,可以容忍30分钟内达到一致的状态,因为用户短时间看不到明星发的微博是无感知的。
  • “最终”的含义就是不管多长时间,最终还是要达到一致性的状态。
BASE 理论本质上是对 CAP 的延伸和补充,更具体地说,是对 CAP 中 AP 方案的一个补充:
  • CAP 理论是忽略延时的,而实际应用中延时是无法避免的。这一点就意味着完美的 CP 场景是不存在的,即使是几毫秒的数据复制延迟,在这几毫秒时间间隔内,系统是不符合 CP 要求的。因此 CAP 中的 CP 方案,实际上也是实现了最终一致性,只是“一定时间”是指几毫秒而已。
  • AP 方案中牺牲一致性只是指发生分区故障期间,而不是永远放弃一致性。这一点其实就是 BASE 理论延伸的地方,分区期间牺牲一致性,但分区故障恢复后,系统应该达到最终一致性。

3、分布式事务方案

分布式事务的实现有许多种,下面介绍比较经典的方案:
  • XA规范
  • 2PC(二阶段提交)
  • 3PC(三阶段提交)
  • Saga
  • TCC
  • 本地消息表
  • 可靠事务消息
  • 最大努力通知

3.1 XA规范

XA 是由 X/Open 组织提出的分布式事务的规范,是后面的 Saga、TCC 的基础。XA 仅仅是个规范,具体的实现是数据库产商来提供的,目前主流的数据库基本都支持 XA 事务,包括 Mysql、Oracle、Sqlserver、Postgre 等,比如 Mysql 提供了 XA 规范的接口函数和类库实现。
XA 包含四个角色:
  • AP(Application Program)应用程序:就是要使用分布式事务的应用程序。
  • RM(Resource Manager)资源管理器:通常就是指数据库,要访问的资源在数据库中,然后数据库会保障资源的 ACID 特性。
  • TM(Transaction Manager)事务管理器:就是在系统里嵌入的一个专门管理横跨多个数据库事务的一个组件,主要是给事务分配唯一标识,负责事务的启动、提交及回滚,保障全局事务的原子性。
  • CRM(Communication Resource Manager)通信资源管理器:一般是由消息中间件来作为这个组件,也可以没有。
这里的 RM、TM、AP 三个角色是经典的角色划分,会贯穿后续 Saga、Tcc 等事务模式。
XA 事务由一个应用程序(AP)、一个或多个资源管理器(RM)、一个事务管理器(TM)组成。XA 协议中分为两阶段:
  1. 第一阶段(prepare):即所有的参与者 RM 准备执行事务并锁住需要的资源。参与者 ready 时,向 TM 报告已准备就绪。
  1. 第二阶段 (commit/rollback):当事务管理者 (TM) 确认所有参与者 (RM) 都 read y后,向所有参与者发送 commit 命令。
notion image
如果有一阶段 prepare 操作失败,那么 TM 会通知各 RM 进行回滚,最后事务成功回滚。
notion image
优点:
  • 简单易理解
  • 开发较容易,回滚之类的操作,由底层数据库自动完成
缺点:
  • 对资源进行了长时间的锁定,并发度低,不适合高并发的业务

3.2 2PC(二阶段提交)

XA 其实都只是定义了一套规范,2PC(Two-Phase Commit)即两阶段提交协议则定义了实现分布式事务过程的细节。两阶段提交协议是将事务的提交过程分成了准备阶段和提交阶段来处理:
  1. 准备阶段。TM 会向参与此次事务的各个 RM 发起预提交事务的请求,每个参与者要么直接返回失败(如权限验证失败),要么在本地执行事务,写本地的 Redo 和 Undo 日志,但不提交。
    1. 事务询问:协调者节点向所有参与者节点询问是否可以执行提交操作(vote),并开始等待各参与者节点的响应。
    2. 执行事务:参与者节点检查事务权限,执行询问发起为止的所有事务操作,并将 Undo 信息和 Redo 信息写入事务日志。
    3. 各参与者节点响应协调者节点发起的询问:如果参与者节点的事务操作实际执行成功,则它返回一个 Yes 消息;如果参与者节点的事务操作实际执行失败,则它返回一个 No 消息。
notion image
  1. 提交阶段。协调者根据各参与者的反馈情况来决定最终是否可以执行事务提交。有两种可能:
      • 执行事务提交。假如协调者从所有的参与者获得的反馈都是 Yes 响应,就会执行事务的提交。
          1. 发送提交请求。协调者向所有的参与发送 Commit 请求。
          1. 事务提交。参与者收到 Commit 请求后,会正式执行事务的 Commit 操作,并在完成提交后释放在整个事务期间内占用的资源。
          1. 反馈事务提交结果。参与者在完成事务提交后,向协调者发送 Ack 消息。
          1. 完成事务。协调者接收到所有参与者反馈的 Ack 消息后,完成事务。
      • 中断事务。如果任一参与者节点在第一阶段返回的响应消息为”No”,或者协调者节点在第一阶段的询问超时之前无法获取所有参与者节点的响应消息时,就会中断事务。
          1. 发送回滚请求。协调者向所有的参与发送 Rollback 请求。
          1. 事务回滚。参与者收到 Rollback 请求后,利用之前写入的Undo信息执行回滚,并释放在整个事务期间内占用的资源。
          1. 反馈事务回滚结果。参与者在完成事务回滚后,向协调者发送 Ack 消息。
          1. 中断事务。协调者接收到所有参与者反馈的 Ack 消息后,完成事务中断。
      notion image
优点:
  • 实现强一致性,原理简单,实现方便
缺点:
  • 同步阻塞。在两阶段提交的执行过程中,所有参与该事务操作的逻辑都处于阻塞状态,而且在准备阶段占用的资源,一直到分布式事务完成才会释放,这个过程中如果其他人要访问这个资源,也会被阻塞住。
  • 单点故障:TM 是个单点,一旦 TM 出现问题,那么整个两阶段提交流程将无法运转,更为严重的是,如果 TM 是在提交阶段出现问题的话,那么其他参与者将会一直处于锁定事务资源的状态中,而无法继续完成事务操作。
  • 数据不一致:在二阶段提交的阶段二中,当协调者向参与者发送 commit 请求之后,发生了局部网络异常或者在发送 commit 请求过程中协调者发生了故障,这回导致只有一部分参与者接受到了 commit 请求。而在这部分参与者接到 commit 请求之后就会执行 commit 操作。但是其他部分未接到 commit 请求的机器则无法执行事务提交。于是整个分布式系统便出现了数据不一致性的现象。

3.3 3PC(三阶段提交)

为了解决 2PC 的问题,提出了 2PC 的改进版即三阶段提交协议(3PC),重要改进是引入超时机制即同时在协调者和参与者中都引入超时机制,然后将二阶段的投票过程一分为二。3PC 由 CanCommit、PreCommit、DoCommit 三个阶段组成。
  1. CanCommit 阶段:3PC 的 CanCommit 阶段其实和 2PC 的投票阶段很像。TM 首先发一个 CanCommit 消息给参与事务的 RM,这一步不会执行实际的SQL,其实就是看能否访问通 RM,包括 RM 自身的一些网络环境等,如果连某一个 RM 都访问不通,就没必要继续执行事务了。
    1. 事务询问:协调者向参与者发送 CanCommit 请求。询问是否可以执行事务提交操作。然后开始等待参与者的响应。
    2. 响应反馈:参与者接到 CanCommit 请求之后,正常情况下,如果其自身认为可以顺利执行事务,则返回 Yes 响应,并进入预备状态。否则反馈 No。
    3. notion image
  1. PreCommit 阶段:阶段二协调者会根据第一阶段的询盘结果采取相应操作,一般也有两种情况:
      • 执行事务预提交:假如协调者从所有的参与者获得的反馈都是 Yes 响应,那么就会执行事务的预提交。
          1. 发送预提交请求 协调者向参与者发送 PreCommit 请求,并进入 Prepared 阶段。
          1. 事务预提交:参与者接收到 PreCommit 请求后,会执行事务操作,并将 Undo 和 Redo 信息记录到事务日志中。
          1. 响应反馈:如果参与者成功的执行了事务操作,则返回 ACK 响应,同时开始等待最终指令:提交(commit)或中止(abort)。
      • 中断事务:假如有任何一个参与者向协调者发送了 No 响应,或者等待超时之后,协调者都没有接到参与者的响应,那么就执行事务的中断。
          1. 发送中断请求:协调者向所有参与者发送 abort 请求。
          1. 中断事务:参与者收到来自协调者的 abort 请求之后(或超时之后,仍未收到协调者的请求),执行事务的中断。
      notion image
  1. DoCommit 阶段:阶段三会进行真正的事务提交。也会存在两种可能:
      • 执行事务提交。假如协调者从所有的参与者获得的反馈都是 ACK 响应,就会执行事务的提交。
          1. 发送提交请求:协调者向所有参与者发送事务 commit 通知
          1. 事务提交:所有参与者在收到通知之后执行 commit 操作,并释放占有的资源
          1. 反馈事务提交结果:参与者在完成事务提交后,向协调者发送 Ack 消息。
          1. 完成事务。协调者接收到所有参与者反馈的 Ack 消息后,完成事务。
      • 中断事务:假如协调者接收到任意一个参与者返回了 No 响应,或者在等待超时后没有接收到所有参与者的反馈响应,那么就会执行中断事务。
          1. 发送回滚请求。协调者向所有的参与发送 Rollback 请求。
          1. 事务回滚。参与者收到 Rollback 请求后,利用之前写入的 Undo 信息执行回滚,并释放在整个事务期间内占用的资源。
          1. 反馈事务回滚结果。参与者在完成事务回滚后,向协调者发送 Ack 消息。
          1. 中断事务。协调者接收到所有参与者反馈的 Ack 消息后,完成事务中断。
      notion image
3PC 相比于 2PC 的改进:主要解决了单点故障问题和同步阻塞问题(但没有解决数据不一致问题)。
  • 引入了 CanCommit 阶段,这一步首先确认所有 RM 的环境都是OK的。
  • RM 在 DoCommit 阶段等待超时就会自动提交事务。在 DoCommit 阶段,如果 RM 收到 PreComit 消息并返回成功了,但是等待超时没有收到 TM 发来的 DoCommit 或 abort 消息,RM 就会判定 TM 故障了。RM 会认为,既然接收到了 PreCommit 消息,说明 CanCommit 阶段所有的 RM 一定是返回成功的,所以如果超时没有收到 DoCommit 消息,RM 就会认为其它 RM 的 PreCommit 都会成功,然后都可以自动执行事务提交。
优点:
  • RM 可以自己提交事务,解决 TM 单点故障的问题
缺点:
  • 数据不一致:在 DoCommit 阶段,如果 TM 发送 abort 消息给一部分 RM 后挂了,这部分 RM 回滚事务,然后其余的 RM 等待超时自动提交了事务,这就会造成数据不一致的问题。

3.4 Saga

Saga 是这一篇数据库论文 sagas 提到的一个方案。其核心思想是将长事务拆分为多个本地短事务,每个子事务都提供相应的执行逻辑和补偿逻辑。由 Saga 事务协调器协调,如果正常结束那就正常完成,如果某个步骤失败,要把前面所提交的数据全部回滚(补偿)。整体流程如下:
  • 每个Saga事务由一系列幂等的有序子事务(sub-transaction) Ti 组成。
  • 每个Ti 都有对应的幂等补偿动作Ci,补偿动作用于撤销Ti造成的结果。
我们的全局事务发起人,将整个全局事务的编排信息,包括每个步骤的正向操作和反向补偿操作定义好之后,提交给服务器,服务器就会按步骤执行前面 SAGA 的逻辑。
可以看到,和 TCC 相比,Saga 没有“预留”动作,它的 Ti 就是直接提交到库。
例如我们要进行一个类似于银行跨行转账的业务,将 A 中的 30 元转给B,根据 Saga 事务的原理,我们将整个全局事务,切分为以下服务:
  • 转出(TransOut)服务,这里转出将会进行操作A-30
  • 转出补偿(TransOutCompensate)服务,回滚上面的转出操作,即 A+30
  • 转入(TransIn)服务,转入将会进行 B+30
  • 转入补偿(TransInCompensate)服务,回滚上面的转入操作,即 B-30
整个SAGA事务执行成功的逻辑是:
执行转出成功=>执行转入成功=>全局事务完成
notion image
如果在中间发生错误,例如转入B发生错误,则会调用已执行分支的补偿操作,即:
执行转出成功=>执行转入失败=>执行转入补偿成功=>执行转出补偿成功=>全局事务回滚完成
notion image
当 SAGA 对分支 A 进行失败补偿时,A 的正向操作可能
  1. 已执行;
  1. 未执行;
  1. 甚至有可能处于执行中,最终执行成功或者失败是未知的。
SAGA 在对 A 进行补偿时,要妥善处理好这三种情况。
优点:
  • 并发度高,不用像 XA 事务那样长期锁定资源。
缺点:
  • 需要定义正常操作以及补偿操作,开发量比 XA 大
  • 一致性较弱,例如业务是发送短信,补偿动作则得再发送一次短信说明撤销,用户体验比较差。
  • 不能保证隔离性,需要在业务自行控制并发
适用场景:
  • SAGA 适用的场景较多,长事务适用,对中间结果不敏感的业务场景适用

3.5 TCC

TCC(Try-Confirm-Cancel)的概念,是基于 2PC 提出的。TCC 分为 3 个阶段
  • Try 阶段:尝试执行,完成所有业务检查(一致性), 预留必须业务资源(准隔离性)
  • Confirm 阶段:如果所有分支的 Try 都成功了,则走到 Confirm 阶段。Confirm 真正执行业务,不作任何业务检查,只使用 Try 阶段预留的业务资源
    • notion image
  • Cancel 阶段:如果所有分支的 Try 有一个失败了,则走到 Cancel 阶段。Cancel释放 Try 阶段预留的业务资源。
    • notion image
假如 Confirm/Cancel 操作遇见失败会怎么样?按照 Tcc 模式的协议,Confirm/Cancel 操作是要求最终成功的,遇见失败的情况,都是由于临时故障或者程序 bug。dtm 在 Confirm/Cancel 操作遇见失败时,会不断进行重试,直到成功。
为了避免程序 bug 导致补偿操作一直无法成功,建议开发者对全局事务表进行监控,发现重试超过 3 次的事务,发出报警,由运维人员找开发手动处理。
优点:
  • 实现很灵活,几乎可以满足任何分布式事务的场景
  • 并发度较高,无长期资源锁定。
  • 一致性较好,不会发生 SAGA 已扣款最后又转账失败的情况
缺点:
  • 开发量较大,需要开发 Try/Confirm/Cancel 三个接口,且需要考虑幂等性。
  • 其实实现思想还是依据 2PC,因此包含了 2PC 所有缺点。
适用场景:
  • TCC 主要用于处理一致性要求较高、需要较多灵活性的短事务。

TCC如何做到更好的一致性

对于我们的 A 跨行转账给 B 的场景,如果采用 SAGA,在正向操作中调余额,在补偿操作中,反向调整余额,那么会出现这种情况:如果 A 扣款成功,金额转入B失败,最后回滚,把A的余额调整为初始值。整个过程中如果A发现自己的余额被扣减了,但是收款方B迟迟没有收到资金,那么会对A造成非常大的困扰。
上述需求在 SAGA 中无法解决,但是可以通过TCC来解决,设计技巧如下:
  • 在账户中的 balance 字段之外,再引入一个 trading_balance 字段
  • Try 阶段检查账户是否被冻结,检查账户余额是否充足,没问题后,调整 trading_balance (即业务上的冻结资金)
  • Confirm 阶段,调整 balance ,调整 trading_balance (即业务上的解冻资金)
  • Cancel 阶段,调整 trading_balance (即业务上的解冻资金)
这种情况下,终端用户 A 就不会看到自己的余额扣减了,但是 B 又迟迟收不到资金的情况

为什么只适合短事务

TCC 的事务编排放在了应用端上,就是事务一共包含多少个分支,每个分支的顺序什么样,这些信息不会像 SAGA 那样,都发送给 dtm 服务器之后,再去调用实际的事务分支。当应用出现 crash 或退出,编排信息丢失,那么整个全局事务,就没有办法往前重试,只能够进行回滚。如果全局事务持续时间很长,例如一分钟以上,那么当应用进行正常的发布升级时,也会导致全局事务回滚,影响业务。因此 TCC 会更适合短事务。
那么是否可以把 TCC 的事务编排都保存到服务器,保证应用重启也不受到影响呢?理论上这种做法是可以解决这个问题的,但是存储到服务器会比在应用端更不灵活,无法获取到每个分支的中间结果,无法做嵌套等等。
考虑到一致性要求较高和短事务是高度相关的(一个中间不一致状态持续很长时间的事务,自然不能算一致性较好),这两者跟“应用灵活编排”,也是有较高相关度,所以将 TCC 实现为应用端编排,而 SAGA 实现为服务端编排。

3.6 本地消息表

本地消息表的核心思想是将分布式事务拆分成本地事务进行处理,过程是事务主动发起方在本地新建事务消息表,事务发起方处理业务和记录事务消息在本地事务中完成,轮询事务消息表的数据发送事务消息,事务被动方基于消息中间件消费事务消息表中的事务。
notion image
不同于 TCC 提供了 Cancel 取消接口,本地消息表则不提供回退的补偿机制,而是通过 MQ 重复投递消息让下游服务重复消费实现的补偿机制。也就是说服务方只提供一个幂等接口,不成功就重试,直到成功为止,一直不成功则人工处理。所以本地消息表其实就是利用了系统的本地事务来实现的一种最终一致性的分布式事务方案。
优点:
  • 长事务仅需要分拆成多个任务,使用简单
  • 不会长时间锁住资源,减少死锁风险
  • 没有长事务,执行效率高,提高吞吐量
缺点:
  • 生产者需要额外的创建消息表,每个本地消息表都需要进行轮询
  • 事务不是强一致性,而是最终一致性
  • 不支持回滚
适用场景:
  • 适用于可异步执行的业务,且后续操作无需回滚的业务

3.7 RocketMQ事务消息

在上述的本地消息表方案中,生产者需要额外创建消息表,还需要对本地消息表进行轮询,业务负担较重。阿里开源的 RocketMQ 4.3之后的版本正式支持事务消息,该事务消息本质上是把本地消息表放到 RocketMQ上,解决生产端的消息发送与本地事务执行的原子性问题,其他方面的协议基本与本地消息表一致。
事务消息发送及提交:
  • 发送消息(half消息)
  • 服务端存储消息,并响应消息的写入结果
  • 根据发送结果执行本地事务(如果写入失败,此时 half 消息对业务不可见,本地逻辑不执行)
  • 根据本地事务状态执行 Commit 或者 Rollback(Commit 操作发布消息,消息对消费者可见)
notion image
补偿流程:
对没有 Commit/Rollback 的事务消息(pending状态的消息),从服务端发起一次“回查” Producer收到回查消息,返回消息对应的本地事务的状态,为 Commit 或者 Rollback 事务消息方案与本地消息表机制非常类似,区别主要在于原先相关的本地表操作替换成了一个反查接口
优点:
  • 长事务仅需要分拆成多个任务,并提供一个反查接口,使用简单
缺点:
  • 事务消息的回查没有好的方案,极端情况可能出现数据错误
  • 不支持回滚
适用场景:
  • 适用于可异步执行的业务,且后续操作无需回滚的业务

3.8 最大努力通知

发起通知方通过一定的机制最大努力将业务处理结果通知到接收方。具体包括:有一定的消息重复通知机制。因为接收通知方可能没有接收到通知,此时要有一定的机制对消息重复通知。 消息校对机制。如果尽最大努力也没有通知到接收方,或者接收方消费消息后要再次消费,此时可由接收方主动向通知方查询消息信息来满足需求。
本地消息表方案和事务消息方案会保证最终一定执行成功,最大努力通知方案则不一定保证最终一定会成功,即可能消息接收不到,此时需要接收通知方主动调用发起通知方的接口查询业务处理结果,通知的可靠性关键在接收通知方。
解决方案上,最大努力通知需要:
  • 提供接口,让被通知方能够通过接口查询业务处理结果
  • 消息队列ACK机制,消息队列按照间隔1min、5min、10min、30min、1h、2h、5h、10h的方式,逐步拉大通知间隔 ,直到达到通知要求的时间窗口上限。之后不再通知
适用场景:
  • 最大努力通知适用于业务通知类型,例如微信交易的结果,就是通过最大努力通知方式通知各个商户,既有回调通知,也有交易查询接口。最大努力通知更多是业务上的设计,在基础设施层,可以直接使用二阶段消息,或者事务消息、本地消息表

3.9 AT事务模式

这是阿里开源项目 seata 中的一种事务模式,是 seata 主推的事务模式,在蚂蚁金服也被称为FMT。
AT 从原理上面看,与 XA 的设计有很多相近之处。XA 是数据库层面实现的二阶段提交, AT 则是应用/驱动层实现的二阶段提交。
AT的角色和 XA 一样分为3个,但是起了不一样的名称,大家注意分辨:
  • RM 资源管理器,是业务服务,负责本地数据库的管理,与 XA 中的 RM 一致
  • TC 事务协调器,是 Seata 服务器,负责全局事务的状态管理,负责协调各个事务分支的执行,相当于 XA 中的 TM
  • TM 事务管理器,是业务服务,负责全局事务的发起,相当于 XA 中的 APP
AT 的第一阶段为 prepare,它在这一阶段会完成以下事情:
  1. RM 侧,用户开启本地事务
  1. RM 侧,用户每进行一次业务数据修改,假设是一个update语句,那么 AT 会做以下内容:
    1. 根据update的条件,查询出修改前的数据,该数据称为BeforeImage
    2. 执行update语句,根据BeforeImage中的主键,查询出修改后的数据,该数据称为AfterImage
    3. 将BeforeImage和AfterImage保存到一张undolog表
    4. 将BeforeImage中的主键以及表名,该数据称为lockKey,记录下来,留待后续使用
  1. RM 侧,用户提交本地事务时,AT 会做以下内容:
    1. 将2.4中记录的所有的lockKey,注册到 TC(即事务管理器seata)上
    2. 3.1中的注册处理会检查 TC 中,是否已存在冲突的主键+表名,如果有冲突,那么AT会睡眠等待后重试,没有冲突则保存
    3. 3.1成功完成后,提交本地事务
如果 AT 的第一阶段所有分支都没有错误,那么会进行第二阶段的commit,AT 会做以下内容:
  1. TC 会将当前这个全局事务所有相关的lockKey删除
  1. TC 通知与当前这个全局事务相关的所有业务服务,告知全局事务已成功,可以删除undolog中保存的数据
  1. RM 收到通知后,删除undolog中的数据
如果 AT 的第一阶段有分支出错,那么会进行第二阶段的rollback,AT 会做以下内容:
  1. TC 通知与当前这个全局事务相关的所有业务服务,告知全局事务失败,执行回滚
  1. RM 收到通知后,对本地数据的修改进行回滚,回滚原理如下:
    1. 从undolog中取出修改前后的BeforeImage和AfterImage
    2. 如果AfterImage与数据库中的当前记录校验一致,那么使用BeforeImage中的数据覆盖当前记录
    3. 如果AfterImage与数据库中的当前记录不一致,那么这个时候发生了脏回滚,此时需要人工介入解决
  1. TC 待全局事务所有的分支,都完成了回滚,TC 将此全局事务所有的lockKey删除
参考这篇文章

3.10 各分布式事务方案适用场景总结

总结一下各个方案的常见的使用场景:
  • Saga 事务:较适用于长事务,且补偿动作容易处理的场景。
  • 2PC/3PC:依赖于数据库的 XA 机制,XA 方案一般适用于单系统跨多个数据库的场景(多数据源),跨多个系统实现起来就比较复杂,所以微服务场景下一般不会使用 XA 方案。
  • TCC:适用于执行时间确定且较短,实时性要求高,对数据一致性要求高,比如互联网金融企业最核心的三个服务:交易、支付、账务。
  • 本地消息表/MQ 事务:都适用于事务中参与方支持操作幂等,对一致性要求不高,业务上能容忍数据不一致到一个人工检查周期,事务涉及的参与方、参与环节较少,业务上有对账/校验系统兜底。
  • 最大努力通知:适用于不太核心的一些业务例如短信通知、微信交易结果通知,通过最大努力通知方式通知各个商户,既有回调通知,也有交易查询接口。

4、分布式事务框架

分布式事务框架:
  • 阿里Seata:支持多种事务模式,分别有AT、TCC、SAGA 和 XA
  • tcc-transaction
  • Hmily
  • ByteTCC
  • EasyTransaction
  • JTA + Atomiko:XA 常用的技术框架

参考

分布式一致性算法:Paxos算法Mysql集群篇:分表分表总结
mcbilla
mcbilla
一个普通的干饭人🍚
Announcement
type
status
date
slug
summary
tags
category
icon
password
🎉欢迎来到飙戈的博客🎉
-- 感谢您的支持 ---
👏欢迎学习交流👏