分布式数据库(三)

小注

极客时间 - 分布式数据库 30 讲

原子性(协议保证)

Atomicity: Either all the changes from the transaction occur (writes, and messages sent), or none occur.

原子性就是要求事务只有两个状态:

  • 一是成功,也就是所有操作全部成功;
  • 二是失败,任何操作都没有被执行,及时过程中已经执行了部分操作,也要保证回滚这些操作。

多数情况下事务是由多个操作构成的序列。而分布式事务原子性的外在表现与事务原子性一致,但前者要涉及多个物理节点,而且增加了网络这个不确定性因素。为了协调内部的多项操作,对外表现出统一的成功或失败状态,这需要一系列的算法或协议保证。

面向应用层的 TCC

TCC 协议是『面向应用层』中比较典型的协议,是Try、Confirm 和 Cancel 三个单词的缩写,它们是事务过程中的三个操作。

用一个例子来阐明 TCC 处理流程:

系统架构图如下:

TCC 举例架构图

银行的存款系统是单元化架构的,也就是说,系统由多个单元构成,每个单元包含了一个存款系统的部署实例和对应的数据库,专门为某一个地区的用户服务。比如,单元 A 为北京用户服务,单元 B 为上海用户服务。

单元化架构的好处是每个单元只包含了部分用户,这样运行负载比较小,而且一旦出现问题,也只影响到少部分客户,可以提升整个存款系统的可靠性。

不过这种架构也有局限性。那就是虽然单元内的客户转账非常容易,但是跨单元的转账需要引入额外的处理机制,而 TCC 就是一种常见的选择。

TCC 的整个过程由两类角色参与,一类是事务管理器,只能有一个;另一类是事务参与者,也就是具体的业务服务,可以是多个,每个服务都要提供 Try、Confirm 和 Cancel 三个操作。

下面是 TCC 的具体执行过程。

A 和 B 的账户分别在单元 A 和单元 B 上。现在 A 的账户余额是 4,900 元,需要给 B 转 2,000 元,一个正常流程如下:

TCC 正常流程

第一阶段,事务管理器会发出 Try 操作,要求进行资源的检查和预留。也就是说,单元 A 要检查 A 账户余额并冻结其中的 2,000 元,而单元 B 要确保 B 的账户合法,可以接收转账。在这个阶段,两者账户余额始终不会发生变化。

第二阶段,因为参与者都已经做好准备,所以事务管理器会发出 Confirm 操作,执行真正的业务,完成 2,000 元的划转。

但是很不幸,B 账户是无法接收转账的非法账户,处理过程就变成下面的样子:

TCC 失败流程

第一阶段,事务管理器发出 Try 指令,单元 B 对 B 账户的检查没有通过,回复 No。而单元 A 检查 A 账户余额正常,并冻结了 2,000 元,回复 Yes。

第二阶段,因为前面有参与者回复 No,所以事务管理器向所有参与者发出 Cancel 指令,让已经成功执行 Try 操作的单元 A 执行 Cancel 操作,撤销在 Try 阶段的操作,也就是单元 A 解除 2,000 元的资金冻结。

从上述流程可以发现,TCC 仅是应用层的分布式事务框架,具体操作完全依赖于业务编码实现,可以做针对性的设计,但是这也意味着业务侵入会比较深。

此外,考虑到网络的不可靠,操作指令必须能够被重复执行,这就要求 Try、Confirm、Cancel 必须是幂等性操作,也就是说,要确保执行多次与执行一次得到相同的结果。显然,这又增加了开发难度。


两阶段提交协议 2PC

2PC 的首次正式提出是在 Jim Gray 1977 年发表的一份文稿中,文稿的题目是 "Notes on Data Base Operating Systems",对当时数据库系统研究成果和实践进行了总结,而 2PC 在工程中的应用还要再早上几年。

2PC 的处理过程也分为准备和提交两个阶段,每个阶段都由事务管理器与资源管理器共同完成。其中,事务管理器作为事务的协调者只有一个,而资源管理器作为参与者执行具体操作允许有多个。

用一个例子来阐明 2PC 处理流程:

系统架构图如下:

2PC 举例架构图

