前面提到的复制带来了高可用性和高性能的好处,但也带来了多个副本如何保持数据一致的问题。对此我们提出了一致性模型的概念,它是系统和开发者之间的一种约定,如果开发者遵循某些规则,那么开发者执行读操作或写操作得结果是可预测的。这个可预测非常重要,可预测保证了程序逻辑的确定性,如果对一个系统的读写操作却无法返回可预测的结果,那么这样的系统很难用。
下面是著名分布式一致性验证框架对一致性模型的分类:

一致性模型按照可用性可以分为3类:
-
不可用
表示满足这类一致性模式的系统发生网络分区时,为了保证数据一致性和正确性,系统会不可用。用CAP定理来解释就是典型的CP系统,这类一致性模型包括线性一致性和顺序一致性。 -
基本可用
满足这类一致性模型的系统可以容忍一部分节点发生故障,还未出现故障的节点仍然可用,但前提是客户端不能将请求发送到可用的不可用的副本节点上。这类一致性模型包括因果一致性,PRAM一致性和读你所写一致性。 -
高可用
满足这类一致性模型的系统可用性是最高的,即使网络发生严重分区,在没有发生故障的节点,仍然保证可用,这类一致性模型包括:读后写一致性,单调读一致性和单调写一致性。
线性一致性
线性一致性是最想的一致性模型(也是CAP定理所指的一致性),线性一致性也称为 强一致性,严格一致性,原子一致性,立即一致性或外部一致性,
线性一致性的严格定义:给定一个执行历史,执行历史根据并发操作可以扩展为多个顺序历史,只要从中找到一个合法的顺序历史,那么该执行历史线性一致性。
那么如何将执行历史转变为顺序历史呢?
首先并发操作之间一共存在如下3种情况,总的说来可将其分为顺序关系和并发关系:
-
一个操作在另一个操作之前发送,这两个操作是顺序关系
顺序关系.png
-
两个操作之间有重叠,这两个操作是并发关系
并发关系.png
-
一个操作包含另一个操作,这两个操作也是并发关系
并发关系.png
线性一致性有个约束:在将执行历史转变为顺序历史的过程中,如果两个操作是顺序关系,那么它们的先后顺序必须保持相同,如果两个操作是并发关系,则他们可以按任何顺序排列。
现在有如下场景:

那么我们将其转换为顺序历史如图:

可以推断出:顺序历史S1是不合法的,S2显然是合法的。所以S2 是符合条件的顺序历史,所以上面的场景是满足线性一致性。
顺序一致性
顺序一致性是一种比线性一致性弱一些的一致性模型,顺序一致性同样允许堆并发操作历史进行重新排列,顺序一致性只要求同一个客户端的操作再排序后保持先后顺序不变,但不同的客户端之间的先后顺序是可以改变的。

按照线性一致性的要求,上面只会得到一种顺序历史,但是该顺序历史显然是无法满足线性一致性的。

但顺序一致性可以允许不同客户端之间的操作改变先后顺序:

所以,顺序一致性和线性一致性的主要区别在于没有全局时间的限制,顺序一致性不要求不同客户端之间的操作顺序一致,只关注局部顺序。
这点可以类比现代编译器和CPU通常都会优化指令的执行顺序,以提升程序性能,实际执行的指令顺序和程序写的指令顺序可能是不一致的。
因果一致性
因果一致性是一种比顺序一致性更弱一些的一致性模型,它与顺序一致性一样不依赖于全局操作的顺序。因果一致性要求,必须以相同的顺序看到因果相关的操作,而没有因果关系的并发操作可以被不同的进程以不同的顺序观察到。
最典型的因果关系就是社交网络中的发帖和评论关系,根据因果关系,必须先有发帖才能有对该帖子的评论,所以发帖操作必须在评论操作之前。
因果一致性一般应用在跨地域同步数据中心系统中,例如Facebook、微信这样的应用程序,全球各地的用户,往往会访问其距离最近的数据中心,数据中心之间再进行双向的数据同步。
最终一致性
最终一致性是最弱的一致性模型之一,该模型认为,只要系统最终能够达到一个稳定的状态,在某个阶段,系统各个节点处理客户端操作顺序可以不同,读操作也不需要返回最新的写操作结果,并且这个最终也没有指定系统必须达到稳定状态的硬性时间。这听起来似乎不可靠,但是在实践中,这个模型工作得很好,当下许多追加高性能的分布式存储系统都是使用最终一致性模型,例如 Dynamo。
以客户端为中心的一致性模型
前面讲的4种一致性可以归为一类,称为以数据为中心的一致性模型。以数据为中心的一致性模型旨在为数据存储系统提供一个系统级别的全局一致性视图,讨论这里一致性模型都是当并发的客户端同时更新数据时,考虑每个副本的数据是否一致,以及系统提供的一致性。
还有另一类以客户端为中心的一致性模型,这类一致性模型从客户端的角度来观察分布式系统,不再从系统的角度考虑每个副本的数据是否一致,而是考虑客户端的读写请求结果的结果,从而推断出系统的一致性。
简单来说,以数据为中心的一致性模型常常考虑多个客户端时的系统状态,而以客户端为中心的一致性模型聚焦于单个客户端观察到的系统状态。
-
单调读
单调读必须满足:如果客户端读到关键字的值为
,那么该客户端对于
的任何后续的读操作都必须返回
或比
更新的值。即保证客户端不会读到旧值。
-
单调写
同一个客户端(或进程)的写操作在所有副本上都以同样的顺序执行,即保证客户端的写操作是串行的。
例如,客户端先执行写操作,再执行写操作
,如果另一个客户端不停地读
的值,那么会先读到
的值先为0再为1,不会先读到1,再读到0。
-
读你所写
也称为读己之写一致性,该一致性要求:当写操作完成后,在同一副本或其他副本上的读操作必须能够读到写入的值。一个违反了读你所写的例子如下:
image.png
由于数据同步延迟,读到了旧数据0,这就违反了读你所写一致性。
-
PRAM 一致性
PRAM 一致性由单调读,单调写和读你所写3个一致性模型组成,他要求:同一个客户端的多个写操作,将被所有的副本按照同样的执行顺序观察到,但不同的客户端发出的写操作可以以不同的执行顺序被观察到。一个违反了PRAM一致性例子如图:
image.png
对于客户端A的操作,再副本1上的顺序是先存款20元,再取款10元,可是在副本2上的顺序却是先取款10元,再存款20元。
-
读后写
读后写一致性要求:同一个客户端对于数据项,如果先读到了写操作w1的结果
,那么之后的写操作w2保证基于
或比
更新的值。
参考资料
1、《深入理解分布式系统》
2、https://www.infoq.cn/article/wechat-serial-number-generator-architecture/
网友评论