https://lotabout.me/2019/QQA-What-is-Sequential-Consistency/
https://wudaijun.com/2018/09/distributed-consistency/
线性一致性(Linearizability)
也叫做strong consistency或者atomic consistency,于 1987年提出,线性一致性强于顺序一致性,是程序能实现的最高的一致性模型,也是分布式系统用户最期望的一致性。 与顺序一致性相比,线性一致性只多了一条约束:如果事件A开始时间晚于事件B结束时间,则在最终事件历史中,要求B在A前。或者换句话说:事件生效时间是在事件开始时间到结束事件之间的某个时刻。 举个例子:
1 2 3 4 | A: --W(x,1)-- B: ---W(x,2)--- C: --R(x,2)-- -R(x,1)- D: --R(x,1)- |
这个案例是满足线性一致性的,因为W(x,1)
和W(x,2)
两个事件是同时发生的,因此事件生效点可能出现在开始和结束之间的任意点,就这个例子来讲,全局事件历史为: B-W(x, 2), C-R(x,2), A-W(x,1), D-R(x,1), C-R(x,1)
,其中D-R(x,1), C-R(x,1)
可以对换。
如果将 D 的 R(x,1)
改为 R(x,2)
,则不满足线性一致性,因为 C 的 R(x,1)
和 D 的 R(x,2)
都发生在 A, B 的 W 事件之后,因此它们应该对 x 的值结果具有相同的视角,不会读出不一致的结果。
如果说顺序一致性只保证单节点事件先后顺序的话,线性一致性还保证节点间的事件先后顺序,因此线性一致性的实现是非常困难的,一方面它需要一个全局同步的时钟(顺序一致性不需要全局同步的时钟),另一方面,越严格的一致性,会让分布式系统的复杂度更高,并且与之对应的,性能也会更差。
除了线性一致性和顺序一致性外,还一些其它更弱的一致性模型,比如因果一致性,最终一致性等,这里不再赘述。理解一致性本身和一些基础概念之后,理解其它一致性都不难。
内存一致性模型
在前面我将顺序一致性简单描述为调度器模型,这可能会让你想到操作系统的进程/线程调度,在进程/线程的视角下,多路 CPU 本身不就是个”微型分布式系统”么。是的,不过多核体系是早于分布式的,因此它的一致性模型叫做内存一致性模型。那问题来了,现代多核体系的内存一致性模型满足顺序一致性么?再来看个例子:
初始状态下,x = 0, y = 0
进程 A | 进程 B |
---|---|
x=1 | y=1 |
r1=y | r2=x |
按照顺序一致性的定义,我们将四条指令交叉任意排列,最终可能得到三种结果: r1==1,r2==0
,r1==0,r2=1
, r1=1,r2=1
三种结果,但不可能得到 r1=0,r2=0
的结果,因为这意味着全局执行顺序为 r1=y, r2=x, x=1, y=1
,并不符合单个进程内部的执行顺序。那么实际上,这段程序会输出r1=0,r2=0
的结果么?答案是会的,原因如下:
- 编译器指令重排,编译器会在不影响程序语义的情况下,调整代码中的指令顺序。但编译器只能够解析显示语义,即单线程上下文,它无法(或者说非常难)解析程序的隐式语义,即程序的多线程上下文依赖。
- CPU指令乱序执行,由于内存读取非常慢,CPU在不影响单线程语义的情况下,会将数据提前加载到缓存,提高执行效率。这就可能造成CPU指令处理顺序和程序指令顺序不一致,由于CPU 乱序只保证单线程语义,因此同样无法解析程序逻辑隐私因果关系,也可能造成结果不符程序预期。
- 缓存一致性,由于 LB/SB 的存在,缓存一致性是有极短延迟的,可能某个共享数据被CPU更新并写入到 SB(Store Buffer)中,其它 CPU 并不能即时看到。
基于以上三点,现代 CPU 架构基本都是不支持顺序一致性的,因为其需要非常高昂的代价,严重限制编译器和硬件的优化能力。比如顺序一致性要求处理器按照程序序(program order)来执行程序,但在大部分情况下,这是没必要的。因此,现代硬件体系遵循的其实是: sequential consistency for data race free programs,即对没有 data race 的程序来说,是满足顺序一致性的(编译器能够分析上下文相关性),但如果涉及到 data race,程序员需要使用 compile barrier 和 memory barrier 来限制编译器和 CPU 的乱序能力,以保证多线程下顺序一致性。这方面推荐一些优秀的文章: