分布式事务理论基础

事务简介

事务(Transaction)是访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。在关系数据库中,一个事务由一组 SQL 语句组成。事务应该具有4个属性:原子性、一致性、隔离性、持久性。这四个属性通常称为 ACID 特性。

  • 原子性(atomicity):一个事务是一个不可分割的工作单位,事务中包括的操作要么都做,要么都不做。
  • 一致性(consistency):事务必须是使数据库从一个一致性状态变到另一个一致性状态,事务的中间状态不能被观察到的。
  • 隔离性(isolation):一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。隔离性又分为四个级别:读未提交(read uncommitted)、读已提交(read committed,解决脏读)、可重复读(repeatable read,解决虚读)、串行化(serializable,解决幻读)。
  • 持久性(durability):持久性也称永久性(permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。

任何事务机制在实现时,都应该考虑事务的ACID特性,包括:本地事务、分布式事务。

Spring 提供了一个PlatformTransactionManager接口,其有2个重要的实现类:

  • DataSourceTransactionManager:用于支持本地事务,事实上,其内部也是通过操作java.sql.Connection来开启、提交和回滚事务。
  • JtaTransactionManager:用于支持分布式事务,其实现了JTA规范,使用XA协议进行两阶段提交。
    需要注意的是,这只是一个代理,我们需要为其提供一个JTA provider,一般是Java EE容器提供的事务协调器(Java EE server’s transaction coordinator),也可以不依赖容器,配置一个本地的JTA provider。

XA规范与DTP模型

X/Open,即现在的 open group,是一个独立的组织,主要负责制定各种行业技术标准。

官网地址:http://www.opengroup.org/

X/Open 组织主要由各大知名公司或者厂商进行支持,这些组织不光遵循 X/Open 组织定义的行业技术标准,也参与到标准的制定。

就分布式事务处理(Distributed Transaction Processing,简称DTP)而言,X/Open主要提供了以下参考文档:
《Distributed Transaction Processing: Reference Model》
分布式事务处理参考模型,下载地址:http://pubs.opengroup.org/onlinepubs/9294999599/toc.pdf
《Distributed Transaction Processing: The XA Specification》
分布式事务处理XA规范,下载地址:http://pubs.opengroup.org/onlinepubs/009680699/toc.pdf

XA规范

在 DTP 本地模型实例中,由 AP、RMs 和 TM 组成,不需要其他元素。AP、RM 和 TM 之间,彼此都需要进行交互,如下图所示:
在这里插入图片描述
这张图中(1)表示 AP-RM 的交互接口,(2)表示 AP-TM 的交互接口,(3)表示 RM-TM 的交互接口。关于这张图,XA 规范有以下描述:

The subject of this X/Open specification is interface (3) in the diagram above, the XA interface by which TMs and RMs interact.
For more details on this model and diagram, including detailed definitions of each component, see the referenced DTP guide.

也就是说XA规范最主要的作用就是定义了 RM-TM 的交互接口,下图更加清晰了演示了 XA 规范在 DTP 模型中发挥作用的位置,从图中可以看出来,XA 仅仅出现在 RM 和 TM 的连线上:
在这里插入图片描述
XA 规范除了定义的 RM-TM 交互的接口(XA Interface)之外,还对两阶段提交协议进行了优化。 一些读者可能会误认为两阶段提交协议是在 XA 规范中提出来的。事实上:

  • 两阶段协议(two-phase commit)是在 OSI TP 标准中提出的。
  • 在 DTP 参考模型(《Distributed Transaction Processing: Reference Model》)中,指定了全局事务的提交要使用两阶段协议(two-phase commit)。
  • XA 规范(《Distributed Transaction Processing: The XA Specification》)只是定义了两阶段提交协议中需要使用到的接口,也就是上述提到的 RM-TM 交互的接口,因为两阶段提交过程中的参与方,只有 TM 和 RMs。

参见《Distributed Transaction Processing: Reference Model》第3版 2.1节,原文如下:

Commitment Protocol
A commitment protocol is the synchronisation that occurs at transaction completion.
The X/Open DTP Model follows the two-phase commit with presumed rollback1 protocol defined in the referenced OSI TP standards.
A description of the basic protocol is given in Section 3.4.3 on page 13.
In certain cases, a global transaction may be completed heuristically.
Heuristic transaction completion is described in Section 3.4.5 on page 14.

XA接口

XA规范中定义的RM和TM交互的接口如下图所示:
在这里插入图片描述

为什么很少用XA

在 IBM 大型机上基于 CICS 很多跨资源是基于 XA 协议实现的分布式事务,事实上 XA 也算分布式事务处理的规范了,但在为什么互联网中很少使用,究其原因我觉得有以下几个:

  • 性能不高(阻塞性协议、增加响应时间、锁时间、死锁)
  • 数据库支持不完善(MySQL 5.7之前都有缺陷)
  • 协调者依赖独立的J2EE中间件(早期重量级Webogic、Jboss,后期轻量级Atomikos、Narayana和Bitronix)
  • 运维复杂,DBA缺少这方面经验
  • 并不是所有资源都支持XA协议
  • 大厂懂所以不使用,小公司不懂所以不敢用

准确讲XA是一个规范、协议,它只是定义了一系列的接口,只是目前大多数实现XA的都是数据库或者 MQ,所以提起XA往往多指基于资源层的底层分布式事务解决方案。
其实现在也有些数据分片框架或者中间件也支持XA协议,毕竟它的兼容性、普遍性更好。

两阶段提交的提升

基于数据库的 XA 协议本质上就是两阶段提交,但由于性能原因在互联网高并发场景下并不适用。如果数据库只能保证本地 ACID 时,那么其中出现交易异常后,如何实现整个交易原子性 A,从而保证一致性 C 呢?另外在处理过程中如何保证隔离性呢?
最直接的方法就是按照逻辑依次调用服务,但出现异常怎么办?那就对那些已经成功的进行补偿,补偿成功就一致了,这种朴素的模型就是 Saga。但 Saga 这种方式并不能保证隔离性,于是出现了 TCC。
在实际交易逻辑前先做业务检查、对涉及到的业务资源进行“预留”,或者说是一种“中间状态”,如果都预留成功则完成这些预留资源的真正业务处理,典型的如票务座位等场景。当然还有像 Ebay 提出的基于消息表,即可靠消息最终一致模型,但本质上这也属于 Saga 模式的一种特定实现,它的关键点有两个:

  1. 基于应用共享事务记录执行轨迹
  2. 然后通过异步重试确保交易最终一致(这也使得这种方式不适用那些业务上允许补偿回滚的场景)

这类分布式事务场景并不是微服务才出现的,在 SOA 时代其实就有了,常见的 Saga、TCC、可靠消息最终一致等模型也都是很多年前就有了,只是最近几年随着微服务兴起,这些方案又重新被人关注了起来。

仔细对比这些方案与 XA,会发现这些方案本质上都是将两阶段提交从资源层提升到了应用层

  • Saga 的核心就是补偿。一阶段就是服务的正常顺序调用(数据库事务正常提交),如果都执行成功,则第二阶段则什么都不做;但如果其中有执行发生异常,则依次调用其补偿服务(一般多逆序调用未已执行服务的反交易)来保证整个交易的一致性。应用实施成本一般。
  • TCC 的特点在于业务资源检查与加锁。一阶段进行校验,资源锁定,如果第一阶段都成功,二阶段对锁定资源进行交易逻辑;否则,对锁定资源进行释放。应用实施成本较高。
  • 基于可靠消息最终一致。一阶段服务正常调用,同时同步事务记录消息表,二阶段则进行消息的投递和消费。应用实施成本较低。

具体到基于这些模型实现的分布式事务框架,也多借鉴了 DTP(Distributed Transaction Processing)模型。

DTP模型

模型元素

在《Distributed Transaction Processing: Reference Model》第3版中,规定了构成 DTP 模型的5个基本元素:

  1. 应用程序(Application Program ,简称AP):用于定义事务边界(即定义事务的开始和结束),并且在事务边界内对资源进行操作。
  2. 资源管理器(Resource Manager,简称RM):如数据库、文件系统等,并提供访问资源的方式。
  3. 事务管理器(Transaction Manager ,简称TM):负责分配事务唯一标识,监控事务的执行进度,并负责事务的提交、回滚等。
  4. 通信资源管理器(Communication Resource Manager,简称CRM):控制一个 TM 域(TM Domain)内或者跨 TM 域的分布式应用之间的通信。
  5. 通信协议(Communication Protocol,简称CP):提供 CRM 提供的分布式应用节点之间的底层通信服务。

其中通信资源管理器(Communication Resource Managerr,简称CRM)和通信协议(Communication Protocol,简称CP)功能相似。

模型实例

一个 DTP 模型实例,至少有3个组成部分:AP、RMs 和 TM。如下所示:
在这里插入图片描述
这张图类似于我们之前提到的跨库事务的概念,即单个应用需要操作多个库。

在这里就是一个AP需要操作多个RM上的资源,AP 通过 TM 来声明一个全局事务,然后操作不同的 RM 上的资源,最后通知TM来提交或者回滚全局事务。

特别的,如果分布式事务需要跨多个应用,类似于我们前面提到的分布式事务场景中的服务化,那么每个模型实例中,还需要额外的加入一个通信资源管理器 CRM。
在这里插入图片描述
CRM 作为多个模型实例之间通信的桥梁,主要作用如下:

  1. 基本的通信能力:从这个角度,可以将 CRM 类比为 RPC 框架,模型实例之间通过 RPC 调用实现彼此的通信。这一点体现在 AP、CRM 之间的连线。
  2. 事务传播能力:与传统 RPC 框架不同的是,CRM底层采用 OSI TP(Open Systems Interconnection - Distributed Transaction Processing)通信服务,因此CRM具备事务传播能力。这一点体现在 TM、CRM 之间的连线。

事务管理器作用域

一个事务管理器作用域(TM domain)中由一个或者多个模型实例组成,这些模型实例使用的都是同一个 TM,但是操作的 RMs 各不相同,由 TM 来统一协调这些模型实例共同参与形成的全局事务(Global Transaction)。

下图展示了一个由四个模型实例组成的 TM Domain,这四个模型实例使用的都是同一个事务管理器 TM1:
在这里插入图片描述
TM domain 只是列出了最终参与到一个全局事务中,有哪些模型实例,并不关心这些模型实例之间的关系。

不过显然的,当一个 TM domain 中存在多个模型实例时,模型实例彼此之间存在一定的层级调用关系。这就是全局事务的树形结构。

全局事务树形结构

当一个事务管理器作用域(TM domain)中,存在多个模型实例时,会形成一种树形调用关系,如下图所示:
在这里插入图片描述
其中发起分布式事务的模型实例称之为 root 节点,或者称之为事务的发起者,其他的模型实例可以统称为事务的参与者。事务发起者负责开启整个全局事务,事务参与者各自负责执行自己的事务分支。

而从模型实例之间的相互调用关系来说,调用方称之为上游节点(Superior Node),被调用方称之为下游节点(Subordinate Node)。

通过对DTP模型的介绍,我们可以看出来,之前提到的分布式事务的几种典型场景实际上在DTP模型中都包含了,甚至比我们考虑的还复杂。
DTP模型从最早提出到现在已经有接近30年,到如今依然适用,不得不佩服模型设计者的远见。

两阶段提交协议(2PC)

两阶段提交协议(Two Phase Commit)不是在XA规范中提出的,但是XA规范对其进行了优化,从字面意思来理解,就是将提交(Commit)过程划分为2个阶段(Phase):
在这里插入图片描述

In Phase 1, the TM asks all RMs to prepare to commit (or prepare) transaction branches. 
在阶段1中,TM要求所有RM准备提交(或准备)事务分支。
This asks whether the RM can guarantee its ability to commit the transaction branch. 
这询问RM是否可以保证其提交事务分支的能力。
An RM may have to query other entities internal to that RM.
RM可能必须查询该RM内部的其他实体。
If an RM can commit its work, it records stably the information it needs to do so, then replies affirmatively. 
如果RM可以提交其工作,则它将稳定地记录所需的信息,然后做出肯定的答复。
A negative reply reports failure for any reason. 
否定答复出于任何原因都会报告失败。
After making a negative reply and rolling back its work, the RM can discard any knowledge it has of the transaction branch.
在做出否定答复并回滚其工作之后,RM可以放弃其对事务分支的任何了解。

In Phase 2, the TM issues all RMs an actual request to commit or roll back the transaction branch, as the case may be. 
在阶段2中,TM视情况而定向所有RM发出提交或回滚事务分支的实际请求。
(Before issuing requests to commit, the TM stably records the fact that it decided to commit, as well as a list of all involved RMs.) 
(在发出提交请求之前,TM会稳定地记录它决定提交的事实以及所有涉及的RM的列表。)
All RMs commit or roll back changes to shared resources and then return status to the TM. 
所有RM提交或回滚对共享资源的更改,然后将状态返回给TM。
The TM can then discard its knowledge of the global transaction.
然后,TM可以放弃其对全局事务的了解。

Prepare阶段

TM通知各个 RM prepare 提交它们的事务分支。
如果 RM 判断自己进行的工作可以被提交,那就对工作内容进行持久化,再给 TM 肯定答复;要是发生了其他情况,那给 TM 的都是否定答复。在发送了否定答复并回滚了工作后,RM 就可以丢弃这个事务分支信息。

Commit阶段

TM 根据阶段1各个 RM prepare 的结果,决定是提交还是回滚事务。
如果所有的 RM 都 prepare 成功,那么 TM 通知所有的 RM 进行提交;如果有 RM prepare 失败的话,则 TM 通知所有 RM 回滚自己的事务分支。

XA规范对两阶段提交协议有2点优化:

Protocol Optimisations
协议优化
• Read-only
只读
An RM can respond to the TM’s prepare request by asserting that the RM was not asked to update shared resources in this transaction branch. 
RM可以通过断言没有要求RM更新此事务分支中的共享资源来响应TM的准备请求。
This response concludes the RM’s involvement in the transaction; the Phase 2 dialogue between the TM and this RM does not occur. 
该回应表明,RM参与了交易; TM和此RM之间不会发生第二阶段对话。
The TM need not stably record, in its list of participating RMs, an RM that asserts a read-only role in the global transaction.
TM不必在其参与的RM列表中稳定地记录一个在全局事务中具有只读角色的RM。
However, if the RM returns the read-only optimisation before all work on the global transaction is prepared, global serialisability cannot be guaranteed. 
但是,如果RM在准备有关全局事务的所有工作之前返回只读优化,则不能保证全局可串行性。
This is because the RM may release transaction context, such as read locks, before all application activity for that global transaction is finished.
这是因为RM可能会在完成该全局事务的所有应用程序活动之前释放事务上下文,例如读锁。
• One-phase Commit
一阶段提交
A TM can use one-phase commit if it knows that there is only one RM anywhere in the DTP system that is making changes to shared resources. 
如果TM知道DTP系统中的任何地方都在更改共享资源,则可以使用一阶段提交。
In this optimisation, the TM makes its Phase 2 commit request without having made a Phase 1 prepare request. 
在这种优化中,TM发出了第2阶段提交请求,而没有提出第1阶段准备请求。
Since the RM decides the outcome of the transaction branch and forgets about the transaction branch before returning to the TM, 
由于RM决定交易分支的结果,并且在返回TM之前忘记了交易分支,
there is no need for the TM to record stably these global transactions and, in some failure cases, the TM may not know the outcome.
TM不需要稳定地记录这些全局事务,并且在某些失败情况下,TM可能不知道结果。

只读断言
在 Phase 1中,RM 可以断言“我这边不涉及数据增删改”来答复 TM 的 prepare 请求,从而让这个 RM 脱离当前的全局事务,从而免去了Phase 2。

这种优化发生在其他 RM 都完成 prepare 之前的话,使用了只读断言的 RM 早于 AP 其他动作(比如说这个 RM 返回那些只读数据给 AP)前,就释放了相关数据的上下文(比如读锁之类的),这时候其他全局事务或者本地事务就有机会去改变这些数据,结果就是无法保障整个系统的可序列化特性,通俗点说那就会有脏读的风险。

一阶段提交
如果需要增删改的数据都在同一个 RM 上,TM 可以使用一阶段提交,跳过两阶段提交中的 Phase 1,直接执行 Phase 2。

这种优化的本质是跳过 Phase 1,RM 自行决定了事务分支的结果,并且在答复 TM 前就清除掉事务分支信息。对于这种优化的情况,TM 实际上也没有必要去可靠的记录全局事务的信息,在一些异常的场景下,此时 TM 可能不知道事务分支的执行结果。

两阶段提交协议(2PC)存在的问题

二阶段提交看起来确实能够提供原子性的操作,但是二阶段提交还是有几个缺点的:

1、同步阻塞问题
两阶段提交方案下全局事务的 ACID 特性,是依赖于 RM 的。例如MySQL 5.7官方文档关于对 XA 分布式事务的支持有以下介绍:

A global transaction involves several actions that are transactional in themselves, but that all must either complete successfully as a group, or all be rolled back as a group.
In essence, this extends ACID properties “up a level” so that multiple ACID transactions can be executed in concert as components of a global operation that also has ACID properties.
(As with nondistributed transactions, SERIALIZABLE may be preferred if your applications are sensitive to read phenomena. EPEATABLE READ may not be sufficient for distributed transactions.)

大致含义是说,一个全局事务内部包含了多个独立的事务分支,这一组事务分支要不都成功,要不都失败。各个事务分支的ACID特性共同构成了全局事务的 ACID 特性。也就是将单个事务分支的支持的 ACID 特性提升一个层次(up a level)到分布式事务的范畴。

即使在非分布事务中(即本地事务),如果对操作读很敏感,我们也需要将事务隔离级别设置为SERIALIZABLE。
而对于分布式事务来说,更是如此,可重复读隔离级别不足以保证分布式事务一致性。
也就是说,如果我们使用 MySQL 来支持 XA 分布式事务的话,那么最好将事务隔离级别设置为 SERIALIZABLE。
而 SERIALIZABLE(串行化)是四个事务隔离级别中最高的一个级别,也是执行效率最低的一个级别。

2、单点故障
由于协调者的重要性,一旦协调者 TM 发生故障。参与者 RM 会一直阻塞下去。尤其在第二阶段,协调者发生故障,那么所有的参与者还都处于锁定事务资源的状态中,而无法继续完成事务操作。
(如果是协调者挂掉,可以重新选举一个协调者,但是无法解决因为协调者宕机导致的参与者处于阻塞状态的问题)

3、数据不一致
在两阶段提交的阶段二中,当协调者向参与者发送 commit 请求之后,发生了局部网络异常或者在发送 commit 请求过程中协调者发生了故障,这会导致只有一部分参与者接受到了 commit 请求,而在这部分参与者接到 commit 请求之后就会执行 commit 操作,但是其他部分未接到 commit 请求的机器则无法执行事务提交,于是整个分布式系统便出现了数据不一致性的现象。

4、超时问题
同样在二阶段提交的阶段二中,当协调者向参与者发送 commit 请求之后,发生了局部网络异常导致部分参与者一直没有返回 committed 响应,而其他参与者在接到 commit 请求之后就会执行 commit 操作,这时协调者就不知道这部分参与者的提交状态。

由于二阶段提交存在着诸如同步阻塞、单点问题等缺陷,所以,研究者们在两阶段提交的基础上做了改进,提出了三阶段提交。

三阶段提交协议(3PC)

三阶段提交(3PC),是两阶段提交(2PC)的改进版本。与两阶段提交不同的是,三阶段提交有两个改动点:

  1. 引入超时机制。同时在协调者和参与者中都引入超时机制。
  2. 在第一阶段和第二阶段中插入一个准备阶段。保证了在最后提交阶段之前各参与节点的状态是一致的。

也就是说,除了引入超时机制之外,3PC 把 2PC 的准备阶段再次一分为二,这样三阶段提交就有 CanCommit、PreCommit、DoCommit 三个阶段。
在这里插入图片描述

CanCommit阶段

3PC 的 CanCommit 阶段其实和2PC的准备阶段很像。协调者向参与者发送 commit 请求,参与者如果可以提交就返回 Yes 响应,否则返回 No 响应。

  1. 事务询问:协调者向参与者发送 CanCommit 请求,询问是否可以执行事务提交操作,然后开始等待参与者的响应
  2. 响应反馈:参与者接到 CanCommit 请求之后,正常情况下,如果其自身认为可以顺利执行事务,则返回 Yes 响应,并进入预备状态,否则反馈 No。

PreCommit阶段

协调者根据参与者的反应情况来决定是否可以执行事务的 PreCommit 操作。根据响应情况,有以下两种可能:

1、假如协调者从所有的参与者获得的反馈都是 Yes 响应,那么就会执行事务的预执行

  • 发送预提交请求:协调者向参与者发送 PreCommit 请求,并进入 Prepared 阶段
  • 事务预提交:参与者接收到 PreCommit 请求后,会执行事务操作,并将 undo 和 redo 信息记录到事务日志中
  • 响应反馈:如果参与者成功的执行了事务操作,则返回 ACK 响应,同时开始等待最终指令

2、假如有任何一个参与者向协调者发送了 No 响应,或者等待超时之后,协调者都没有接到参与者的响应,那么就执行事务的中断

  • 发送中断请求:协调者向所有参与者发送 abort 请求
  • 中断事务:参与者收到来自协调者的 abort 请求之后(或超时之后,仍未收到协调者的请求),执行事务的中断

DoCommit阶段

该阶段进行真正的事务提交,也可以分为以下两种情况:
1、执行提交

  • 发送提交请求:协调接收到参与者发送的 ACK 响应,那么他将从预提交状态进入到提交状态,并向所有参与者发送 doCommit 请求
  • 事务提交:参与者接收到 doCommit 请求之后,执行正式的事务提交,并在完成事务提交之后释放所有事务资源
  • 反馈结果:事务提交完之后,向协调者发送 ACK 响应
  • 完成事务:协调者接收到所有参与者的 ACK 响应之后,完成事务

2、中断事务:协调者没有接收到参与者发送的ACK响应(可能是接受者发送的不是ACK响应,也可能响应超时),那么就会执行中断事务

  • 发送中断请求:协调者向所有参与者发送 abort 请求
  • 事务回滚:参与者接收到 abort 请求之后,利用其在阶段二记录的 undo 信息来执行事务的回滚操作,并在完成回滚之后释放所有的事务资源
  • 反馈结果:参与者完成事务回滚之后,向协调者发送 ACK 消息
  • 中断事务:协调者接收到参与者反馈的 ACK 消息之后,执行事务的中断

两阶段与三阶段的区别

相对于2PC,3PC主要解决的是单点故障问题,并减少阻塞,因为一旦参与者无法及时收到来自协调者的信息之后,他会默认执行 commit,而不会一直持有事务资源并处于阻塞状态。

但是这种机制也会导致数据一致性的问题,因为网络抖动是不可避免和无法预测的,协调者发送的 abort 响应没有及时被参与者接收到,那么参与者在等待超时之后执行了 commit 操作这就和其他接到 abort 命令并执行回滚的参与者之间存在数据不一致的情况。

小结

了解了2PC和3PC之后,我们可以发现,无论是两阶段提交还是三阶段提交都无法彻底解决分布式事务数据的一致性问题。

Google Chubby的作者Mike Burrows说过, there is only one consensus protocol, and that’s Paxos” – all other approaches are just broken versions of Paxos.
意即世上只有一种一致性算法,那就是Paxos,所有其他一致性算法都是Paxos算法的不完整版

BASE理论与柔性事务

经典的分布式系统CAP理论

2000年7月,加州大学伯克利分校的 Eric Brewer 教授在 ACM PODC 会议上提出 CAP 猜想。Brewer 认为在设计一个大规模的分布式系统时会遇到三个特性:
1、一致性(Consistency)
一致性指"all nodes see the same data at the same time",即更新操作成功并返回客户端完成后,所有节点在同一时间的数据完全一致,不能存在中间状态。

关于一致性,如果能时刻保证客户端看到的数据都是一致的,那么称之为强一致性。如果允许存在中间状态,只要求经过一段时间后,数据最终是一致的,则称之为最终一致性。此外,如果允许存在部分数据不一致,那么就称之为弱一致性。

2、可用性(Availability)
可用性是指系统提供的服务必须一直处于可用的状态,对于用户的每一个操作请求总是能够在有限的时间内返回结果。

“有限的时间内”是指,对于用户的一个操作请求,系统必须能够在指定的时间内返回对应的处理结果,如果超过了这个时间范围,那么系统就被认为是不可用的。

“返回结果”是可用性的另一个非常重要的指标,它要求系统在完成对用户请求的处理后,返回一个正常的响应结果,不论这个结果是成功还是失败。

3、分区容错性(Partition-tolerance)
分布式系统在遇到任何网络分区故障的时候,仍然需要能够保证对外提供满足一致性和可用性的服务,除非是整个网络环境都发生了故障。

而一个分布式系统最多只能满足其中的2项。2年后,麻省理工学院的 Seth Gilbert 和 Nancy Lynch 从理论上证明了 CAP。之后,CAP 理论正式成为分布式计算领域的公认定理。
在这里插入图片描述
既然一个分布式系统无法同时满足一致性、可用性、分区容错性三个特点,我们就需要抛弃一个。
需要明确的一点是,对于一个分布式系统而言,分区容错性是一个最基本的要求。因为既然是一个分布式系统,那么分布式系统中的组件必然需要被部署到不同的节点,否则也就无所谓分布式系统了。而对于分布式系统而言,网络问题又是一个必定会出现的异常情况,因此分区容错性也就成为了一个分布式系统必然需要面对和解决的问题。
因此系统架构师往往需要把精力花在如何根据业务特点在 C(一致性)和 A(可用性)之间寻求平衡。

而前面我们提到的 X/Open XA 两阶段提交协议的分布式事务方案,强调的就是一致性;由于可用性较低,实际应用的并不多。

而基于 BASE 理论的柔性事务,强调的是可用性,目前大行其道,大部分互联网公司采可能会优先采用这种方案。

BASE理论

BASE 理论是对 CAP 理论的延伸,核心思想是即使无法做到强一致性(Strong Consistency,CAP的一致性就是强一致性),但应用可以采用适合的方式达到最终一致性(Eventual Consitency)。

BASE是Basically Available(基本可用)Soft state(软状态)Eventually consistent(最终一致性)三个短语的缩写。

  1. 基本可用(Basically Available)指分布式系统在出现不可预知故障的时候,允许损失部分可用性。
  2. 软状态(Soft State)指允许系统中的数据存在中间状态,并认为该中间状态的存在不会影响系统的整体可用性。
  3. 最终一致性(Eventual Consistency)强调的是所有的数据更新操作,在经过一段时间的同步之后,最终都能够达到一个一致的状态。因此,最终一致性的本质是需要系统保证最终数据能够达到一致,而不需要实时保证系统数据的强一致性。

BASE 理论面向的是大型高可用可扩展的分布式系统,和传统的事物 ACID 特性是相反的。它完全不同于 ACID 的强一致性模型,而是通过牺牲强一致性来获得可用性,并允许数据在一段时间内是不一致的,但最终达到一致状态。但同时,在实际的分布式场景中,不同业务单元和组件对数据一致性的要求是不同的,因此在具体的分布式系统架构设计过程中,ACID 特性和 BASE 理论往往又会结合在一起。

柔性事务

最大努力通知

最大努力通知型(Best-effort delivery)是最简单的一种柔性事务,适用于一些最终一致性、时间敏感度低的业务,且被动方处理结果不影响主动方的处理结果。典型的使用场景:如银行通知、商户通知等。

最大努力通知型的实现方案,一般符合以下特点:

  1. 不可靠消息:业务活动主动方,在完成业务处理之后,向业务活动的被动方发送消息,直到通知N次后不再通知,允许消息丢失(不可靠消息)。
  2. 定期校对:业务活动的被动方,根据定时策略,向业务活动主动方查询(主动方提供查询接口),恢复丢失的业务消息。

TCC两阶段补偿型

TCC方案可能是目前最火的一种柔性事务方案了。关于TCC(Try-Confirm-Cancel)的概念,最早是由 Pat Helland 于2007年发表的一篇名为《Life beyond Distributed Transactions:an Apostate’s Opinion》的论文提出。在该论文中,TCC 还是以 Tentative-Confirmation-Cancellation 命名。正式以 Try-Confirm-Cancel 作为名称的是 Atomikos 公司,其注册了 TCC 商标。

国内最早关于 TCC 的报道,应该是 InfoQ 上对阿里程立博士的一篇采访。经过程博士的这一次传道之后,TCC 在国内逐渐被大家广为了解并接受。

Atomikos 公司在商业版本事务管理器 ExtremeTransactions 中提供了 TCC 方案的实现,但是由于其是收费的,因此相应的很多的开源实现方案也就涌现出来,如:TCC-Transaction、ByteTCC、Spring-Cloud-Rest-TCC。

TCC 的作用主要是解决跨服务调用场景下的分布式事务问题:
1、Try阶段:完成所有业务检查(一致性),预留业务资源(准隔离性)
2、Confirm阶段:确认执行业务操作,不做任何业务检查, 只使用 Try 阶段预留的业务资源
3、Cancel阶段:取消 Try 阶段预留的业务资源

TCC两阶段提交和XA两阶段提交比较

在这里插入图片描述
阶段1:
在 XA 中,各个 RM 准备提交各自的事务分支,事实上就是准备提交资源的更新操作(insert、delete、update等)。
而在 TCC 中,是主业务活动请求(try)各个从业务服务预留资源

阶段2:
XA 根据第一阶段每个 RM 是否都 prepare 成功,判断是要提交还是回滚。如果 prepare 都成功,那么就 commit 每个事务分支,反之则 rollback 每个事务分支。
TCC 中,如果在第一阶段所有业务资源都预留成功,那么 confirm 各个从业务服务,否则 cancel 所有从业务服务的资源预留请求。

TCC两阶段提交与XA两阶段提交区别

XA是资源层面的分布式事务,强一致性,在两阶段提交的整个过程中,一直会持有资源的锁。
XA 事务中的两阶段提交内部过程是对开发者屏蔽的,而事务管理器在两阶段提交过程中,从 prepare到 commit/rollback 过程中,资源实际上一直都是被加锁的。如果有其他人需要更新这两条记录,那么就必须等待锁释放。

TCC是业务层面的分布式事务,最终一致性,不会一直持有资源的锁。
TCC 中的两阶段提交并没有对开发者完全屏蔽,也就是说从代码层面,开发者是可以感受到两阶段提交的存在。try、confirm/cancel 在执行过程中,一般都会开启各自的本地事务,来保证方法内部业务逻辑的 ACID 特性。其中:

  1. try 过程的本地事务,是保证资源预留的业务逻辑的正确性。
  2. confirm/cancel 执行的本地事务逻辑确认/取消预留资源,以保证最终一致性,也就是所谓的补偿型事务(Compensation-Based Transactions)。

由于是多个独立的本地事务,因此不会对资源一直加锁。

另外,这里提到 confirm/cancel 执行的本地事务是补偿性事务,关于什么是补偿性事务,atomikos 官网上有以下描述:
在这里插入图片描述
大致含义是,补偿是一个独立的支持 ACID 特性的本地事务,用于在逻辑上取消服务提供者对上一个 ACID 事务造成的影响,对于一个长事务(long-running transaction),与其实现一个巨大的分布式 ACID 事务,不如使用基于补偿性的方案,把每一次服务调用当做一个较短的本地 ACID 事务来处理,执行完就立即提交。

在这里,confirm 和 cancel 就是补偿事务,用于取消 try 阶段对本地事务造成的影响。因为第一阶段 try 只是预留资源,之后必须要明确的告诉服务提供者,这个资源你到底要不要,对应第二阶段的 confirm/cancel。

现在应该明白为什么把TCC叫做两阶段补偿性事务了,提交过程分为2个阶段,第二阶段的confirm/cancel执行的事务属于补偿事务。

TCC事务模型和DTP事务模型比较

在这里插入图片描述
1、TCC 模型中的主业务服务相当于 DTP 模型中的 AP,TCC 模型中的从业务服务相当于 DTP 模型中的 RM。

2、TCC 模型中,从业务服务提供的 try、confirm、cancel 接口相当于 DTP 模型中 RM 提供的prepare、commit、rollback 接口。

XA 协议中规定了 DTP 模型中 RM 需要提供 prepare、commit、rollback 接口给 TM 调用,以实现两阶段提交。而在 TCC 模型中,从业务服务相当于 RM,提供了类似的 try、confirm、cancel 接口。

3、DTP 模型和 TCC 模型中都有一个事务管理器。不同的是:
在 DTP 模型中,阶段1的(prepare)和阶段2的(commit、rollback),都是由 TM 进行调用的。

在 TCC 模型中,阶段1的(try)接口是主业务服务调用(绿色箭头),阶段2的(confirm、cancel)接口是事务管理器 TM 调用(红色箭头)。这就是 TCC 分布式事务模型的二阶段异步化功能,从业务服务的第一阶段执行成功,主业务服务就可以提交完成,然后再由事务管理器框架异步的执行各从业务服务的第二阶段。这里牺牲了一定的隔离性和一致性的,但是提高了长事务的可用性。

TCC事务模型的优缺点

优点:XA 两阶段提交是资源层面的,而 TCC 实际上把资源层面的二阶段提交上提到了业务层面来实现。有效的避免了 XA 两阶段提交占用资源锁时间过长导致的性能低下问题。

缺点:主业务服务和从业务服务都需要进行改造,从业务方改造成本更高。

分布式事务的取舍

严格的 ACID 事务对隔离性的要求很高,在事务执行中必须将所有的资源锁定,对于长事务来说,整个事务期间对数据的独占,将严重影响系统并发性能。因此,在高并发场景中,对 ACID 的部分特性进行放松从而提高性能,这便产生了 BASE 柔性事务。柔性事务的理念则是通过业务逻辑将互斥锁操作从资源层面上移至业务层面。通过放宽对强一致性要求,来换取系统吞吐量的提升。另外提供自动的异常恢复机制,可以在发生异常后也能确保事务的最终一致。

由上可见柔性事务需要应用层进行参与,因此这类分布式事务框架一个首要的功能就是怎么最大程度降低业务改造成本,然后就是尽可能提高性能(响应时间、吞吐量),最好是保证隔离性。

一个好的分布式事务框架应用尽可能满足以下特性:

  1. 业务改造成本低
  2. 性能损耗低
  3. 隔离性保证完整

但如同 CAP,这三个特性是相互制衡的,往往只能满足其中两个,我们可以画一个三角约束:
在这里插入图片描述
当然如果我们要自己设计一个分布式事务框架,还需要考虑很多其它特性,在明确目标场景偏好后进行权衡取舍,这些特性包括但不限于以下方面:

  • 业务侵入性(基于注解、XML,补偿逻辑)
  • 隔离性(写隔离/读隔离/读未提交,业务隔离/技术隔离)
  • TM/TC 部署形态(单独部署、与应用部署一起)
  • 错误恢复(自动恢复、手动恢复)
  • 性能(回滚的概率、付出的代价、响应时间、吞吐)
  • 高可用(注册中心、数据库)
  • 持久化(数据库、文件、多副本一致算法)
  • 同步/异步(2PC执行方式)
  • 日志清理(自动、手动)

结语

分布式事务一直是业界难题,难在于 CAP 定理,在于分布式系统 8 大错误假设,在于 FLP 不可能原理,在于我们习惯于单机事务 ACID 做对比。

无论是数据库领域 XA、Google percolator 或 Calvin 模型,还是微服务下 Saga、TCC、可靠消息等方案,都没有完美解决分布式事务问题,它们不过是各自在性能、一致性、可用性等方面做取舍,寻求某些场景偏好下的权衡。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

从入门到脱发

你的鼓励是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值