如何在分布式架构下完美实现“全局数据一致性”?

OB君:本文是 “ OceanBase 2.0 技术解析系列” 的第五篇文章。今天咱们继续来聊分布式架构,说说2.0中你们都很关心的“全局一致性快照”功能。更多精彩欢迎关注OceanBase公众号持续订阅本系列内容!

前言

首先,我想有些朋友在看到这个标题以后可能会问:数据库

  • 什么是“全局一致性快照”?
  • 它在OceanBase数据库里起什么做用?
  • 为何OceanBase数据库要在2.0版本中引入这个东西?

实际上,故事起源于数据库中的两个传统概念:“快照隔离级别(Snapshot Isolation)”和“多版本并发控制(Multi-VersionConcurrency Control,简称MVCC)”。这两种技术的大体含义是:为数据库中的数据维护多个版本号(即多个快照),当数据被修改的时候,能够利用不一样的版本号区分出正在被修改的内容和修改以前的内容,以此实现对同一份数据的多个版本作并发访问,避免了经典实现中“锁”机制引起的读写冲突问题。缓存

所以,这两种技术被不少数据库产品(如Oracle、SQL Server、MySQL、PostgreSQL)所采用,而OceanBase数据库也一样采用了这两种技术以提升并发场景下的执行效率。但和传统的数据库的单点全共享(即Shared-Everything)架构不一样,OceanBase是一个原生的分布式架构,采用了多点无共享(即Shared-Nothing)的架构,在实现全局(跨机器)一致的快照隔离级别和多版本并发控制时会面临分布式架构所带来的技术挑战(后文会有详述)。markdown

为了应对这些挑战,OceanBase数据库在2.0版本中引入了“全局一致性快照”技术。本文会介绍和OceanBase“全局一致性快照”技术相关的概念以及基本实现原理。网络

(注:本文中的全部描述,都是针对采用了“快照隔离级别”和“多版本并发控制”技术的实现机制。对于使用“锁”机制来实现传统“隔离级别(Isolation Level)”的经典模式,不在本文的讨论范围以内。)架构

传统数据库的实现原理

首先,咱们来看一下传统数据库中是如何实现“快照隔离级别”和“多版本并发控制”的。并发

以经典的Oracle数据库为例,当数据的更改在数据库中被提交的时候,Oracle会为它分配一个“System Change Number(SCN)”做为版本号。SCN是一个和系统时钟强相关的值,能够简单理解为等同于系统时间戳,不一样的SCN表明了数据在不一样时间点的“已提交版本(Committed Version)”,由此实现了数据的快照隔离级别。负载均衡

假设一条记录最初插入时对应的版本号为SCN0,当事务T1正在更改此记录但还未提交的时候(注意:此时T1对应的SCN1还没有生成,须要等到T1的commit阶段),Oracle会将数据更改以前的已提交版本SCN0放到“回滚段(Undo Segments)”中保存起来,此时若是有另一个并发事务T2要读取这条记录,Oracle会根据当前系统时间戳分配一个SCN2给T2,并按照两个条件去寻找数据:运维

1)必须是已提交(Committed)的数据;分布式

2)数据的已提交版本(Committed Version)是小于等于SCN2的最大值。高并发

根据上面的条件,事务T2会从回滚段中获取到SCN0版本所对应的数据,并不理会正在同一条记录上进行修改的事务T1。利用这种方法,既避免了“脏读(Dirty Read)”的发生,也不会致使并发的读/写操做之间产生锁冲突,实现了数据的多版本并发控制。整个过程以下图所示:

关于“快照隔离级别”和“多版本并发控制”,不一样数据库产品的实现机制会有差别,但大多遵循如下原则:

  • 每次数据的更改被提交时,都会为数据分配一个新的版本号。
  • 版本号的变化必须保证“单调向前”。
  • 版本号取自系统时钟里的当前时间戳,或者是一个和当前时间戳强相关的值。
  • 查询数据时,也须要一个最新版本号(同理,为当前时间戳或者和当前时间戳强相关的值),并查找小于等于这个版本号的最近已提交数据。

分布式数据库面临的挑战前面关于“多版本并发控制”的描述看上去很完美,可是这里面却有一个隐含的前提条件:数据库中版本号的变化顺序必须和真实世界中事务发生的时间顺序保持一致,即:

— 真实世界中较早发生的事务必然获取更小(或者相等)的版本号;

— 真实世界中较晚发生的事务必然获取更大(或者相等)的版本号。

若是不能知足这个一致性,会致使什么结果呢?如下面的场景为例:

1)记录R1首先在事务T1里被插入并提交,对应的SCN1是10010;

2)随后,记录R2在事务T2里被插入并提交,对应的SCN2是10030;

3)随后,事务T3要读取这两条数据,它获取的SCN3为10020,所以它只获取到记录R1(SCN1<SCN3,知足条件),而没有获取到R2(SCN2>SCN3,不知足条件)。示意图以下:

这对应用来讲就是一个逻辑错误:我明明向数据库中插入了两条记录而且都提成功提交了,但却只能读到其中的一条记录。致使这个问题的缘由,就是这个场景违反了上面所说的一致性,即SCN(版本号)的变化顺序没有和真实世界中事务发生的时间顺序保持一致。

其实,违反了这种一致性还可能引起更极端的状况,考虑下面的场景:

1)记录R1首先在事务T1里被插入并提交,对应的SCN1是10030;

2)随后,记录R2在事务T2里被插入并提交,对应的SCN2是10010

3)随后,事务T3要读取这两条数据,它获取的SCN3为10020,所以它只能获取到记录R2(SCN2<SCN3,知足条件),而没法获取到R1(SCN1>SCN3,不知足条件)。示意图以下:

对于应用来讲,这种结果从逻辑上讲更加难以理解:先插入的数据查不到,后插入的数据反而能查到,彻底不合理。

有的朋友可能会说:上面这些状况在实际中是不会发生的,由于系统时间戳永远是单调向前的,所以真实世界中先提交的事务必定有更小的版本号。是的,对于传统数据库来讲,因为采用单点全共享(Shared-Everything)架构,数据库只有一个系统时钟来源,所以时间戳(即版本号)的变化的确能作到单调向前,而且必定和真实世界的时间顺序保持一致。

但对于OceanBase这样的分布式数据库来讲,因为采用无共享(Shared-Nothing)架构,数据分布和事务处理会涉及不一样的物理机器,而多台物理机器之间的系统时钟不可避免存在差别,若是以本地系统时间戳做为版本号,则没法保证不一样机器上获取的版本号和真实世界的时间序保持一致。仍是以上面的两个场景为例,若是T一、T2和T3分别在不一样的物理机器上执行,而且它们都分别以本地的系统时间戳做为版本号,那么因为机器间的时钟差别,彻底可能发生上面所说的两种异常。

为了解决上面所说的问题,在分布式领域引入了两个概念: “外部一致性(External Consistency)” 和 “因果一致性(Causal Consistency)”。仍是以上面的两个场景为例,真实世界中事务的发生顺序为T1 -> T2-> T3,若是SCN的变化能保证SCN1 < SCN2 < SCN3的顺序,而且能够彻底不关心事务发生时所在的物理机器,则认为SCN的变化知足了“外部一致性”。

而“因果一致性”则是“外部一致性”的一种特殊状况:事务的发生不只有先后顺序,还要有先后关联的因果关系。所以“外部一致性”的覆盖范围更广,“因果一致性”只是其中的一种状况,若是知足了“外部一致性”则必定能知足“因果一致性”。OceanBase在实现中知足了“外部一致性”,同时也就知足了“因果一致性”,本文后半段的内容也主要针对“外部一致性”来展开。

业内经常使用的解决方案

那么,分布式数据库应如何在全局(跨机器)范围内保证外部一致性,进而实现全局一致的快照隔离级别和多版本并发控制呢?大致来讲,业界有两种实现方式:
1)利用特殊的硬件设备,如GPS和原子钟(Atomic Clock),使多台机器间的系统时钟保持高度一致,偏差小到应用彻底没法感知的程度。在这种状况下,就能够继续利用本地系统时间戳做为版本号,同时也能知足全局范围内的外部一致性。

