分布式事务简述

分布式事务简述

    • 概述
    • GTM分布式事务
      • 面向应用层的 TCC
      • 2PC 数据库领域最常用的事务方案
      • 分析
      • 两者比较
      • GTM高可用
    • 改进
      • Percolator (NewSQL)
        • Percolator高可用
        • Large-scale Incremental Processing Using Distributed Transactions and Notifications 论文简读
        • 总结
    • PGXC
    • 参考链接


概述

在分布式数据库上需要引入分布式事务来保证数据库ACID,由此也带来了很多挑战:

  1. 原子性:一个事务在多个机器多个数据分片上的修改或者都提交成功,或者都提交失败
  2. 一致性:数据分片之后,仍要保证数据的一致性和完整性
  3. 隔离性:多个机器上的的多个事务,在给定的隔离级别下,不会访问到其他事务未提交的数据
  4. 持久性:一个事务在提交过程中需要将数据同步到其他节点,确保主节点宕机之后,其他节点仍然能够将事务恢复出来。

下面主要谈谈原子性:

如果分开来看的话,事务可以理解为包含一系列操作的序列,原子则代表不可分割的最小粒度。

而合起来看的话,事务的原子性就是让包含若干操作的事务表现得像一个最小粒度的操作。这个操作一旦被执行,只有“成功”或者“失败”这两种结果。这就好像比特(bit),只能代表 0 或者 1,没有其他选择。

关于事务的原子性,图灵奖得主、事务处理大师詹姆斯·格雷(Jim Gray)给出了一个更权威的定义:

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

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

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

GTM分布式事务

全局事务管理器,由GTM/Coordinator(向GTM交互、负责事务的提交)/Datanode(维护本地数据,同时向Coordinator提供读写服务)组成

读写事务提交流程:

  1. 申请GTID及snapshot(根据不同的隔离级别,申请快照的时间也不同)
    1. 由全局的GTIDhesnapshot保证事务的隔离性
    2. 独立的快照带来的问题是查询效率较低,每次查询对应的行都要比较快照版本,所以greenplum有很多对快照的优化。
  2. 访问DataNode
  3. 发起分布式事务提交
  4. 修改GTM事务状态

缺点:GTM容易成为瓶颈,几十个节点规模(在AP系统里,使用分布式GTM可以支持上万节点,同时3000并发)

简单估算:假设每个GTID 8字节,那么1000个活跃事务的快照大小为8KB,在万兆网络环境大概能提供每秒十几万次的请求。所以估计只能做到几十个节点。

在GTM下,垮机事务为了保证事务的原子性,通常使用TCC或者2PC

面向应用层的 TCC

事务回滚补偿模型使用的前提假设是事务在提交的过程中基本不会失败

TCC 是 TryConfirmCancel 三个单词的缩写,它们是事务过程中的三个操作。在cordinator在收到事务的时候会将事务拆分为多个子事务,并将其发给各个数据节点,具体的事务执行流程分为三阶段:

  1. 事务管理器参与者发出 Try 操作,要求进行资源的检查和预留(可以理解在在这个阶段各个参与者就在自己本身执行提交了)。
  2. 当参与者都做好准备之后,事务管理器会发出 Confirm 操作,完成事务
  3. 如果有参与者没有准备好,事务管理器会发出Cancle操作(coordinator在发出对应的GTID给参与者回滚),撤销第一阶段的Try操作。

从上述流程可以发现,TCC 仅是应用层的分布式事务框架,具体操作完全依赖于业务编码实现(业务需要针对每个操作都要注册一个与其对应的确认和补偿(撤销操作)),可以做针对性的设计,但是这也意味着业务侵入会比较深。优点是没每次事务只需要1次网络交互就可以提交(try和confirm可以合为一个commit,如果某个参与者出错,再回滚),比2PC具有优势

考虑到网络的不可靠,操作指令必须能够被重复执行,这就要求 Try、Confirm、Cancel 必须是幂等性操作。

2PC 数据库领域最常用的事务方案

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

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