在该银行的存款系统中,所有客户的数据被分散存储在多个数据库实例中,这些数据库实例具有完全相同的表结构。业务逻辑部署在应用服务器上,通过数据库中间件访问底层的数据库实例。数据库中间件作为事务管理器,资源管理器就是指底层的数据库实例。

假设,A 和 B 的数据分别被保存在数据库 D1 和 D2 上。

下面是 2PC 的具体执行过程。

2PC 正常流程

第一阶段是准备阶段,事务管理器首先向所有参与者发送待执行的 SQL,并询问是否做好提交事务的准备(Prepare);参与者记录日志、分别锁定了 A 和 B 的账户,并做出应答,协调者接收到反馈 Yes,准备阶段结束。

第二阶段是提交阶段,如果所有数据库的反馈都是 Yes,则事务管理器会发出提交(Commit)指令。这些数据库接受指令后,会进行本地操作,正式提交更新余额,给 A 的账户扣减 2,000 元,给 B 的账户增加 2,000 元,然后向协调者返回 Yes,事务结束。

那如果 A 的账户出了问题,导致转账失败,处理过程如下:

2PC 失败流程

第一阶段,事务管理器向所有数据库发送待执行的 SQL,并询问是否做好提交事务的准备。

由于 A 之前在银行购买了基金定投产品,按照约定,每月银行会自动扣款购买基金,刚好这个自动扣款操作正在执行,先一步锁定了账户。数据库 D1 发现无法锁定 A 的账户,只能向事务管理器返回失败。

第二阶段,因为事务管理器发现数据库 D1 不具备执行事务的条件,只能向所有数据库发出『回滚』(Rollback)指令。所有数据库接收到指令后撤销第一阶段的操作,释放资源,并向协调者返回 Yes,事务结束。A 和 B 的账户余额均保持不变。


2PC 的三大问题

相比于 TCC,2PC 的优点是借助了数据库的提交和回滚操作,不侵入业务逻辑。但是,它也存在一些明显的问题:

  1. 同步阻塞:执行过程中,数据库要锁定对应的数据行。如果其他事务刚好也要操作这些数据行,那它们就只能等待。其实同步阻塞只是设计方式,真正的问题在于这种设计会导致分布式事务出现高延迟和性能的显著下降。
  2. 单点故障:事务管理器非常重要,一旦发生故障,数据库会一直阻塞下去。尤其是在第二阶段发生故障的话,所有数据库还都处于锁定事务资源的状态中,从而无法继续完成事务操作。
  3. 数据不一致:在第二阶段,当事务管理器向参与者发送 Commit 请求之后,发生了局部网络异常,导致只有部分数据库接收到请求,但是其他数据库未接到请求所以无法提交事务,整个系统就会出现数据不一致性的现象。比如,A 的余额已经能够扣减,但是 B 的余额没有增加,这样就不符合原子性的要求了。

事实上,多数分布式数据库都是在 2PC 协议基础上改进,来保证分布式事务的原子性。这里介绍两个有代表性的 2PC 改进模型,它们分别来自分布式数据库的两大阵营,NewSQL 和 PGXC。


NewSQL 阵营:Percolator

Percolator 来自 Google 的论文 "Large-scale Incremental Processing Using Distributed Transactions and Notifications",因为它是基于分布式存储系统 BigTable 建立的模型,所以可以和 NewSQL 无缝链接。

Percolator 模型同时涉及了隔离性和原子性的处理。这里主要关注原子性的部分。

使用 Percolator 模型的前提是事务的参与者,即数据库,要 支持多版本并发控制(MVCC)。现在主流的单体数据库和分布式数据库都是支持的 MVCC。

在转账事务开始前,A 和 B 的账户分别存储在分片 P1 和 P2 上。

Percolator 初始表

分片的账户表中各有两条记录,第一行记录的指针(write)指向第二行记录,实际的账户余额存储在第二行记录的 Bal. data 字段中。

Bal.data 分为两个部分,冒号前面的是时间戳,代表记录的先后次序;后面的是真正的账户余额。我们可以看到,现在 A 的账户上有 4,900 元,B 的账户上有 300 元。

下面是 Percolator 的具体执行过程。

