1 前言

分布式DB的一个优势在于,它能够支持NoSQL做不到的强一致性。要深究这个问题,先得明白“强一致性”到底是啥。

  • 只要使用Paxos或Raft算法,就能实现强一致性
  • 也有人说,CAP三选二,分区容忍性和高可用性又必不可少,所以分布式DB做不到强一致性

这些观点都不准确。

“分布式系统”和“DB”这两个学科中,一致性(Consistency)都是重要概念,但表达内容不同:

  • 分布式系统,一致性是在探讨当系统内的一份逻辑数据存在多个物理的数据副本时,对其执行读写操作会产生什么样的结果,这也符合CAP理论的一致性表述
  • DB领域,“一致性”与事务密切相关,又进一步细化到ACID。I隔离性是“一致性”的核心内容,即如何协调事务之间冲突

因此,讨论分布式DB一致性其实在讨论:

  • 数据一致性
  • 事务一致性

从Google Spanner对其外部一致性(External Consistency)的论述也可佐证。

2 数据一致性

  • 包括分布式DB在内的分布式存储系统,为避免设备与网络的不可靠带来的影响,通常会存储多个数据副本。逻辑上的一份数据同时存储在多个物理副本,带来数据一致性问题
  • 讨论数据一致性还有个前提:同时存在读操作和写操作,否则也没意义

两因素一起,就是多副本数据上的一组读写策略,即“一致性模型”(Consistency Model,简称CM)。CM数量很多,难以分辨。

论文“The many faces of consistency”中的概念:

  • 状态一致性(State Consistency):数据所处的客观、实际状态所体现的一致性
  • 操作一致性(Operation Consistency):外部用户通过协议约定的操作,能够读取到的数据一致性

只是观察数据一致性的两个视角。

3 状态视角

任何变更操作后,数据只有两种状态,所有副本一致或不一致。

某些条件下,不一致状态是暂时,最终会转换到一致状态。那些永远不一致情况几乎不会去讨论,所以习惯将:

  • 不一致称为“弱一致”
  • 一致就叫“强一致”

3.1 强一致性:MySQL全同步复制

MySQL1主2备集群。

全同步复制(Fully Synchronous Replication)模式下,用户与MySQL交互的过程:

那么多数据一致性模型,究竟有啥不一样?_强一致性

主库与备库同步binlog时,主库只有收到两个备库的成功响应后,才能向客户端反馈提交成功。

用户获得响应时,主备库数据副本已达成一致,所以后续读操作肯定没问题,但这模式

副作用

① 性能差

主库须等到两个备库均返回成功,才能向用户反馈提交成功。图中由于网络阻塞,“备库2”稍晚于“备库1”返回响应,增加DB整体延时。下次,拖后腿的可能“备库1”。

主库响应时间取决于两个备库中延时最长那个。

② 可用性问题

任何设备都有可能出现故障,尤其x86这样通用商业设备,故障率更高。但全同步复制模式下,集群3节点被串联,如单机可用性95%,集群整体的可用性就是85.7%(95%95%95%=85.7%),跟单机相比反而降低。

集群规模越大,问题越严重,所以全同步复制模式少用。实现状态视角的强一致性代价太大,尤其与可用性有无法回避冲突,所以很多产品选择状态视角的弱一致性。

3.2 弱一致性:NoSQL最终一致性

NoSQL,应用弱一致性的典型代表,但对弱一致性接受仍有限度,即BASE理论中的E最终一致性(Eventually Consistency),弱于最终一致性的产品就无了。

最终一致性:在主副本执行写操作并反馈成功时,不要求其他副本与主副本保持一致,但【经过一段时间】后这些副本最终会追上主副本进度,重新达到数据状态的一致。

“经过一段时间”到底多久?得操作视角分析。

4 操作视角

最终一致性,语义包含很大不确定性,所以不能直接使用,而是加些限定条件,也就衍生出若干种一致性模型。因为它们是在副本不一致情况下,进行操作层面的封装来对外表现数据的状态,都可纳入操作视角。

4.1 写后读一致性(Read after Write Consistency)

也称:

  • “读写一致性”
  • 或“读自己写一致性”(Read My Writes Consistency)。最准确描述了这种一致性模型的使用效果。

小明喜欢发朋友圈。这天我分享完情侣照后,特意刷新朋友圈,确认分享成功。这过程中系统已实现“写后读一致性”:

那么多数据一致性模型,究竟有啥不一样?_强一致性_02

小明发布照片延时极短,用户体验好。因为数据仅被保存在主副本R1,立即反馈保存成功。而其他副本在后台异步更新,由于网络,每个副本更新速度不同,T2时刻上海的两个副本达成一致。这过程完全符合“最终一致性”。

小明的再刷新朋友圈动作,这时如访问副本R2,由于尚未完成同步,情侣照将消失,小明会觉得自己的照片丢了。此处,假定系统可通过某种策略由写入节点的主副本R1负责后续读,就实现了写后读一致性,保证小明再次读到自己照片。

自己写成功的任何数据,下刻一定能读到,内容保证与自己最后一次写完全一致,这就是“读自己写一致性”。旁观者角度也可称为“读你写一致性”(Read Your Writes Consistency)。

4.2 单调读一致性

但小明发朋友圈后,小红一定能看到吗?这次确实出问题。

小红也在刷朋友圈,看到小明刚分享照片,很开心。然后,小红收到一条信息,简单回复一下,又回到朋友圈再次刷新,发现照片不见了!小红生气质问小明,为什么这么快把照片删掉?小明蒙了!

异常流程:

那么多数据一致性模型,究竟有啥不一样?_强一致性_03

  • 小明发布照片后的瞬间,小红刷新朋友圈,此时读副本R1,所以小红看到照片
  • 片刻后,小红再刷新,此时读副本R2,照片消失。小红以为小明删了照片,但这是程序错误造成的,数据向后回滚,出现“时光倒流”

排除这种异常,系统须实现单调读一致性(Monotonic Read Consistency):一个用户一旦读到某值,不会读到比该值更旧的值。如变量X赋值三次:10、20、30;之后读变量X,如第一次读到20,下次只有读到20或30才合理。因为第一次读到20那刻,意味10已是过期数据。

实现

可将用户与副本建立固定映射,如哈希算法将用户ID映射到固定副本,避免在多副本切换,就不会出现这异常。

4.3 前缀一致性

复杂case的时间扭曲。

小明看总决赛,刚开球小明就拍了一张现场照片发到朋友圈,想要炫耀一下。小红也很喜欢篮球,但临时有事没有去现场,就在评论区问小明:“现在比分是多少?”小明回复:“4:2。”

小明同学在加拿大的小刚,却看到了一个奇怪的现象,评论区先出现了小明的回复“4:2。”,而后才刷到小红的评论“现在比分是多少?”。难道小明能预知未来?

那么多数据一致性模型,究竟有啥不一样?_强一致性_04

小明和小红的评论分别写入了节点N1和N2,但是它们与N3同步数据时,由于网络传输的问题,N3节点接收数据的顺序与数据写入的顺序并不一致,所以小刚是先看到答案后看到问题。

显然,问题与答案之间是有因果关系的,但这种关系在复制的过程中被忽略了,于是出现了异常。

保持这种因果关系的一致性,被称为前缀读前缀一致性(Consistent Prefix)。要实现这种一致性,可以考虑在原有的评论数据上增加一种显式的因果关系,这样系统可以据此控制在其他进程的读取顺序。

4.4 线性一致性(Linearizability)

“前缀一致性”案例中,问题与答案之间存在一种显式声明,但现实多数场景的因果关系更复杂,也不可能要求全部显式声明。

分布式DB无法要求应用系统在每次变更操作时附带声明一下,这次变更是因为读取了哪些数据导致。显式声明无法奏效,如何寻找因果关系?

“你所经历的一切,造就了现在的你。”一切对原因推测都是主观,之前发生的一切都可能是原因。更可靠的是将自然语意的因果关系转变为事件发生先后顺序。

线性一致性就建立在事件先后顺序。在线性一致性下,系统表现得像只有一个副本,所有操作被记录在一条时间线,并被原子化,任意两个事件都可比较先后顺序。

这些事件集,数学称具有“全序关系”的集合,而“全序”也称为“线性序”。线性一致性因此得名。

但集群中各节点不能做到真正时钟同步,节点都有各自时间线。如何将操作记录在一条时间线?需要一个绝对时间即全局时钟

产品层面

主流分布式DB多以实现线性一致性为目标,设计之初或演进过程纷纷引入全局时钟,如Spanner、TiDB、OceanBase、GoldenDB和巨杉。