具体的事务流程如下:

  1. 准备阶段,事务管理器首先向所有参与者发送待执行的 SQL,并询问是否做好提交事务的准备(Prepare),参与者(资源管理器)锁定好对应的资源之后,给事务管理器返回YES
  2. 提交阶段,如果所有参与者的反馈都是 Yes,则事务管理器会发出提交(Commit)指令。这些数据库接受指令后,会进行本地操作,然后返回YES,所有的参与者都返回YES之后,事务完成
  3. Rollback,如果前两步失败,事务管理器需要向参与者发送回滚指令。释放资源。事务结束。

分析

2pc的目的只是为了协调多个参与者,在整体上达成原子性,有以下几个缺点:

  1. 每个参与者个体相互之间无法保证原子一致,从实践上理解就是参与者或者协调者宕机会引发出各种异常处理、甚至丢数据的场景。
  2. 2pc不是一致性协议,意味着2pc的参与者们无法对某个决议达成共识,尽管2pc的结果大部分情况下都是表现为原子性的。

因此,在2pc的实践上,需要搭配各种异常、超时处理的逻辑,或者外部一致性的保证(中心化的一致性视图(greenplum)),或者搭配一致性协议(OceanBase),或额外的保证(后面讲到的percolator)

两者比较

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

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

GTM高可用

在GTM宕机时,如何处理才能保证高可用?一般业务会从下面几个点去考虑

  • GTID持久化
  • GTM多副本同步
  • DataNode利用数据库本身的同步能力(mysql主备同步)或者自研的同步技术(oceanbase paxos)
  • Coordinator无状态(oceanbase)
  • 故障自动切主

问题:

  1. GTID持久化会导致事务的提交延迟变大
  2. Coordinator无状态导致回滚补偿机制复杂

改进

多数分布式数据库都是在 2PC 协议基础上改进,来保证分布式事务的原子性。主要有以下两种

Percolator (NewSQL)

应用:bigtable/TiDB/CockroachDB

Percolator 来自 Google 的论文“Large-scale Incremental Processing Using Distributed Transactions and Notifications”,因为它是基于分布式存储系统 BigTable 建立的模型,所以可以和 NewSQL 无缝链接。乐观锁(所有的加锁操作都是缓存在本地的)。

使用 Percolator 模型的前提是事务的参与者,即数据库,要支持多版本并发控制(MVCC)。流程如下:

  1. 准备阶段,事务管理器向分片发送 Prepare 请求,包含了具体的数据操作要求。分片接到请求后要做两件事,写日志和添加私有版本。所有分片中会有一个主锁,其他分片会感知到这个主锁的存在。
  2. 提交阶段,事务管理器只需要和拥有主锁的分片通讯,发送 Commit 指令,且不用附带其他信息。主锁对应的分片写入之后,主锁失效,从锁仍然指向主锁,不会更改,当后续进行查询的时候,如果从分片发现主锁失效,说明从分片的数据已经commit了。指向主锁的从锁也可以取消了。(分片延迟解锁)
    1. 由此可知,这种通过指针查找的方式,会给读操作增加额外的工作

通过这种方式,Percolator改进了2PC的如下问题:

  1. 数据不一致
    1. 2PC 的一致性问题主要是第二阶段,不能确保事务管理器与多个参与者的通讯始终正常。但在 Percolator 的第二阶段,事务管理器只需要与一个分片通讯,这个 Commit 操作本身就是原子的。所以,事务的状态自然也是原子的,一致性问题被完美解决了。
  2. 单点故障
    1. Percolator 通过日志和异步线程的方式弱化了这个问题。
      • Percolator 引入的异步线程可以在事务管理器宕机后,回滚各个分片上的事务,不会让分片上被占用的资源无法释放。
      • 事务管理器可以用记录日志的方式使自身无状态化,日志通过共识算法同时保存在系统的多个节点上。这样,事务管理器宕机后,可以在其他节点启动新的事务管理器,基于日志恢复事务操作。

但是也存在缺点: commit过程有三次RTT:

  1. 加主锁primary lock
  2. secondary 分片加锁
  3. 主锁解锁

解决方式也需要根据业务和技术选型来,比如可以将primary和secondary加锁结合在一起,减少一轮RTT,再加上一些一致性协议,不需要主锁解锁也可以直接向客户端返回成功,后面的步骤由一致性协议去异步保证