Percolator 准备阶段

第一,准备阶段,事务管理器向分片发送 Prepare 请求,包含了具体的数据操作要求。

分片接到请求后要做两件事,写日志和添加私有版本。关于私有版本,可以简单理解为,在 lock 字段上写入了标识信息的记录就是私有版本,只有当前事务能够操作,通常其他事务不能读写这条记录。

这里要注意一下,两个分片上的 lock 内容并不一样。

主锁的选择是随机的,参与事务的记录都可能拥有主锁,但一个事务只能有一条记录拥有主锁,其他参与事务的记录在 lock 字段记录了指针信息『primary@Ming.bal』,指向主锁记录。

准备阶段结束的时候,两个分片都增加了私有版本记录,余额正好是转账顺利执行后的数字。

Percolator 提交阶段

第二,提交阶段,事务管理器只需要和拥有主锁的分片通讯,发送 Commit 指令,且不用附带其他信息。

分片 P1 增加了一条新记录时间戳为 8,指向时间戳为 7 的记录,后者在准备阶段写入的主锁也被抹去。这时候 7、8 两条记录不再是私有版本,所有事务都可以看到 A 的余额变为 2,700 元,事务结束。

在提交阶段是不用更新 B 的记录的。因为分片 P2 的最后一条记录,保存了指向主锁的指针。其他事务读取到 B7 这条记录时,会根据指针去查找 A.bal,发现记录已经提交,所以 B 的记录虽然是私有版本格式,但仍然可视为已经生效了。

当然,这种通过指针查找的方式,会给读操作增加额外的工作。如果每个事务都照做,性能损耗就太大了。所以,还会有其他异步线程来更新 B 的余额记录,最终变成下面的样子。

Percolator 最终表


对于 2PC 的问题,Percolator 有以下几点改进:

1、数据不一致

2PC 的一致性问题主要缘自第二阶段,不能确保事务管理器与多个参与者的通讯始终正常。

但在 Percolator 的第二阶段,事务管理器只需要与一个分片通讯,这个 Commit 操作本身就是原子的。所以,事务的状态自然也是原子的,一致性问题被完美解决了。

2、单点故障

Percolator 通过日志和异步线程的方式弱化了这个问题。

一是,Percolator 引入的异步线程可以在事务管理器宕机后,回滚各个分片上的事务,提供了善后手段,不会让分片上被占用的资源无法释放。

二是,事务管理器可以用记录日志的方式使自身无状态化,日志通过共识算法同时保存在系统的多个节点上。这样,事务管理器宕机后,可以在其他节点启动新的事务管理器,基于日志恢复事务操作。

Percolator 模型在分布式数据库的工程实践中被广泛借鉴。比如,分布式数据库 TiDB,完全按照该模型实现了事务处理;CockroachDB 也从 Percolator 模型获得灵感,设计了自己的 2PC 协议。

CockroachDB 的变化在于没有随机选择主锁,而是引入了一张全局事务表,所有分片记录的指针指向了这个事务表中对应的事务记录。


PGXC 阵营:GoldenDB 的一阶段提交

GoldenDB 展现了另外一种改良思路,称之为『一阶段提交』。

GoldenDB 遵循 PGXC 架构,包含了四种角色:协调节点、数据节点、全局事务器和管理节点,其中协调节点和数据节点均有多个。GoldenDB 的数据节点由 MySQL 担任,后者是独立的单体数据库。

GoldenDB 架构图

虽然名字叫『一阶段提交』,但 GoldenDB 的流程依然可以分为两个阶段。

GoldenDB 第一阶段

第一阶段,GoldenDB 的协调节点接到事务后,在全局事务管理器(GTM)的全局事务列表中将事务标记成活跃的状态。这个标记过程是 GoldenDB 的主要改进点,实质是通过全局事务列表来申请资源,规避可能存在的事务竞争。

这样的好处是避免了与所有参与者的通讯,也减少了很多无效的资源锁定动作。

GoldenDB 第二阶段

第二阶段,协调节点把一个全局事务分拆成若干子事务,分配给对应的 MySQL 去执行。如果所有操作成功,协调者节点会将全局事务列表中的事务标记为结束,整个事务处理完成。如果失败,子事务在单机上自动回滚,而后反馈给协调者节点,后者向所有数据节点下发回滚指令。

