在分布式(数据库)系统中,咱们常常会听到一些“高大上”却又比较“迷惑”的词汇,好比,ACID和CAP中的"C"是不是同一含义、Snapshot Isolation(SI)和Serializable Snapshot Isolation(SSI)区别是什么、Serializable和Linearizable是一个意思吗、Consistency和Consensus呢?若是你没法清晰的回答这些问题,那么但愿本文会对你有所帮助。算法
ACID和CAP中的C是都是Consistency的缩写,可是他们的含义倒是大相径庭的。ACID包含Atomicity(原子性)、Consistency(一致性)、Durability(持久性)、Isolation(隔离性)四个方面,其中Atomicity(原子性)、Durability(持久性)和 Isolation(隔离性)都是存储引擎提供的能力保障,可是Consistency(一致性)却不是存储引擎提供的,相反,它是业务层面的一种逻辑约束,以转帐这个最为经典的例子而言,A有100元RMB,B有0元RMB,如今A要转给B 50元RMB,那么转帐先后,A和B的总钱数必须仍是100元RMB,显然,这只是业务层规定的逻辑约束而已。在《DDIA》一书中,对ACID也有着以下的描述:数据库
Consistency和Consensus比较迷惑的不是它们的含义,更多的是他们看上去很“类似”,其实,只要把它们翻译成中文,也就好理解了。Consistency就是“一致性”的意思了(前文已经屡次提到),而Consensus是“共识”的意思。工程中,Consistency表示你“什么时候”能读到“正确”的数据,常见的有Linearizability Consistency(线性一致性)、Sequential Consistency(顺序一致性)和Casual Consistency(因果一致性)等。而Consensus多指一种“共识”算法,即多方参与共同决定一件事情,好比Basic Paxos算法就能够用来在一群分布式节点中决定一个值(诸如选主操做)。固然Consistency和Consensus也不是彻底没有关系的,不少Consistency模型中也会用到Consensus算法来完成。编程
Serializable和Linearizable能够说是最容易混淆的两个概念,Serializable字面翻译为“序列化”,Linearizable字面翻译为“线性化”,乍一看差很少,但事实上它们是彻底不一样的两个方面。为了直观的看出它们的关系,引用一张出自jepsen官网的图:多线程
Consistency并非分布式数据库中新增的概念,相反,Consistency存在于计算机中的各个角落。好比,多核CPU的Cache之间存在Consistency问题,并发编程中多线程之间也存在内存Consistency问题。本文就将以常见的多线程编程为例,着重介绍Linearizability Consistency(线性一致性)、Sequential Consistency(顺序一致性)和Casual Consistency(因果一致性)之间的概念和区别,不用担忧,这些概念和分布式中的Consistency模型是彻底一致的。闭包
Linearizability Consistency(线性一致性)的要求两个:并发
任何一次读都能读到某个数据的最近一次写的数据。dom
系统中的全部进程,看到的操做顺序,都和全局时钟下的顺序一致。分布式
显然这两个条件都对全局时钟有很是高的要求。比它要求更弱一些的,就是Sequential Consistency(顺序一致性)。性能
Sequential Consistency(顺序一致性),也一样有两个条件,其一与前面Linearizability Consistency(线性一致性)的要求同样,也是能够立刻读到最近写入的数据,然而它的第二个条件就弱化了不少,它容许系统中的全部进程造成本身合理的统一的一致性,不须要与全局时钟下的顺序都一致。这里的第二个条件的要点在于:操作系统
系统的全部进程的顺序一致,并且是合理的,就是说任何一个进程中,这个进程对同一个变量的读写顺序要保持,而后你们造成一致。
不须要与全局时钟下的顺序一致。
可见, Sequential Consistency(顺序一致性)在顺序要求上并无那么严格,它只要求系统中的全部进程达成本身认为的一致就能够了,即错的话一块儿错,对的话一块儿对,同时不违反程序的顺序便可,并不须要个全局顺序保持一致。
引用《分布式计算-原理、算法与系统》一张图进一步说明Linearizability Consistency(线性一致性)和Sequential Consistency(顺序一致性)之间的区别:
图a是知足Sequential Consistency(顺序一致性),可是不知足Linearizability Consistency(线性一致性)。缘由在于,从全局时钟的观点来看,P2进程对变量X的读操做在P1进程对变量X的写操做以后,然而读出来的倒是旧的数据。可是这个图倒是知足Sequential Consistency(顺序一致性)的,由于两个进程P1,P2的一致性并无冲突。从这两个进程的角度来看,顺序应该是这样的:Write(y,2) , Read(x,0) , Write(x,4), Read(y,2),每一个进程内部的读写顺序都是合理的,可是显然这个顺序与全局时钟下看到的顺序并不同。
图b知足Linearizability Consistency(线性一致性),由于每一个读操做都读到了该变量的最新写的结果,同时两个进程看到的操做顺序与全局时钟的顺序同样,都是Write(y,2) , Read(x,4) , Write(x,4), Read(y,2)。
图c不知足Sequential Consistency(顺序一致性),固然也就不知足Linearizability Consistency(线性一致性)。由于从进程P1的角度看,它对变量Y的读操做返回告终果0。那么就是说,P1进程的对变量Y的读操做在P2进程对变量Y的写操做以前,这意味着它认为的顺序是这样的:write(x,4) , Read(y,0) , Write(y,2), Read(x,0),显然这个顺序又是不能被知足的,由于最后一个对变量x的读操做读出来也是旧的数据。所以这个顺序是有冲突的,不知足顺序一致性。
Casual Consistency(因果一致性)在一致性的要求上,又比Sequential Consistency(顺序一致性)下降了:它仅要求有因果关系的操做顺序获得保证,非因果关系的操做顺序则无所谓。因果相关的要求是这样的:
本地顺序:本进程中,事件执行的顺序即为本地因果顺序。
异地顺序:若是读操做返回的是写操做的值,那么该写操做在顺序上必定在读操做以前。
闭包传递:和时钟向量里面定义的同样,若是a->b,b->c,那么确定也有a->c。
引用《分布式计算-原理、算法与系统》一书中的图来进一步说明 Casual Consistency(因果一致性)和 Sequential Consistency(顺序一致性)之间的区别:
图a知足 Sequential Consistency(顺序一致性),所以也知足Casual Consistency(因果一致性),由于从这个系统中的四个进程的角度看,它们都有相同的顺序也有相同的因果关系。
图b知足Casual Consistency(因果一致性)可是不知足 Sequential Consistency(顺序一致性)。首先P1和P2的写是没有因果关系的,从P3看来,Read(x,7) 表示P2的 Write(x,7)必定在P3的Read(x,7)以前, P3的Read(x,2)表示P1的Write(x,2)必定在P3的Read(x,2)以前,又由于P3中Read(x,7) 在Read(x,2)以前(本地因果顺序),所以,从P3角度看P1和P2的执行顺序应该是:Write(x,7)、Write(x,2)、Write(x,4)。一样的分析方法,能够得出从P4角度看P1和P2的执行顺序应该是:Write(x,2)、Write(x,4)、Write(x,7)。因为P3和P4看到的执行顺序不一致,所以这不知足Sequential Consistency(顺序一致性)要求。
图c展现了比Casual Consistency(因果一致性)更弱的一种一致性模型: PRAM(Pipelined Random Access Memory)管道式存储器,是Lipton和Sandberg于1988年在学术报告”PRAM: A scalable shared memory”中提出。如前所述, Sequential Consistency(顺序一致性)要求全部进程看到的程序执行顺序必须一致,而Casual Consistency(因果一致性)下降了一致性要求,它要求有因果关系的操做在全部进程上看到必须一致,而PRAM Consistency进一步下降一致性要求。先看PRAM定义:“…Writesdone by a single process are received by all other processes in the order inwhich they were issued, but writes from different processes may be seen in adifferent order by different processes.” 意即在PRAM中,不一样进程能够看到不一样的执行顺序,但在某一进程上的多个写操做,在全部进程上看到的顺序必须一致,而不一样进程上的写操做在不一样进程上看起来其执行顺序则能够不一致。图c展现的例子而言,从P3角度看到的P1和P2操做顺序为:Write(x,2)、Write(x,4)、Read(x,4)、Write(x,7),这是知足Casual Consistency(因果一致性)的。从P4角度看为:Write(x,2)、Read(x,4)、Write(x,7)、Write(x,4),这显然不知足Casual Consistency(因果一致性)的要求。
从jepsen官网的那张图能够看到,Casual Consistency(因果一致性)和PRAM下面还包含了Writes Follow Reads、Monotonic Reads、Monotonic Writes、Read Your Writes等一致性模型,这些都比较简单,鉴于篇幅缘由本文再也不赘述。
前文提到了,Serializable是一种事务隔离级别(并发的事务之间),是ACID中的Isoloation的意思。可是最开始ACID的Isolation只有4中隔离级别,随着技术的演进,出现了不少当初的标准没有定义的新的隔离级别,诸如snapshot Isoloation等。下表详细归纳了6种隔离级别,每种隔离级别强度层层递进,但也存在或引入某些新的问题,以snapshot Isoloation为例,它能解决repeatable read存在的幻读问题,可是却存在write skew的问题。
在这以前先定义几个概念:
这里提到的写锁和排他锁能够互换,读锁和共享锁能够互换,长期锁也被称为二阶段锁,就是事务某个时候锁上了算一个阶段,最后一块儿释放算一个阶段。
现象:最开始的阶段是一切皆有可能发生,没有任何锁,因此碰到的第一个问题是脏写。当一个事务覆盖写了另外一个正在运行的事务写入的值时就会发生脏写。好比下面的例子,事务一致性的约束性条件是x必须等于y, 一开始x和y初始值都为0,以后事务T1 准备写入 x=y=1 而且事务 T2 准备写入 x=y=2,可是因为T1和T2都没有对数据加锁,所以致使互相发生覆盖写(脏写),致使最终都成功commit以后,x==2,y==1,违反了约束性条件。
现象: 当一个事务读取另外一个仍处于运行中的事务写入的值时(未提交),就会发生脏读。好比下面的例子,事务一致性的约束性条件是x+y=100, 一开始x和y初始值都为50,事务T1准备将x改写为10(加长期写锁),此时事务T2读x为10(没有加任何锁),读y为50,对事务T2而言,x+y=60不知足约束性条件。
现象:在使用短时间读锁和长期写锁实现read commited以后就可能存在这种异常。好比下面的例子,事务一致性的约束性条件是x+y=100, 一开始x和y初始值都为50,T1读x为50(对x加短时间读锁),以后事务T2对x和y都作了修改(对x和y加长期写锁),而后成功提交。 以后T1读y为90,此时对T1而言,x+y=140不知足约束条件,所以出现不可重复读。(注:由于此时T2已经commit,所以这不属于脏读read uncommited,注意和上面的例子作区分。同时还要注意,不少人认为只有对同一个对象读屡次出现值不同才算不可重复读,其实这是狭隘的理解,其实能够屡次读不一样对象,只要屡次读会改变一致性约束就算不可重复读)
现象:幻读发生在正在执行的事务 T1 有断言读 (如select where) 时,另一个事务 T2 执行了和断言集合有交集的插入操做。好比 T1 在 T2 插入d以前读到了员工总数是 3,可是 T2 执行的时候有交集,插入了新的数据d,这个时候员工总数是 4,可是 T1 若是再读取的话,就会发现员工总数变成了 4,而不是最初的 3,这就是幻读。
更新丢失这个现象不是比幻读更约束的现象,这个是在防止脏读(实现read commited)之后可能会出现的现象。
现象:事务 T2 提交的写被其余事务覆盖,首先,这不是脏写,由于 T2 已经提交,其次没有脏读,由于在写以后没有读操做,这样的现象称为更新丢失。
现象:Cursor Lost Update 是上面 Lost Update 的一个变种,跟 SQL 的 cursor 相关。在下面的例子中,RC(x) 代表在 cursor 下面 read x,而 WC(x) 则代表在 cursor 下面写入 x。若是容许 T2 在 T1 RC 和 WC 之间写入数据,那么 T2 的更新也会丢失。
偏能够理解为不一致,这个是发生在多个数据之间有一个总的约束的时候(逻辑上业务层面的约束)。
现象:读偏也是在实现read commited后可能出现的现象。 假设如今的约束是x+y=100,事务T1先读x为50(T1对x加短时间读锁),而后事务T2将x改写为25(T2对x加长期写锁),将y改写为75(T2对x加长期写锁),对事务T2而言,x+y=100是知足约束的,因此它能够成功提交。T2提交成功以后(全部的锁也都释放了),此时T1还在运行,T1读y为75,x+y>100,不知足约束。
SI 解决冲突的方法通常是 “First-Commiter-Wins”, 也就是说,若是两个并发的事务修改了同一个数据,先写的事务会成功,然后写的事务会发现版本和本来的不一致而退出事务(abort)。
以这里的例子来讲,T1 的 y 只会读到本身开始时候的版本,也就是 50,而不是 75,这样读偏就解决了。可是快照隔离仍是不能解决另外一个问题,就是写偏。这是咱们要面临的新问题。
现象:这个和读偏相似,只不过,它是一种业务逻辑层面上的不一致。事务T1和T2一开始都有本身的版本,T1读x为30,并将y从10改成60,从T1自身角度看并无违反约束(x+y<=100)。一样,T2中先读y为10,并将x从30改成50,从T2角度看也并无违反约束(x+y<=100)。最后,在T1和T2提交时都是能够提交成功的(任意顺序),由于它们没有修改相同的数据,在数据层面不存在冲突。可是此时x+y已经大于100了,违反了业务逻辑层面的约束。
这个条件是只要有两个连续的 rw 依赖就会放弃提交,即便没有成环。这个检查发生在读的时候若是发现读的版本和本身开始以前的版本不一致就会找到依赖的事务,构建一条入边,另外一个事务构建一条出边,若是某个事务入边出边都有 rw 边,这个节点就会被做为嫌疑人。固然还有其余关于Serializable Snapshot Isolation(SSI)隔离的论文能够参考。
至此,本文对开始提出的几个概念和疑惑都作了介绍和解答,其实,分布式(数据库)系统中还有不少相似的概念,好比计算层经常使用的逻辑执行计划与物理执行计划、语法树与抽象语法树等,其中,有不少并非概念上的迷惑,而是因为这些技术和词汇大多由国外发明,在向国内渗透过程当中,通常人很难在一开始就能彻底东西背后的含义。就比如,外国人第一次看到“当心地滑”,很难搞清楚两种含义的区别。