工程实现

大多产品采用单点授时(TSO),即从一台时间服务器获取时间,同时配有高可靠设计。

而Spanner以全球化部署为目标,因为TSO有部署范围上的限制,所以Spanner实现方式是通过GPS和原子钟实现的全局时钟,也就是TrueTime,可保证在全球范围内任意节点能同时获得的一个绝对时间,误差7ms内。

但线性一致性,学术界有争议。反对者论据来自相对论重要结论,“时间是相对的”。无绝对时间,也就不存在全序的事件顺序,不同观察者可能对哪个事件先发生无法达成一致。因此,线性一致性有局限。

工程角度,我们的软件应用场景基本都在经典物理学适用范围内吧,所以线性一致性适用。

4.5 因果一致性(Causal Consistency)

既然线性一致性不完美,不依赖绝对时间的方法就是因果一致性

因果一致性的基础是偏序关系,即部分事件顺序可比。至少一个节点内部的事件可排序,依靠节点的本地时钟即可;节点间若发生通讯,则参与通讯的两个事件也可排序,接收方的事件一定晚于调用方的事件。

基于这种偏序关系,Leslie Lamport论文“Time, Clocks, and the Ordering of Events in a Distributed System”提出逻辑时钟

借助逻辑时钟仍然可以建立全序关系,当然这个全序关系是不够精确的。因为如果两个事件并不相关,那么逻辑时钟给出的大小关系是没有意义的。

因果一致性弱于线性一致性,但并发性能优势,也够处理多数异常现象,所以因果一致性在工业界得到应用。

前缀一致性 V.S 因果一致性

  • 实现前缀一致性,只要显示声明依赖关系,这有很多灵活做法
  • 因果一致性,建立在偏序关系基础上,很难在应用层面实现,要有底层支持

分布式DB领域,CockroachDB和YugabyteDB设计都采用逻辑混合时钟(Hybrid Logical Clocks),这方案源自Lamport的逻辑时钟。因此,这俩产品都没有实现线性一致性,而是接近因果一致性,其中CockroachDB将自己的一致性模型称为“No Stale Reads”。

5 总结

  1. 一致性模型林林总总,数量繁多,可从状态、操作视角观察,梳理其读写操作的不同策略
  2. 状态视角,数据一致性只有强一致、弱一致两种状态,而实际系统强一致少见,最终一致性是弱一致性特殊形式
  3. 操作视角,最终一致性可被封装成多种一致性模型,甚至是最强的线性一致性
  4. 分布式DB主要应用了线性一致性或因果一致性。线性一致性必须要有全局时钟,全局时钟可能来自授时服务器或者特殊物理设备(如原子钟),全局时钟的实现方式会影响到集群的部署范围;因果一致性可以通过逻辑时钟实现,不依赖于硬件,不会限制集群的部署范围。

一致性强度衡量(大到小):

  1. 线性一致性
  2. 顺序一致性(Sequentially Consistent),较少在分布式DB使用,不展开
  3. 因果一致性
  4. 写后读一致性、单调读一致性、前缀一致性。这三者之间无法比较强弱

还有常见弱一致性模型没提:有限旧一致性(Bounded Staleness)、会话一致性(Session Consistency)、单调写一致性(Monotonic Write Consistency)和读后写一致性(Write Follows Read Consistency)等,可参阅Azure Cosmos DB官方文档

会话一种致性的会话就是通常所指的用户Session,它是多种一致性模型的组合,参考Cosmos DB官方文档。

那么多数据一致性模型,究竟有啥不一样?_数据_05

6 FAQ

本文集中讨论了数据一致性,但是并没有特别强调Paxos。这等于说Paxos不是实现强一致性的必要条件。可是,有些时候大家又会将Paxos称为一致性协议。这“一致性协议”和数据一致性啥关系?

paxos协议定义的是一种决策过程。本文里的一致性是客观定义。

是的,一致性模型里有两个要点,读写策略和多副本状态。

状态视角,是不是只有全同步实现强一致性,即使像paxos、raft这些实现了操作上线性一致性算法,状态视角看也不是强一致。 然而全同步降低系统的可用性,paxos、raft不保证所有节点状态的一致,而是通过额外算法保证操作视角的一致性,同时提高系统的可用性。

Paxos,Raft是牺牲一定A(多数节点存活才ok),实现C的一种多节点的通信协议,Paxos无需主节点去统一时序,Raft,zab需主节点,它们都是实现线性一致性的方式?

Paxos这类共识算法,可看作复制协议的一种,虽然有时也叫一致性协议,但这一致性指Consensus。Consensus是实现数据一致性目标下的具体技术,但不是唯一选择。采用主从复制也可达到同效,PGXC风格分布式DB就是采用主从复制。

  • ACID里的C是指单副本、多操作的事务一致性

我觉得数据一致性是从数据的用户视角出发对数据属性的描述,而paxos协议是达成共识的过程的一种实现方式,是从数据的生产者或者维护者角度出发的

Paxos本质上是共识算法,主要是用来维护DB副本的一致性/权威性。而今天讲的一致性是从用户角度来谈,而不局限于是数据副本。 同时,今天讲的一致性也需要共识算法Paxos,Raft来保证。比如选举,如何才能选出正确的Leader等等。

高级别的一致性模型,可以基于Raft算法复制,但使用主从复制也是可以的😊

和那些偏理论的课程不同,能感觉到作者对于分布式DB的理解非常深刻,且结合了实际的金融业务,有点追剧的感觉了。 能不能拿出OceanBase goldendb这类领头羊产品给大家讲讲选型要注意的?

把学术的东西和目前工业界的实践联系起来,再落到具体的工作中,比如技术选型。所以,能得到你的肯定,我很高兴。当然,对产品的关注是必不可少的,从04开始的每一讲我都会对领头羊产品做局部设计上的拆解,并且比对不同方案的优劣,不过这个领头羊并不固定,因为我想向你介绍最有特点的设计。希望你能喜欢这种组织方式,后面的课程中,期待还能收到你的反馈,我们结合问题一起讨论。

先回答问题,“一致性协议”和数据一致性的关系是什么?很多留言的朋友都提到了一个重要的问题,即Paxos是一个共识算法,共识算法就是全局节点就某一事实达成一致,而数据一致中的数据我觉得可以理解为共识算法的日志,从此看来数据一致就是一致性协议的一个子集;而共识算法还包括很多其他部分,比如容错,日志压缩,集群变更等。 还有就是这些共识算法基本都遵从quorum的,所以都可以看成操作一致性,这也是用户所看到的东西是一致的,这不也是我们希望的吗?毕竟数据到底到没到全部节点不重要,用户看到才是王道,此类例子很多,比如zk,那么是否可以理解为操作一致性(即物理上的强一致性)没什么发挥空间呢? 还有CAP中C到底是指操作一致性还是状态一致性呢?

CAP中的C就是Consistency,是数据一致性,也是我们所说的操作视角的一致性,这里包含的多副本和读写策略两层含义。共识算法是复制协议层面的内容,并不一定对操作做严格定义。比如,就算我们使用Raft算法,但是如果开放了Follower读,也有可能达不到线性一致性或因果一致性的。事实上,CockroachDB的Follower读就是这样的。

我认为是数据的一致性依靠paxos,raft等一致性算法来保证

即使是Raft协议,如果开放follower读,也会出现不一致的情况,所以读写策略还是很重要的。

CAP的C是多副本、单操作的数据一致性;而ACID的C是单副本、多操作的事物一致性。

线性一致性和状态一致性(ACID 中的 C),到底有啥不一样?我理解的线性一致性是最终一致性里面最强的一致性模型。线性一致性和状态一致性的区别在于:读的那一瞬间,数据在多个副本之间是不是一样的;一样的就是状态一致性。线性一致性,我的理解:可以在多个数据副本之上抽象出一条逻辑上的时间轴,数据按提交到系统时的时间,从左到右依次排开;数据刚排在时间轴上时是灰色的,只有数据在多个副本之间同步完成,数据在时间轴上的点才回被“点亮”;读的时候,只能读到这样的数据:其在时间轴上已经被“点亮”了,且其左边的所有数据都已被“点亮”了。Ivan,是不是?

你把多数据副本的同步过程说的很形象。不过ACID里的C是说事务一致性,和数据一致性还是不同的,03讲有具体的介绍,我推荐你读一下。数据一致性中还有一点很重要,就是操作之间的先后次序判断,这也是为什么我们说,线性一致性必须要有全局时钟支持。也许02中的表述还不够直观,我建议关注一下第12讲,说不定会有收获

