分布式系统一致性
简介
分布式系统中,为了保障数据的可靠性,需要将数据冗余存储,已保证在部分节点故障是,仍能提供稳定的服务。
数据的冗余一般通过数据的多副本来实现,同一数据的不同副本应该分布于不同的节点上,以保障数据可靠。
为解决分布式系统中不同副本间数据的一致性问题的模型就称为一致性模型。
分类
一致性模型主要可以分为两类:
强一致性模型:保证所有进程对数据的读写顺序都保持一致;
弱一致性模型:不能保证所有进程对数据的读写顺序都保持一致;
强一致性模型
强一致性模型又分为:
线性一致性(Linearizable Consistency)
线性一致性也叫严格一致性(Strict Consistency)或者原子一致性(Atomic Consistency),它的条件是:
- 任何一次读都能读取到某个数据最近的一次写的数据。
- 所有进程看到的操作顺序都跟全局时钟下的顺序一致。
线性一致性是对一致性要求最高的一致性模型,就现有技术是不可能实现的。因为它要求所有操作都实时同步,在分布式系统中要做到全局完全一致时钟现有技术是做不到的。首先通信是必然有延迟的,一旦有延迟,时钟的同步就没法做到一致。当然不排除以后新的技术能够做到,但目前而言线性一致性是无法实现的。
顺序一致性(Sequential Consistency)
顺序一致性是 Lamport(1979)在解决多处理器系统共享存储器时首次提出来的。参考我之前写的文章《分布式系统:Lamport 逻辑时钟》。它的条件是:
- 任何一次读写操作都是按照某种特定的顺序。
- 所有进程看到的读写操作顺序都保持一致。
首先我们先来分析一下线性一致性和顺序一致性的相同点在哪里。他们都能够保证所有进程对数据的读写顺序保持一致。线性一致性的实现很简单,就按照全局时钟(可以简单理解为物理时钟)为参考系,所有进程都按照全局时钟的时间戳来区分事件的先后,那么必然所有进程看到的数据读写操作顺序一定是一样的,因为它们的参考系是一样的。而顺序一致性使用的是逻辑时钟来作为分布式系统中的全局时钟,进而所有进程也有了一个统一的参考系对读写操作进行排序,因此所有进程看到的数据读写操作顺序也是一样的。
那么线性一致性和顺序一致性的区别在哪里呢?通过上面的分析可以发现,**顺序一致性虽然通过逻辑时钟保证所有进程保持一致的读写操作顺序,但这些读写操作的顺序跟实际上发生的顺序并不一定一致。**而线性一致性是严格保证跟实际发生的顺序一致的。
弱一致性模型
因果一致性(Causal Consistency)
因果一致性是一种弱化的顺序一致性模型,因为它将具有潜在因果关系的事件和没有因果关系的事件区分开了。那么什么是因果关系?如果事件 B 是由事件 A 引起的或者受事件 A 的影响,那么这两个事件就具有因果关系。
举个分布式数据库的示例,假设进程 P1 对数据项 x 进行了写操作,然后进程 P2 先读取了 x,然后对 y 进行了写操作,那么对 x 的读操作和对 y 的写操作就具有潜在的因果关系,因为 y 的计算可能依赖于 P2 读取到 x 的值(也就是 P1 写的值)。
另一方面,如果两个进程同时对两个不同的数据项进行写操作,那么这两个事件就不具备因果关系。无因果关系的操作称为并发操作。这里只是简单陈述了一下,深入的分析见我之前写的文章《分布式系统:向量时钟》。
因果一致性的条件包括:
- 所有进程必须以相同的顺序看到具有因果关系的读写操作。
- 不同进程可以以不同的顺序看到并发的读写操作。
下面我们来分析一下为什么说因果一致性是一种弱化的顺序一致性模型。顺序一致性虽然不保证事件发生的顺序跟实际发生的保持一致,但是它能够保证所有进程看到的读写操作顺序是一样的。而**因果一致性更进一步弱化了顺序一致性中对读写操作顺序的约束,仅保证有因果关系的读写操作有序,没有因果关系的读写操作(并发事件)则不做保证。**也就是说如果是无因果关系的数据操作不同进程看到的值是有可能是不一样,而有因果关系的数据操作不同进程看到的值保证是一样的。
最终一致性(Eventual Consistency)
最终一致性是更加弱化的一致性模型,因果一致性起码还保证了有因果关系的数据不同进程读取到的值保证是一样的,而最终一致性只保证所有副本的数据最终在某个时刻会保持一致。
从某种意义上讲,最终一致性保证的数据在某个时刻会最终保持一致就像是在说:“人总有一天会死”一样。实际上我们更加关心的是:
- “最终”到底是多久?通常来说,实际运行的系统需要能够保证提供一个有下限的时间范围。
- 多副本之间对数据更新采用什么样的策略?一段时间内可能数据可能多次更新,到底以哪个数据为准?一个常用的数据更新策略就是以时间戳最新的数据为准。
由于最终一致性对数据一致性的要求比较低,在对性能要求高的场景中是经常使用的一致性模型。
以客户端为中心的一致性(Client-centric Consistency)
前面我们讨论的一致性模型都是针对数据存储的多副本之间如何做到一致性,考虑这么一种场景:在最终一致性的模型中,如果客户端在数据不同步的时间窗口内访问不同的副本的同一个数据,会出现读取同一个数据却得到不同的值的情况。为了解决这个问题,有人提出了以客户端为中心的一致性模型。以客户端为中心的一致性为单一客户端提供一致性保证,保证该客户端对数据存储的访问的一致性,但是它不为不同客户端的并发访问提供任何一致性保证。
举个例子:客户端 A 在副本 M 上读取 x 的最新值为 1,假设副本 M 挂了,客户端 A 连接到副本 N 上,此时副本 N 上面的 x 值为旧版本的 0,那么一致性模型会保证客户端 A 读取到的 x 的值为 1,而不是旧版本的 0。一种可行的方案就是给数据 x 加版本标记,同时客户端 A 会缓存 x 的值,通过比较版本来识别数据的新旧,保证客户端不会读取到旧的值。
以客户端为中心的一致性包含了四种子模型:
- 单调读一致性(Monotonic-read Consistency):如果一个进程读取数据项 x 的值,那么该进程对于 x 后续的所有读操作要么读取到第一次读取的值要么读取到更新的值。即保证客户端不会读取到旧值。
- 单调写一致性(Monotonic-write Consistency):一个进程对数据项 x 的写操作必须在该进程对 x 执行任何后续写操作之前完成。即保证客户端的写操作是串行的。
- 读写一致性(Read-your-writes Consistency):一个进程对数据项 x 执行一次写操作的结果总是会被该进程对 x 执行的后续读操作看见。即保证客户端能读到自己最新写入的值。
- 写读一致性(Writes-follow-reads Consistency):同一个进程对数据项 x 执行的读操作之后的写操作,保证发生在与 x 读取值相同或比之更新的值上。即保证客户端对一个数据项的写操作是基于该客户端最新读取的值。
总结
数据复制导致了一致性的问题,为了保持副本的一致性可能会严重地影响性能,唯一的解决办法就是放松一致性的要求。通过一致性模型我们可以理解和推理在分布式系统中数据复制需要考虑的问题和基本假设,便于结合具体的业务场景做权衡。每种模型都有效地限制了对一个数据项执行度操作应返回的值。通常来说限制越少的模型越容易应用,但一致性的保证就越弱。