GoldenDB 的『一阶段提交』,本质上是改变了资源的申请方式,更准确的说法是,并发控制手段从锁调度变为时间戳排序(Timestamp Ordering)。这样,在正常情况下协调节点与数据节点只通讯一次,降低了网络不确定性的影响,数据库的整体性能有明显提升。因为第一阶段不涉及数据节点的操作,也就弱化了数据一致性和单点故障的问题。


小结

  1. 事务的原子性就是让包含若干操作的事务表现得像一个最小粒度的操作,而这个操作一旦被执行只有两种结果,成功或者失败。
  2. 相比于单机事务,分布式事务原子性的复杂之处在于增加了多物理设备和网络的不确定性,需要通过一定的算法和协议来实现。这类协议也有不少,重点介绍了 TCC 和 2PC 这两个常用协议。
  3. TCC 提供了一个应用层的分布式事务框架,它对参与者没有特定要求,但有较强的业务侵入;2PC 是专为数据库这样的资源层设计的,不侵入业务,也是今天分布式数据库主流产品的选择。
  4. 考虑到 2PC 的重要性和人们对其实用价值的误解,又展开说明 2PC 的两种改良模型,分别是 Percolator 和 GoldenDB 的『一阶段提交』。Percolator 将 2PC 第二阶段工作简化到极致,减少了与参与者的通讯,完美解决了一致性问题,同时通过日志和异步线程弱化了单点故障问题。GoldenDB 则改良了 2PC 第一阶段的资源协调过程,将协调者与多个参与者的交互转换为协调者与全局事务管理器的交互,同样达到了减少通讯的效果,弱化了一致性和单点故障的问题。

加餐:2PC 第一阶段『准备阶段』也被称为『投票阶段』,和 Paxos 协议处理阶段的命名相近,2PC 和 Paxos 协议有没有关系,如果有又是什么关系呢?

Paxos 是对单值或者一种状态达成共识的过程,而 2PC 是对多个不同数据项的变更或者多个状态,达成一致的过程。它们是有区别的,Paxos 不能替换 2PC,但它们也是有某种联系的:Paxos Commit。

Paxos Commit 协议是 2006 年在论文 "Consensus on Transaction Commit" 中首次提出的。值得一提的是,这篇论文的两位作者,是 Jim Gray 和 Leslie Lamport。他们分别是数据库和分布式领域的图灵奖获得者,也分别是 2PC 和 Paxos 的提出者。Paxos Commit 的处理过程图如下:

Paxos Commit

Paxos Commit 协议中有四个角色,有两个与 2PC 对应,分别是 TM(Transaction Manager,事务管理者)也就是事务协调者,RM(Resource Manager,资源管理者)也就是事务参与者;另外两个角色与 Paxos 对应,一个是 Leader,一个是 Acceptor。其中,TM 和 Leader 在逻辑上是不可能分的,所以在图中隐去了。因为 Leader 是选举出来的,所以第一个 Leader 标识为 Initial Leader。处理过程如下:

  1. 首先由 RM1,就是某个 RM,向 Leader 发送 Begin Commit 指令。这个操作和 2PC 稍有不同,但和客户端向 Leader 发送指令在效果上是大致相同的。同时,RM1 要向所有 Acceptor 发送 Prepared 指令。因为把事务触发也算进去了,所以整个协议有三个阶段构成,Prepare 是其中的第二阶段,而 RM 对 Prepare 指令的响应过程又拆分成了 a 和 b 两个子阶段。所以,这里的 Prepared 指令用 2a Prepared 表示,要注意这是一个完成时,表示已经准备完毕。
  2. Leader 向除 RM1 外的所有 RM,发送 Prepare 指令。RM 执行指令后,向所有 Acceptor 发送消息 2a Prepared。这里的关键点是,一个 2a Prepared 消息里只包含一个 RM 的执行情况。而每个 Acceptor 都会收到所有 RM 发送的消息,从而得到全局 RM 的执行情况。
  3. 每个 Acceptor 向 Leader 汇报自己掌握的全局状态,载体是消息 2b Prepared。2b Prepared 是对 2a Prepared 的合并,每个消息都记录了所有 RM 的执行情况。最后,Leader 基于多数派得出了最终的全局状态。这一点和 2PC 完全不同,事务的状态完全由投票决定,Leader 也就是事务协调者,是没有独立判断逻辑的。
  4. Leader 基于已知的全局状态,向所有 RM 发送 Commit 指令。