2)版本号再也不依赖各个机器本身的本地系统时钟,全部的数据库事务经过集中式的服务获取全局一致的版本号,由这个服务来保证版本号的单调向前。这样一来,分布式架构下获取版本号的逻辑模型和单点架构下的逻辑模型就同样了,完全消除了机器之间时钟差别的因素。

第一种方式的典型表明是Google的Spanner数据库。它使用GPS系统在全球的多个机房之间保持时间同步,并使用原子钟确保本地系统时钟的偏差一直维持在很小的范围内,这样就能保证全球多个机房的系统时钟可以在一个很高的精度内保持一致,这种技术在Spanner数据库内被称为TrueTime。在此基础上,Spanner数据库就能够沿用传统的方式,以本地系统时间戳做为版本号,而不用担忧破坏全局范围内的外部一致性。

这种方式的好处,是软件的实现比较简单,而且避免了采用集中式的服务可能会致使的性能瓶颈。但这种方式也有它的缺点,首先对机房的硬件要求明显提升,其次“GPS+原子钟”的方式也不能100%保证多个机器之间的系统时钟彻底一致,若是GPS或者原子钟的硬件误差致使时间偏差过大,仍是会出现外部一致性被破坏的问题。根据GoogleSpanner论文中的描述,发生时钟误差(clock drift)的几率极小,但并不为0。下图是Google Spanner论文中对上千台机器所作的关于时钟偏差范围的统计:

OceanBase则选用了第二种实现方式,即用集中式的服务来提供全局统一的版本号。作这个选择主要是基于如下考虑:

  • 能够从逻辑上消除机器间的时钟差别因素,从而完全避免这个问题。
  • 避免了对特殊硬件的强依赖。这对于一个通用数据库产品来讲尤为重要,咱们不能假设全部使用OceanBase数据库的用户都在机房里部署了“GPS+原子钟”的设备。

OceanBase的“全局一致性快照”技术如前文所述, OceanBase数据库是利用一个集中式服务来提供全局一致的版本号。事务在修改数据或者查询数据的时候,不管请求源自哪台物理机器,都会从这个集中式的服务处获取版本号,OceanBase则保证全部的版本号单调向前而且和真实世界的时间顺序保持一致。

有了这样全局一致的版本号,OceanBase就能根据版本号对全局(跨机器)范围内的数据作一致性快照,所以咱们把这个技术命名为“全局一致性快照”。有了全局一致性快照技术,就能实现全局范围内一致的快照隔离级别和多版本并发控制,而不用担忧发生外部一致性被破坏的状况。

可是,相信有些朋友看到这里就会产生疑问了,好比:

  • 这个集中式服务里是如何生成统一版本号的?怎么能保证单调向前?
  • 这个集中式服务的服务范围有多大?整个OceanBase集群里的事务都使用同一个服务吗?
  • 这个集中式服务的性能如何?尤为在高并发访问的状况下,是否会成为性能瓶颈?
  • 若是这个集中式服务发生中断怎么办?
  • 若是在事务获取全局版本号的过程当中,发生了网络异常(好比瞬时网络抖动),是否会破坏“外部一致性”?

下面针对这些疑问逐一为你们解答。

首先,这个集中式服务所产生的版本号就是本地的系统时间戳,只不过它的服务对象再也不只是本地事务,而是全局范围内的全部事务,所以在OceanBase中这个服务被称做“全局时间戳服务(Global Timestamp Service,简称GTS)”。因为GTS服务是集中式的,只从一个系统时钟里获取时间戳,所以能保证获取的时间戳(即版本号)必定是单调向前的,而且必定和真实世界的时间顺序保持一致。

那么,是否一个OceanBase数据库集群中只有一个GTS服务,集群中全部的事务都从这里获取时间戳呢?对OceanBase数据库有了解的朋友都知道,“租户”是OceanBase中实现资源隔离的一个基本单元,比较相似传统数据库中“实例”的概念,不一样租户之间的数据彻底隔离,没有一致性要求,也无需实现跨租户的全局一致性版本号,所以OceanBase数据库集群中的每个“租户”都有一个单独的GTS服务。这样作不但使GTS服务的管理更加灵活(以租户为单位),并且也将集群内的版本号请求分流到了多个GTS服务中,大大减小了因单点服务致使性能瓶颈的可能。下面是OceanBase数据库集群中GTS服务的简单示意图:

