条分缕析分布式:浅析强弱一致性
顺序一致性的英文是:sequential consistency。
线性一致性的英文是:linearizability。实际上,它就是CAP定理中的C,我们在上一篇文章中已经提到过。
最终一致性的英文是:eventual consistency。
线性一致性和顺序一致性,属于分布式系统的一致性模型 (consistency model)。这代表了分布式系统的一个非常非常重要的方面。
通常人们把线性一致性称为「强一致性」,把最终一致性称为「弱一致性」,但线性一致性和最终一致性其实存在本质的区别。严格来说,它们并不是一个范畴的概念。
一致性模型之间的「强弱」比较,是一个相对的概念。比如,线性一致性是比顺序一致性更强的一致性模型。当然,除了线性一致性和顺序一致性,也存在其它一些一致性模型(其中很多都比顺序一致性要弱)。
满足线性一致性的系统,也必定满足顺序一致性,但反过来不一定。这是由一致性模型之间的强弱关系决定的。
一致性模型的来历
容错 (fault tolerance)。即使某些网络节点发生故障,由于原本保存着在故障节点上的数据在正常节点上还有备份,所以整个系统仍然可能是可用的。这也是我们期待分布式系统能够提供的「高可用」特性。
提升吞吐量。将一份数据复制多份并保存在多个副本节点上,还顺便带来一个好处:对于同一个数据对象的访问请求(至少是读请求)可以由多个副本节点分担,从而使得整个系统可以随着请求量的增加不断扩展。
我们先向一个副本节点写入x=42,然后读取数据对象x的值。显然,不管我们从哪个副本节点上进行读取,我们都希望读到最新写入的值(也就是42)。只有这样才合理。
两个系统用户分别在两个副本节点上同时执行写操作。其中,用户A在第1个副本上执行x=42;用户B在第2个副本上执行x=43。然后用户C读取x的值。虽然两个写操作是「同时」进行的,但为了让系统“表现得像只有一个副本”,我们还是需要对它们进行一个先后排序。又因为它们是「同时」执行的,所以谁先谁后都有可能是合理的。如果我们认为x=42在x=43之前先执行,那么读取到的x的值就应该是43;反过来,如果我们认为x=43在x=42之前先执行,那么读取到的x的值就应该是42。
用户A先在第1个副本上执行x=42,然后用户B再在第2个副本上执行x=43,最后用户C在第3个副本上读取x的值。仍然为了让系统“表现得像只有一个副本”,直觉上看,用户C读取到的x的值似乎应该是43。但是,也不一定非要如此。因为两个写操作是分别由用户A和用户B发起的,他们并不知道彼此谁先谁后(虽然从时间上看用户A的写操作确实在先)。所以,我们也可以选择认为用户B执行x=43在用户A执行x=42之前。这样的话,用户C读取到的x的值就应该是42。当然,根据本文后面的讨论,这种排序就不满足线性一致性了,但却满足顺序一致性。
用户A执行x=42。
用户B执行x=43。
用户C读取到x的值是43。
用户B执行x=43。
用户A执行x=42。
用户C读取到x的值是42。
线性一致性和顺序一致性
整个系统可以看成由多个进程和一个共享的数据存储组成。对于数据存储的读写操作由进程发起。这里的进程,相当于本文前面提到的系统用户或系统使用者。
同一个进程发起的读写操作是先后顺序执行的。注意,这里的「进程」概念跟我们平常编程时用到的进程有所不同,进程里面不再分多个线程了。
数据存储可能有多个副本,但我们在讨论一致性模型的时候,把它看成一个整体来看待,不区分读写操作提交到了具体哪个副本上。
每个操作的执行,从开始调用到执行结束,都需要花一定的时间。因此,一个进程发起的操作还没有执行完的时候,另一个进程的操作可能就已经开始了。
A --> wi(x),表示一个写操作:第i个进程向数据对象x写入了值A。
ri(x) --> A,表示一个读操作:第i个进程从数据对象x中读到了值A。
条件I:重排后的序列中每一个读操作返回的值,必须等于前面对同一个数据对象的最近一次写操作所写入的值。
条件II:原来每个进程中各个操作的执行先后顺序,在这个重排后的序列中必须保持一致。
A --> w1(x)
r3(x) --> A
C --> w2(x)
r3(x) --> C
B --> w1(x)
r3(x) --> B
条件I:在这个重排后的序列中,每个读操作都返回了前面最近一次写入的值,比如第2个操作读到的值A,是前面第1个操作写入的;第4个操作读到的值C,是前面第3个操作写入的。
条件II:原来进程P1中的两个写操作,A --> w1(x)和B -->w1(x),在这个重排后的序列中仍然保持了先后顺序。与此类似,原来进程P3中的3个读操作,在这个重排后的序列中也保持了原来的先后顺序。
A --> w1(x)
B --> w1(x)
r3(x) --> B
r3(x) --> C
r3(x) --> A
第4个操作读到了值C,而前面最近一次写操作(第2个操作)所写入的值是B。
第5个操作读到了值A,而前面最近一次写操作(也是第2个操作)所写入的值是B。
条件III:不同进程的操作,如果在时间上不重叠,那么它们的执行先后顺序,在这个重排后的序列中必须保持一致。
进程P1的B --> w1(x)和进程P2的C --> w2(x),在执行时间上是重叠的,所以它们的排序不受条件III的约束。即,在重排后的序列中,这两个操作谁先谁后都可以。同样,进程P1的B --> w1(x)和进程P3的r3(x) --> A,也是如此。
进程P1的A --> w1(x)和进程P2的C --> w2(x),在执行时间上是不重叠的,即前一个操作都执行完了,后一个操作才开始执行。那么,这两个操作就必须满足条件III了:在重排后的序列中,A --> w1(x)必须排在C --> w2(x)前面。
与上面同样的道理,在重排后的序列中,进程P2的C -->w2(x)必须排在进程P3的r3(x) --> A之前。
A --> w1(x)
r3(x) --> A
C --> w2(x)
r3(x) --> C
B --> w1(x)
r3(x) --> B
进程内的任意两个操作之间,总是先后顺序执行的(执行时间上不可能重叠);而根据条件II,它们的先后顺序在最后重排后的序列中也会保持。
不同进程的不同操作之间,在执行时间上可能重叠(并发执行),也可能不重叠。根据条件III,不重叠的两个操作,它们在时间上的先后顺序,在最后重排后的序列中会得以保持。而对于执行时间上重叠的两个操作,它们在最后重排后的序列中的先后顺序没有规定。
它们都试图让系统“表现得像只有一个副本”一样。
它们都保证了程序执行顺序不会被打乱。体现在条件II对于进程内各个操作的排序保持上。
线性一致性考虑了时间先后顺序,而顺序一致性没有。
满足线性一致性的执行过程,肯定都满足顺序一致性;反之不一定。
线性一致性隐含了时效性保证(recency guarantee)。它保证我们总是能读到数据最新的值。
在顺序一致性中,我们有可能读到旧版本的数据。比如,在本文第一个顺序一致性的例子中,在进程P2将数据对象x的值写成了C之后,进程P3仍然读到了旧的值(A)。
最终一致性和它的特殊性
safety:它表示「坏事」永远不会发生。比如,一个系统如果遵守线性一致性或顺序一致性,那么就永远不会出现违反三个(对于顺序一致性来说是两个)条件的执行过程。而一旦系统出现问题,safety被违反了,我们也能明确指出是在哪个时间点上出现意外的。
liveness:它表示「好事」最终会发生。这种属性听起来会比较神奇:在任何一个时间点,你都无法判定liveness被违反了。因为,即使你期望的「好事」还没有发生,也不代表它未来不会发生。就像最终一致性一样,即使当前系统处于不一致的状态,也不代表未来系统就不会达到一致的状态。而只要系统存在“在未来某个时刻达到一致状态”的可能性,最终一致性就没有被违反。另外,可用性 (availability) 也属于liveness属性。
一致性的强弱关系
当且仅当一个一致性模型所能接受的执行过程,都能被另一个一致性模型所接受时(前者的集合是后者集合的子集),我们就说前者是比后者「更强」(stronger) 的一致性模型。
小结
参考文献:
[1] Peter Bailis, Ali Ghodsi, "Eventual Consistency Today: Limitations, Extensions, and Beyond", 2013.
[2] Werner Vogels, "Eventually Consistent", 2008.
[3] Leslie Lamport, "How to Make a Multiprocessor Computer That Correctly Executes Multiprocess Progranm", 1979.
[4] Mustaque Ahamad, Gil Neiger, James E. Burns, et al, "Causal Memory: Definitions, Implementation and Programming", 1994.
[5] Maurice P. Herlihy, Jeannette M. Wing, "Linearizability: A Correctness Condition for Concurrent Objects", 1990.
[6] Seth Gilbert, Nancy Lynch, "Brewer’s Conjecture and the Feasibility of Consistent, Available, Partition-Tolerant Web", 2002.
[7] Peter Bailis, Ali Ghodsi, et al, "Bolt-on Causal Consistency", 2013.
[8] Bowen Alpern, Fred B. Schneider, "Defining Liveness", 1985.
[9] Martin Kleppmann,《Designing Data-Intensive Applications》, 2017.
[10] Prince Mahajan, Lorenzo Alvisi, Mike Dahlin, "Consistency, Availability, and Convergence", 2011.
其它精选文章:
条分缕析分布式:到底什么是一致性?
漫谈分布式系统、拜占庭将军问题与区块链
看得见的机器学习:零基础看懂神经网络
基于Redis的分布式锁到底安全吗
深度学习、信息论与统计学
蓄力十年,做一个成就
三个字节的历险
由「精益创业」所想到的
知识的三个层次
扫码或长按关注微信公众号:张铁蕾。
有时候写点技术干货,有时候写点有趣的文章。
这个公众号有点科幻。