这个过程中,如果 Acceptor 总数是 2F+1,那么每个 RM 就有 2F+1 条路径与 Leader 通讯。只要保证其中 F+1 条路径是畅通的,整个协议就可以正常运行。因此,Paxos Commit 协议的优势之一,就是在很大程度上避免了网络故障对系统的影响。但是,相比于 2PC 来说,它的消息数量大幅增加,而且多了一次消息延迟。目前,实际产品中还很少有使用 Paxos Commit 协议的。


原子性(优化事务延迟)

事务延迟估算

整个 2PC 的事务延迟由两个阶段组成,可以用公式表达为:

$L_{txn}$ =$ L_{prep}$ + $L_{commit}$

其中,$ L_{prep}$ 是准备阶段的延迟, $L_{commit}$ 是提交阶段的延迟。

准备阶段,它是事务操作的主体,包含若干读操作和若干写操作。把读操作的次数记为 R,读操作的平均延迟记为 Lr,写操作次数记为 W,写操作平均延迟记为 Lw。那么整个准备阶段的延迟可以用公式表达为:

$L_{prep}$ = R * $L_r$ + W * $L_w$

在不同的产品架构下,读操作的成本是不一样的。选一种最乐观的情况,CockroachDB。因为它采用 P2P 架构,每个节点既承担了客户端服务接入的工作,也有请求处理和数据存储的职能。所以,最理想的情况是,读操作的客户端接入节点,同时是当前事务所访问数据的 Leader 节点,那么所有读取就都是本地操作。

磁盘操作相对网络延迟来说是极短的,所以可以忽略掉读取时间。那么,准备阶段的延迟主要由写入操作决定,可以用公式表达为:

$L_{prep}$ = W * $L_w$

分布式数据库的写入,并不是简单的本地操作,而是使用共识算法同时在多个节点上写入数据。所以,一次写入操作延迟等于一轮共识算法开销,我们用 $L_c$ 代表一轮共识算法的用时,可以得到下面的公式:

$L_{prep}$ = W * $L_c$

再来看第二阶段,提交阶段,Percolator 模型的提交阶段只需要写入一次数据,修改整个事务的状态。对于 CockroachDB,这个事务标识可以保存在本地。那么提交操作的延迟也是一轮共识算法,也就是:

$L_{commit}$ = $L_c$

分别得到两个阶段的延迟后,带入最开始的公式,可以得到:

$L_{txn}$ = (W + 1) * $L_c$

把这个公式带入具体例子里来看一下。还是 A 给 B 转账,金额是 500 元。

事务延迟举例

在这个转账事务中,包含两条写操作 SQL,分别是扣减 A 账户余额和增加 B 账户余额,W 等于 2。再加上提交操作,一共有 3 个 $L_c$。可以看到,这个公式里事务的延迟是与写操作 SQL 的数量线性相关的,而真实场景中通常都会包含多个写操作,那事务延迟肯定不能让人满意。


优化方法

缓存写提交(Buffering Writes until Commit)

第一个办法是将所有写操作缓存起来,直到 commit 语句时一起执行,这种方式称为 Buffering Writes until Commit,把它翻译为『缓存写提交』。而 TiDB 的事务处理中就采用这种方式,借用 TiDB 官网的一张交互图来说明执行过程。

缓存写提交

所有从 Client 端提交的 SQL 首先会缓存在 TiDB 节点,只有当客户端发起 Commit 时,TiDB 节点才会启动两阶段提交,将 SQL 被转换为 TiKV 的操作。这样,显然可以压缩第一阶段的延迟,把多个写操作 SQL 压缩到大约一轮共识算法的时间。那么整个事务延迟就是:

$L_{txn}$ = 2 * $L_c$

但缓存写提交存在两个明显的缺点。