说到性能,通过实测,单个GTS服务可以在1秒钟内响应2百万次申请时间戳(即版本号)的请求,所以只要租户内的QPS不超过2百万,就不会遇到GTS的性能瓶颈,实际业务则很难触及这个上限。虽然GTS的性能不是一个问题,但从GTS获取时间戳毕竟比获取本地时间戳有更多的开销,至少网络的时延是没法避免的,对此咱们也作过实测,在满负荷压测而且网络正常的状况下,和采用本地时间戳作版本号相比,采用GTS对性能所带来的影响不超过5%,绝大多数应用对此都不会有感知。

除了保证GTS的正常处理性能以外,OceanBase数据库还在不影响外部一致性的前提下,对事务获取GTS的流程作了优化,好比:

  • 将某些GTS请求转化为本地请求,从本地控制文件中获取版本号,避免了网络传输的开销;
  • 将多个GTS请求合并为一个批量请求,以提升GTS的全局吞吐量;
  • 利用“本地GTS缓存技术”,减小GTS请求的次数。

这些优化措施进一步提升了GTS的处理效率,所以,使用者彻底不用担忧GTS的性能。
前面所说的都是正常状况,但对于集中式服务来讲必定要考虑异常状况。首先就是高可用的问题,GTS服务也像OceanBase中基本的数据服务同样,以Paxos协议实现了高可用,若是GTS服务因为异常状况(好比宕机)而中断,那么OceanBase会根据Paxos协议自动选出一个新的服务节点,整个过程自动并且迅速(1~15秒),无需人工干预。

那若是发生网络异常怎么办?好比网络抖动了10秒钟,会不会影响版本号的全局一致性,并进而影响外部一致性?对数据库来讲,外部一致性反映的是真实世界中“完整事务”之间的先后顺序,好比前面说到的T1 -> T2-> T3,其实准确来讲是T1's Begin-> T1's End -> T2's Begin -> T2's End -> T3's Begin -> T3's End,即任意两个完整的事务窗口之间没有任何重叠。若是发生了重叠,则事务之间并不具有真正的“前后顺序”,外部一致性也就无从谈起。
所以,无论网络如何异常,只要真实世界中的“完整事务”知足这种先后顺序,全局版本号就必定会知足外部一致性。

最后,若是事务发现GTS响应过慢,会从新发送GTS请求,以免因为特殊状况(如网络丢包)而致使事务的处理被GTS请求卡住。
总之,GTS在设计和开发的过程当中已经考虑到了诸多异常状况的处理,确保能够提供稳定可靠的服务。

总结有了“全局一致性快照”技术以后,OceanBase数据库便具有了在全局(跨机器)范围内实现“快照隔离级别”和“多版本并发控制”的能力,能够在全局范围内保证“外部一致性”,并在此基础之上实现众多涉及全局数据一致性的功能,好比全局一致性读、全局索引等。

这样一来,和传统单点数据库相比,OceanBase在保留分布式架构优点的同时,在全局数据一致性上也没有任何降级,应用开发者就能够像使用单点数据库同样使用OceanBase,彻底没必要担忧机器之间的底层数据一致性问题。能够说,借助“全局一致性快照”技术,OceanBase数据库完美地实现了分布式架构下的全局数据一致性!

参考文献

1. Snapshot isolation
2. Multiversionconcurrency control
3. Isolation(database systems)
4. Spanner (database)
5. CloudSpanner: TrueTime and External Consistency
6. Causal consistency

2.0解析系列文章

OceanBase TechTalk · 北京站

10月27日(本周六),OceanBase TechTalk 第二期线下技术交流活动将在北京启动。
届时,OceanBase三大重量级嘉宾:陈萌萌(酒满)、韩富晟(颜然)、乔国治(鸷腾)将跟你们一块儿聊聊支撑了今年天猫双11的 OceanBase 2.0版本的产品新特性和重大的技术革新。北京中关村,咱们不见不散!

报名连接:2018 年第二届 OceanBase TechTalk 技术沙龙 · 北京站