还是没太懂前缀一致性和因果一致性区别,前缀一致性是某些关系可比,并发不可比,不也是偏序关系?我还一直觉得这两个是一回事。

  • 因果一致性是靠逻辑时钟确定偏序关系,无需应用介入
  • 前缀一致性靠事件之间显式声明的依赖关系,可在应用层处理

MySQL案例使用的全同步复制,Raft也是强一致性算法,但它在应答客户端请求成功后,并不保证多副本间暂时的数据一致性,有可能数据不同。只不过在收到读请求时,都会转发给M来保证强一致性?

  • Raft是多数派协议,从写入成功那一刻的数据状态来说,肯定不一致
  • 不过,通过操作方面的封装,约定由主副本对外提供服务,所以不会体现出副本间数据差异。这样的设计不仅简化了客户端的操作,还提高了读请求的性能
  • 一致性模型,除了副本的状态,还要看读写操作。最终一致性的定义,其实只是描述副本的状态。一致性模型,主要还是从读写操作的效果分析,也和数据副本的一致性有关但并非强依赖。如不使用Raft,用半同步,也可做到线性一致性

弱一致性是说有可能不同用户看到的state不一样,而不仅仅是副本之间数据不一致。弱一致性的典型情况:A和B同时发起请求对同一数据进行修改,由于并发写入,可能导致最终的数据状态取决于谁的修改先到达或被执行,从而覆盖了另一个的修改?

弱一致性在某些分布式系统中是可以接受的,尤其是在需要高可用性和性能的场景下,因为强一致性往往需要更多的通信和同步开销,可能会降低系统的性能。对于一些应用,比如社交网络、购物平台等,短暂的数据不一致可能不会对用户体验产生显著影响。然而,在某些关键业务领域,如金融交易、医疗系统等,弱一致性可能是不可接受的,因为需要确保数据的准确性和完整性。在这些情况下,通常会选择使用强一致性的策略来保证数据的一致性。

Paxos这类强一致性共识算法,只保证副本之间数据最终达到一致,但没有保证事务性,即复制过程中会出现中间状态,用户角度来看就会看到数据不一致了。所以需要操作层面来保证数据的一致性。

强一致性:MySQL 全同步复制现在有一个 MySQL 集群,由一主两备三个节点构成,那么在全同步复制(Fully Synchronous Replication)模式下,用户与 MySQL 交互的过程是这样的。 如果同步复制的过程过程中,如A是主节点,有2个备节点BC,对A执行操作的时候,A先将binlog复制给B,然后再复制给C,假如复制给B成功了,此时复制给C失败了,那刚好此时如果有用户分别去B、C中查询,此时看到的数据是不一样的,此时就不满足强一致,咋解决?

这时候主节点也没提交成功,而且只复制了日志,还没apply到备节点。最重要的是这个复制机制只承诺了主节点对外提供服务,RPO为0,没有承诺备节点的一致性读。

线性一致性和因果一致性就是时钟上差别,为啥这么区别,因为对用户有特别影响?就是保证写入数据在下刻一定能被读到。

zk是顺序一致性和因果一致性?

Zookeeper确实提供了两种不同类型的一致性保证:顺序一致性和原子广播(也称为因果一致性):

  • 顺序一致性保证所有客户端看到的更新顺序是相同的
  • 原子广播保证所有客户端看到的更新顺序都是基于相同的因果关系

这些一致性保证对于分布式系统中的协调和同步非常重要。

Spanner强一致性

只有Spanner实现真正强一致性,一致性其实分为状态一致性与操作一致性,我理解状态一致性是内部,而操作一致性是对client,本文说Spanner实现强一致性指操作一致性?我了解Spanner也是采用大多数成功,比如2 of 3成功后即返回写入成功,那么有一个副本其实可能处于不同步状态,那么Spanner其实是不满足状态层面的强一致性的?不过Spanner的每次读取会拿最新的数据,且有True Time保证数据是线性的,所以老师所说的Spanner应该是满足操作的强一致性是吗?可是感觉TiDB也满足了操作的强一致性,是因为我对TiDB了解不足,还是我理解错了Spanner的强一致性呢?

TiDB基于TSO也能做到线性一致性,但是隔离级别只支持到SI,比Spanner的外部一致性还是要低一些。