首先是在客户端发送 Commit 前,SQL 要被缓存起来,如果某个业务场景同时存在长事务和海量并发的特点,那么这个缓存就可能被撑爆或者成为瓶颈。

其次是客户端看到的 SQL 交互过程发生了变化,在 MySQL 中如果出现事务竞争,判断优先级的规则是 First Write Win,也就是对同一条记录先执行写操作的事务获胜。而 TiDB 因为缓存了所有写 SQL,所以就变成了 First Commit Win,也就是先提交的事务获胜。用一个具体的例子来演示这两种情况。

事务竞争

在 MySQL 中同时执行 T1,T2 两个事务,T1 先执行了 update,所以获得优先权成功提交。而 T2 被阻塞,等待 T1 提交后才完成提交。

事务竞争

在 TiDB 中执行同样的 T1、T2,虽然 T2 晚于 T1 执行 update,但却先执行了 commit,所以 T2 获胜,T1 失败。

First Write Win 与 First Commit Win 在交互上是显然不同的,这虽然不是大问题,但对于开发者来说,还是有一定影响的。可以说,TiDB 的『缓存写提交』方式已经不是完全意义上的交互事务了。


管道(Pipeline)

有一种方法,既能缩短延迟,又能保持交互事务的特点。CockroachDB 采用的就是这种方式,称为 Pipeline。具体过程就是在准备阶段是按照顺序将 SQL 转换为 K / V 操作并执行,但是并不等待返回结果,直接执行下一个 K / V 操作。

这样,准备阶段的延迟,等于最慢的一个写操作延迟,也就是一轮共识算法的开销,所以整体延迟同样是:

$L_{prep}$ = $L_c$

加上提交阶段的一轮共识算法开销:

$L_{txn}$ = 2 * $L_c$

再结合转账的例子看一下:

转账例子

同样的操作,按照 Pipeline 方式,增加小红账户余额时并不等待小明扣减账户的动作结束,两条 SQL 的执行时间约等于 1 个 $L_c$。加上提交阶段的 1 个 $L_c$,一共是 2 个 $L_c$,并且延迟也不再随着 SQL 数量增加而延长。

2 个 $L_c$ 是多久,需要带入真实场景计算一下。

首先,评估一下期望值。对于联机交易来说,延迟通常不超过 1 秒,如果用户体验良好,则要控制在 500 毫秒以内。其中留给数据库的处理时间不会超过一半,也就是 250-500 毫秒。这样推算,$L_c$ 应该控制在 125-250 毫秒之间。

再来看看真实的网络环境。目前人类现有的科技水平是不能超越光速的,这个光速是指光在真空中的传播速度,大约是 30 万千米每秒。而光纤由于传播介质不同和折线传播的关系,传输速度会降低 30%,大致是 20 万千米每秒。但是,这仍然是一个比较理想的速度,因为还要考虑网络上的各种设备、协议处理、丢包重传等等情况,实际的网络延迟还要长很多。

这里引用了论文 "Highly Available Transactions: Virtues and Limitations" 中的一些数据,这篇论文发表在 VLDB2014 上,在部分章节中初步探讨了系统全球化部署面临的延迟问题。论文作者在亚马逊 EC2 上,使用 Ping 包的方式进行了实验,并统计了一周时间内 7 个不同地区机房之间的 RTT(Round-Rip Time,往返延迟)数据。

简单来说,RTT 就是数据在两个节点之间往返一次的耗时。在讨论网络延迟的时候,为了避免歧义,通常使用 RTT 这个概念。

RTT 数据

实验中,地理跨度较大两个机房是巴西圣保罗和新加坡,两地之间的理论 RTT 是 106.7 毫秒(使用光速测算),而实际测试的 RTT 均值为 362.8 毫秒,P95(95%)RTT 均值为 649 毫秒。将 649 毫秒代入公式,那 $L_{txn}$ 就是接近 1.3 秒,这显然太长了。考虑到共识算法的数据包更大,这个延迟还会更长。


并行提交(Parallel Commits)

像 CockroachDB、YugabyteDB 这样分布式数据库,它们的目标就是全球化部署,所以还要努力去压缩事务延迟。