当然,就算解决了事务提交问题,在该事务框架下单行查询的代价也是比较大的。

Percolator高可用

论文中并没有提到如何做高可用,一般工程上的优化有以下几种:

  1. 全局时间戳周期性持久化
  2. 本地通过kv层保证持久化
  3. 远程通过raft同步日志的方式保证持久化

由于percolator是乐观锁,因此存在以下问题:

  1. 在写冲突比较少的时候性能好
  2. 本地缓存数据,需要限制事务大小
  3. 大事务、或者热点行,冲突概率大,性能很差
  4. 对业务有入侵,需要业务定义重试逻辑
  5. 重试有可能破坏隔离性,因为第一次提交和后面重试提交的事务id不同,可见性也不同
  6. 因此后面发展出了乐观锁和悲观锁共存的percolator(tidb),用户可以控制使用乐观锁还是悲观锁,悲观锁会先去数据节点锁住数据
    1. 因为悲观锁在percolator中也需要持久化,所以悲观锁过多也会影响性能
Large-scale Incremental Processing Using Distributed Transactions and Notifications 论文简读

Section 2.1 因为Percolator建立在Bigtable上, 这一节交代了Bigtable提供的接口.主要其实就是自带时间戳和行级事务.

Section 2.2 讲解了跨行事务的实现.

整体方法还是2PC, 特色是利用了Bigtable的单行事务, 具体的细节就不描述了, 简单说明下该方法的可行性:

  1. 如果事务在primary行提交前或primary行提交时失败: 那么所有行的write列还未改变, 读取时会直接读事务之前最新的版本数据, 相当于该事务对数据无任何影响.
  2. 如果在primary行提交成功后, secondaries提交失败: primary已经成功则无任何影响, secondaries处于未完成状态, 当某个事务读取secondaries数据时, 发现其处于未完成状态, 则去检测它对于的primary的状态, 便可以区分它之前所处的事务是成功还是失败, 然后进行相对应的清理操作, rollforward或者rollback.

相当于整个事务的成功和失败状态由primary保证, 而primary的一致性由Bigtable自带单行事务保证, secondaries的一致性由Percolator上述的lazy cleanup完成.
两者一起, 则保证了整个事务的可靠性.
不过如果secondaries在清理状态的时候还失败, 且一直失败…

Section 2.3 讲了TSO的高吞吐实现.

也很简单的, 每次申请一个区间, 然后把区间最高值持久化到磁盘.
处理请求时从则从内存里面已经提前申请好的区间里面分配.
如果crash了, 则从持久化的上次区间最高值恢复.

总结
  • 原子性
    • 通过primary record 的原子操作保证分布式事务的原子性
    • secondary record通过primary record的提交状态确认事务状态
  • 隔离性
    • 在kv中维护多版本数据(版本信息、锁信息等)
    • 通过全局时间戳服务为事务定序,通过开始时间确定事务快照。
  • 高可用
    • 本地通过kv存储持久化
    • 远程通过raft同步日志的方式保证持久化
  • 优点
    • 分层清晰,事务处理和存储耦合性低,kv层不需要感知是,只要做好存储就行了
    • kv存储有大量开源产品可用

PGXC

集群包含了四种角色:协调节点数据节点全局事务器管理节点,其中协调节点数据节点均有多个。主要流程如下:

  1. 协调节点接到事务后,在全局事务管理器(GTM)的全局事务列表中将事务标记成活跃的状态。是通过全局事务列表来申请资源,规避可能存在的事务竞争
  2. 协调节点把一个全局事务分拆成若干子事务,分配给对应的 MySQL 去执行。如果所有操作成功,协调者节点会将全局事务列表中的事务标记为结束,整个事务处理完成。如果失败,子事务在单机上自动回滚,而后反馈给协调者节点,后者向所有数据节点下发回滚指令。

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

应用:TiDB/GoldenDB

TiDB5.0中的1PC含义是在垮AZ部署数据库的场景,为了降低垮AZ事务的开销,将事务更新限制在当前region中。

参考链接

  1. 深入解析分布式数据库核心:分布式事务| OceanBaseDev

你可能感兴趣的:(数据库,分布式技术,互联网技术栈,数据库,分布式事务)