万万没想到,分布式存储系统的一致性是......

万万没想到,分布式存储系统的一致性是......

文章来源:重庆满阳科技有限公司 发布时间:2018-04-27阅读量:

分布式存储系统模型



在分布式存储系统(包括OceanBase这样的分布式数据库)的使用中,我们经常会提到“一致性”这个词,但是这个术语在不同的系统、不同人的心目中有不同的内涵,很容易造成混淆。想象一个最简单的存储系统,只有一个客户端(单进程)和一个服务端(单进程服务)。客户端顺序发起读写操作,服务端也顺序处理每个请求,那么无论从服务器视角还是从客户端视角,后一个操作都可以看到前一个操作的结果。

然后,系统变的复杂一些,系统还是单个服务进程(单副本),但是有多个客户端并发进行操作。这个模型下,多个客户端的操作会互相影响,比如一个客户端会读到不是自己写的数据(另一个客户端写入的)。一般单机并发程序就是这样的模型,比如多个线程共享内存的程序中。然后,系统向另外一个方向变的复杂一些,为了让后端存储系统更健壮(目的不仅如此),我们可以让两个不同的服务进程(位于不同的机器上)同时存储同一份数据的拷贝,然后,通过某种同步机制让这两个拷贝的数据保持一致。这就是我们所说的“多副本”。假设还是一个客户端进程顺序发起读写操作,每个操作理论上可以发给两个副本所在的任意一个服务。那么,如果副本之间是数据同步不及时,就可能发生前面写入的数据读不到,或者前面读到的数据后面读不到等情况。

结合前两种模型,在一类真实的系统中,实际存在多个同时执行读写操作的客户端同时读写多个副本的后端存储服务。如果这些不同客户端的读写操作涉及到同一个数据项(比如,文件系统中,同一个文件的同一个位置范围;或者数据库系统中同一个表的同一行),那么他们的操作会互相影响。比如,A、B两个客户端,操作同一行数据,A修改这行,是不是要求B立即能够读到这个最新数据呢?在多副本的系统中,如果不要求上述保证,那么可能可以允许A和B分别操作不同的副本。

更进一步,前面的系统模型中,假设每个服务进程拥有和管理着一份“全量”的数据。而更复杂的系统中,每个服务进程中,只服务整个数据集的一个子集。例如在OceanBase类似的系统中,单机往往是无法容纳全部数据的一份副本的,所以,数据库表和表的分区是分散在多机上提供服务,每个服务进程只负责某些表分区的一个副本。在这样的系统中,如果一个读写操作涉及到的多个分区位于多个服务进程中,可能出现更复杂的读写语义的异常情况。比如,一个写操作W中修改了两个不同服务进程的两个不同的分区的副本上的两个不同的数据项,随后的一个读取这两个数据项的读操作,是否允许读到W对一个数据项的修改,而读不到对另一个数据项的修改?

综上,我们要讨论的通用的分布式存储系统具有如下特性:

1.数据分为多个分片存储在多台服务节点上

2.每个分片有多个副本,存储在不同的服务节点上

3.许多客户端并发访问系统,执行读写操作,每个读写操作在系统中需要花费不等的时间

4.除非下文中特别注明和讨论,读写操作是原子的

与数据库事务一致性的区别



数据库事务的ACID的中也有一个一致性(consistency),但彼一致性非此一致性。ACID中的一致性是指,数据库的事务的执行,或者说事务观察到的数据,总是要满足某些全局的 一致性 约束条件,如唯一性约束,外键约束等。这个概念和数据库的数据是否多副本没关系。而本文的一致性在多副本的语境下才有意义。所以,数据库事务的一致性,是指数据项之间总是满足某些约束条件,或者说整个数据库在满足约束条件的意义上是正确的。

更让人崩溃的是,事务的隔离性也容易和这里的一致性混淆,因为它和一致性模型类似,限定了某种并发操作的执行顺序。事务的隔离性是指并发执行的事务,能以多大的程度看到看到彼此。这个概念也和数据是否多副本没有关系,单副本的单机数据库也需要支持不同的隔离级别。比如,如果数据库设定为可串行化(serializable)隔离级别,那么并发事务的执行结果,必须等价为让这些事务以某种顺序串行执行的结果。事务的隔离性,是为了并发程序(客户端程序)正确性而生的一种编程抽象,可以类比多线程程序访问共享数据时候需要解决的竞争。在实际系统中,事务是由一系列读写操作组成的,原子的事务的中间状态是可能被并发的其他事务“观察”到的。而在一致性模型的讨论中,我们假设读写操作在服务端是“瞬时”完成的,也就是说,读写操作本身是原子的。

客户端视角一致性模型



在多副本的存储系统中,无论采用什么样的多副本同步协议,为了保证多个副本能够一致,本质上都要求做到:

1.同一份数据的所有副本,都能够接收到全部写操作(无论需要花费多久时间)

2.所有副本要以某种确定顺序执行这些写操作

客户视角的一致性模型定义了下面4种不同的保证。

1.单调读。如果一个客户端读到了数据的某个版本n,那么之后它读到的版本必须大于等于n。

2.读自己所写。如果一个客户端写了某个数据的版本n,那么它之后的读操作必须读到大于等于版本n的数据。

3.单调写。单调写保证同一个客户端的两个不同写操作,在所有副本上都以他们到达存储系统的相同的顺序执行。单调写可以避免写操作被丢失。

4.读后写。读后写一致性,保证一个客户端读到版本n数据后(可能是其他客户端写入的),随后的写操作必须要在版本号大于等于n的副本上执行。

系统对外提供的不同的一致性级别,实际上提供了这其中某几个保证。不同的一致性级别,限定了系统允许的操作执行顺序,以及允许读到多旧的数据。

为什么要定义不同的一致性级别呢?对用户来说,当然越严格的一致性越好,在异常和复杂场景下,严格的一致性级别可以极大地简化应用的复杂度。但是天下没有免费的午餐,一般来说,越严格的一致性模型,意味着性能(延迟)、可用性或者扩展性(能够提供服务的节点数)等要有所损失。



[1] [2] 下一页