准备阶段的操作已经压缩到极限了,目前的办法就是让两个动作并行执行。

在优化前的处理流程中,CockroachDB 会记录事务的提交状态:

TransactionRecord{ 
	Status: COMMITTED, 
	...
}

并行执行的过程是这样的:

准备阶段的操作,在 CockroachDB 中被称为意向写。这个并行执行就是在执行意向写的同时,就写入事务标志,当然这个时候不能确定事务是否提交成功的,所以要引入一个新的状态『Staging』,表示事务正在进行。那么这个记录事务状态的落盘操作和意向写大致是同步发生的,所以只有一轮共识算法开销。事务表中写入的内容是类似这样的:

TransactionRecord{
    Status: STAGING,
    Writes: []Key{"A", "C", ...},
    ...
}

Writes 部分是意向写的 Key。这是留给异步进程的线索,通过这些 Key 是否写成功,可以倒推出事务是否提交成功。

而客户端得到所有意向写的成功反馈后,可以直接返回调用方事务提交成功。客户端只在当前进程内判断事务提交成功后,不维护事务状态,而直接返回调用方;事后由异步线程根据事务表中的线索,再次确认事务的状态,并落盘维护状态记录。这样事务操作中就减少了一轮共识算法开销。

并行执行

并行提交的优化思路其实和 Percolator 很相似,那就是不要纠结于在一次事务中搞定所有事情,可以只做最少的工作,留下必要的线索,就可以达到极致的速度。而后续的异步进程,只要根据线索,完成收尾工作就可以了。


小结

  1. 高延迟一直是分布式事务的痛点。在一些测试案例中,MySQL 多节点的 XA 事务延迟甚至达到单机事务的 10 倍。按照 2PC 协议的处理过程,分布式事务延迟与事务内写操作 SQL 语句数量直接相关。延迟时间可以用公式表达为 $L_{txn}$ = (W + 1) * $L_c$。
  2. 使用缓存写提交方式优化,可以缩短准备阶段的延迟,${L_txn}$ = 2 * $L_c$。但这种方式与事务并发控制技术直接相关,仅在乐观锁时适用,TiDB 使用了这种方式。而且一旦将并发控制改为悲观协议,事务延迟又会上升。
  3. 通过管道方式优化,整体事务延迟可以降到两轮共识算法开销,并且在悲观协议下也适用。
  4. 使用并行提交,可以进一步将整体延迟压缩到一轮共识算法开销。CockroachDB 使用了管道和并行提交这两种优化手段。

加餐:并行提交中的 异步写事务日志只是根据每个数据项的写入情况,追溯出事务的状态,然后落盘保存,整个过程并没有任何重试或者回滚的操作。这是因为,在之前的同步操作过程中,负责管道写入的同步线程,已经明确知道了每个数据项的写入情况,也就是确定了事务的状态,不同步落盘只是为了避免由此带来的共识算法延迟。

事务状态置为staging,是表示事务已经开始但状态未知,而后在所有写入执行完毕后,事务的状态是明确的。但是,如果此时更新事务状态会带来一轮多副本写入的开销,增加延迟。所以,协调者直接向调用方返回事务处理的结果,再由异步线程来更新持久化的事务状态。这个更新过程,不需要考虑多副本的问题,因为所有写入操作都已经完成了多副本的一致性投票。只是要确认每个写入都成功,则可以判定事务成功,否则事务失败。


学习资料

Daniel Peng and Frank Dabek: Large-scale Incremental Processing Using Distributed Transactions and Notifications

Jim Gray: [Notes on Data Base Operating Systems](Jim Gray: Notes on Data Base Operating Systems)

Peter Bailis et al.: [Highly Available Transactions: Virtues and Limitations


更新时间:2022-02-07 13:36:37

本文由 caroly 创作,如果您觉得本文不错,请随意赞赏
采用 知识共享署名4.0 国际许可协议进行许可
本站文章除注明转载 / 出处外,均为本站原创或翻译,转载前请务必署名
原文链接:https://caroly.fun/archives/分布式数据库三
最后更新:2022-02-07 13:36:37

评论

Your browser is out of date!

Update your browser to view this website correctly. Update my browser now

×