大约在五六年前,第一次接触到了当时已是hot topic的NoSql。不过那个时候学的用的都是mysql,Nosql对于我而言仍是新事物,并无真正使用,只是不明觉厉。可是印象深入的是这么一张图片(后来google到图片来自这里): node
这张图片是讲数据库(包括传统的关系型数据库和NOSQL)与CAP理论的关系。因为并NoSql并无实践经验,也没有去深刻了解,对于CAP理论更是只知其一;不知其二。所以,为何某一款数据库被划分到哪个阵营,并不清楚。 mysql
工做以后对MongoDB使用得比较多,有了必定的了解,前段时间又看到了这张图,因而想搞清楚,MongoDB是否是真的属于CP阵营,又是为何?怀疑这个问题的初衷是由于,MongoDB的经典(官方推荐)部署架构中都会使用replica set,而replica set经过冗余和自动failover提供高可用性(Availability),那么为何上图中说MongoDB牺牲了Avalability呢?而我在MongoDB的官方文档中搜索"CAP",并无搜索到任何内容。因而我想本身搞清楚这个疑问,给本身一个答案。 web
本文先阐明什么是CAP理论,以及关于CAP理论的一些文章,而后讨论MongoDB在一致性与可用性之间的折中与权衡。 算法
对CAP理论我只知道这三个单词的意思,其解释也是来自网上的一些文章,并不必定准确。因此首先得追根溯源,搞清楚这个理论的起源和准确的解释。我以为最好的开始就是wikipedia,从上面能够看到比较准确的介绍,更为重要的是能够看到不少有用的连接,好比CAP理论的出处,发展演变过程。 sql
CAP理论是说对于分布式数据存储,最多只能同时知足一致性(C,Consistency)、可用性(A, Availability)、分区容错性(P,Partition Tolerance)中的二者。 数据库
一致性,是指对于每一次读操做,要么都可以读到最新写入的数据,要么错误。 缓存
可用性,是指对于每一次请求,都可以获得一个及时的、非错的响应,可是不保证请求的结果是基于最新写入的数据。 安全
分区容错性,是指因为节点之间的网络问题,即便一些消息对包或者延迟,整个系统能继续提供服务(提供一致性或者可用性)。 网络
一致性、可用性都是使用很是宽泛的术语,在不一样的语义环境下具体所指是不同的,好比在cap-twelve-years-later-how-the-rules-have-changed一文中Brewer就指出"CAP中的一致性与ACID中的一致性并非同一个问题",所以后文中除非特别说明,所提到的一致性、可用性都是指在CAP理论中的定义。只有明确了你们都是在一样的上下文环境,讨论才有意义。 架构
对于分布式系统,网络分区(network partition)这种状况是难以免的,节点间的数据复制必定存在延迟,若是须要保证一致性(对全部读请求都可以读到最新写入的数据),那么势必在必定时间内是不可用的(不能读取),即牺牲了可用性,反之亦然。
按照维基百科上的描述,CAP之间的相互关系大约起源于1998年,Brewer在2000年的PODC(Symposium on Principles of Distributed Computing)上展现了CAP猜测[3],在2002年,由另外两名科学家Seth Gilbert、Nancy Lynch证实了Brewer的猜测,从而从猜测变成了定理。CAP理论起源
在Towards Robust Distributed Systems中,CAP理论的提出者Brewer指出:在分布式系统中,计算是相对容易的,真正困难的是状态的维护。那么对于分布式存储或者说数据共享系统,数据的一致性保证也是比较困难的。对于传统的关系型数据库,优先考虑的是一致性而不是可用性,所以提出了事务的ACID特性。而对于许多分布式存储系统,则是更看重可用性而不是一致性,一致性经过BASE(Basically Available, Soft state, Eventual consistency)来保证。下面这张图展现了ACID与BASE的区别:
简而言之:BASE经过最终一致性来尽可能保证服务的可用性。注意图中最后一句话"But I think it's a spectrum",就是说ACID BASE只是一个度的问题,并非对立的两个极端。
2002年,在Brewer's conjecture and the feasibility of consistent, available, partition-tolerant web services中,两位做者经过异步网络模型论证了CAP猜测,从而将Brewer的猜测升级成了理论(theorem)。但实话说,我也没有把文章读得很明白。
2009年的这篇文章brewers-cap-theorem,做者给出了一个比较简单的证实:
如上图所示,N1,N2两个节点存储同一份数据V,当前的状态是V0。在节点N1上运行的是安全可靠的写算法A,在节点N2运行的是一样可靠的读算法B,即N1节点负责写操做,N2节点负责读操做。N1节点写入的数据也会自动向N2同步,同步的消息称之为M。若是N1,N2之间出现分区,那么就无法保证消息M在必定的时间内到达N2。
从事务的角度来看这各问题
α这个事务由操做α1, α2组成,其中α1是写数据,α2是读数据。若是是单点,那么很容易保证α2能读到α1写入的数据。若是是分布式的状况的状况,除非能控制 α2的发生时间,不然没法保证 α2能读到 α1写入的数据,但任何的控制(好比阻塞,数据集中化等)要么破坏了分区容错性,要么损失了可用性。
另外,这边文章指出不少状况下 availability比consistency重要,好比对于facebookgoogle这样的网站,短暂的不可用就会带来巨大的损失。
2010年的这篇文章brewers-cap-theorem-on-distributed-systems/,用了三个例子来阐述CAP,分别是example1:单点的mysql;example2:两个mysql,但不一样的mysql存储不一样的数据子集(相似sharding);example3:两个mysql,对A的一个insert操做,须要在B上执行成功才认为操做完成(相似复制集)。做者认为在example1和example2上都能保证强一致性,但不能保证可用性;在example3这个例子,因为分区(partition)的存在,就须要在一致性与可用性之间权衡。
于我看来,讨论CAP理论最好是在"分布式存储系统"这个大前提下,可用性也不是说总体服务的可用性,而是分布式系统中某个子节点的可用性。所以感受上文的例子并非很恰当。
CAP理论发展
到了2012年,CAP理论的发明人 Brewer就CAP理论再次撰文《CAP Twelve Years Later: How the "Rules" Have Changed》,这篇文章比较长,但思路清晰,高屋建瓴,很是值得一读。网上也有对用的中文译文《CAP理论十二年回顾:"规则"变了》,翻译还不错。
文章中,最主要的观点是CAP理论并非说三者不需选择二者。首先,虽然只要是分布式系统,就可能存在分区,但分区出现的几率是很小的(不然就须要去优化网络或者硬件),CAP在大多数时候容许完美的C和A;只有在分区存在的时间段内,才须要在C与A之间权衡。其次,一致性和可用性都是一个度的问题,不是0或者1的问题,可用性能够在0%到100%之间连续变化,一致性分为不少级别(好比在casandra,能够设置consistency level)。所以,当代CAP实践的目标应该是针对具体的应用,在合理范围内最大化数据一致性和可用性的效力。
文章中还指出,分区是一个相对的概念,当超过了预约的通讯时限,即系统若是不能在时限内达成数据一致性,就意味着发生了分区的状况,必须就当前操做在C和A之间作出选择。
从收入目标以及合约规定来说,系统可用性是首要目标,于是咱们常规会使用缓存或者过后校核更新日志来优化系统的可用性。所以,当设计师选择可用性的时候,由于须要在分区结束后恢复被破坏的不变性约。
实践中,大部分团体认为(位于单一地点的)数据中心内部是没有分区的,所以在单一数据中心以内能够选择CA;CAP理论出现以前,系统都默认这样的设计思路,包括传统数据库在内。
分区期间,独立且能自我保证一致性的节点子集合能够继续执行操做,只是没法保证全局范围的不变性约束不受破坏。数据分片(sharding)就是这样的例子,设计师预先将数据划分到不一样的分区节点,分区期间单个数据分片多半能够继续操做。相反,若是被分区的是内在关系密切的状态,或者有某些全局性的不变性约束非保持不可,那么最好的状况是只有分区一侧能够进行操做,最坏状况是操做彻底不能进行。
上面摘录中下选线部分跟MongoDB的sharding状况就很类似,MongoDB的sharded cluste模式下,shard之间在正常状况下,是无需相互通讯的。
在13年的文章中《better-explaining-cap-theorem》,做者指出"it is really just A vs C!",由于
(1)可用性通常是在不一样的机器之间经过数据的复制来实现
(2)一致性须要在容许读操做之间同时更新几个节点
(3)temporary partion,即几点之间的通讯延迟是可能发生了,此时就须要在A和 C之间权衡。但只有在发生分区的时候才须要考虑权衡。
在分布式系统中,网络分区必定会发生,所以"it is really just A vs C!"
在《经过一步步建立sharded cluster来认识MongoDB》一文中,对MongoDB的特性作了一些介绍,包括高性能、高可用、可扩展(水平伸缩),其中,MongoDB的高可用性依赖于replica set的复制与自动failover。对MongoDB数据库的使用有三种模式:standalone,replica set, shareded cluster,在前文中详细介绍了shared cluster的搭建过程。
standalone就是单个mongod,应用程序直接链接到这个Mongod,在这种状况下无分区容错性可言,也必定是强一致性的。对于sharded cluster,每个shard也都推荐是一个replica set。MongoDB中的shards维护的是独立的数据子集,所以shards之间出现了分区影响不大(在chunk迁移的过程可能仍是有影响),所以也主要考虑的是shard内部replica set的分区影响。因此,本文中讨论MongoDB的一致性、可用性问题,针对的也是MongoDB的replica set。
对于replica set,只有一个primary节点,接受写请求和读请求,其余的secondary节点接受读请求。这是一个单写、多读的状况,比多读、多写的状况仍是简化了许多。后文为了讨论,也是假设replica set由三个基点组成,一个primary,两个secondary,且全部节点都持久化数据(data-bearing)
MongoDB关于一致性、可用性的权衡,取决于三者:write-concern、read-concern、read-preference。下面主要是MongoDB3.2版本的状况,由于read-concern是在MongoDB3.2版本中才引入的。
write concern表示对于写操做,MongoDB在什么状况下给予客户端响应。包括下面三个字段:
w:表示当写请求在value个MongoDB实例处理以后才向客户端返回。取值范围:
1:默认值,表示数据写入到standalone的MongoDB或者replica set的primary以后返回
0:不用写入就直接向客户端返回,性能高,但可能丢数据。不过能够配合j:True来增长数据的可持久性(durability)
>1:只有在replica set环境下才有用,若是value大于的replica set中节点的数目,那么可能致使阻塞
'majority':当数据写入到replica set的大多数节点以后向客户端返回,对于这种状况,通常是配合read-concern使用:
"majority"
j:表示当写请求在写入journal以后才向客户端返回,默认为False。两点注意:
若是在对于未开启journaling的MongoDB实例使用j:True,会报错
在MongoDB3.2及以后,对于w>1,须要全部实例都写到journal以后才返回
wtimeout:表示写入的超时时间,即在指定的时间(number),若是还不能向客户端返回(w大于1的状况),那么返回错误
默认为0,至关于没有设置该选项
在MongoDB3.4中,加入了writeConcernMajorityJournalDefault
read-reference:
在前文已经讲解过,一个replica set由一个primary和多个secondary组成。primary接受写操做,所以数据必定是最新的,secondary经过oplog来同步写操做,所以数据有必定的延迟。对于时效性不是很敏感的查询业务,能够从secondary节点查询,以减轻集群的压力。
MongoDB指出在不一样的状况下选用不一样的read-reference,很是灵活。MongoDB driver支持一下几种read-reference:
primary:默认模式,一切读操做都路由到replica set的primary节点
primaryPreferred:正常状况下都是路由到primary节点,只有当primary节点不可用(failover)的时候,才路由到secondary节点。
secondary:一切读操做都路由到replica set的secondary节点
secondaryPreferred:正常状况下都是路由到secondary节点,只有当secondary节点不可用的时候,才路由到primary节点。
nearest:从延时最小的节点读取数据,无论是primary仍是secondary。对于分布式应用且MongoDB是多数据中心部署,nearest能保证最好的data locality。
若是使用secondary或者secondaryPreferred,那么须要意识到:
(1)由于延时,读取到的数据可能不是最新的,并且不一样的secondary返回的数据还可能不同;
(2)对于默认开启了balancer的sharded collection,因为还未结束或者异常终止的chunk迁移,secondary返回的多是有缺失或者多余的数据
(3)在有多个secondary节点的状况下,选择哪个secondary节点呢,简单来讲是"closest"即平均延时最小的节点,具体参加Server Selection Algorithm
read concern是在MongoDB3.2中才加入的新特性,表示对于replica set(包括sharded cluster中使用复制集的shard)返回什么样的数据。不一样的存储引擎对read-concern的支持状况也是不同的
read concern有如下三个level:
local:默认值,返回当前节点的最新数据,当前节点取决于read reference。
majority:返回的是已经被确认写入到多数节点的最新数据。该选项的使用须要如下条件: WiredTiger存储引擎,且使用electionprotocol version 1;启动MongoDB实例的时候指定
linearizable:3.4版本中引入,这里略过了,感兴趣的读者参考文档。
在文章中有这么一句话:
Regardless of the read concern level, the most recent data on a node may not reflect the most recent version of the data in the system.
就是说,即使使用了read concern:majority,返回的也不必定是最新的数据,这个和NWR理论并非一回事。究其根本缘由,在于最终返回的数值只来源于一个MongoDB节点,该节点的选择取决于read reference。
在这篇文章中,对readconcern的引入的意义以及实现有详细介绍,在这里只引用核心部分:
readConcern的初衷在于解决『脏读』的问题,好比用户从 MongoDB的 primary上读取了某一条数据,但这条数据并无同步到大多数节点,而后 primary就故障了,从新恢复后这个primary节点会将未同步到大多数节点的数据回滚掉,致使用户读到了『脏数据』。
当指定 readConcern级别为 majority时,能保证用户读到的数据『已经写入到大多数节点』,而这样的数据确定不会发生回滚,避免了脏读的问题。
一致性 or可用性?回顾一下CAP理论中对一致性可用性的问题:一致性,是指对于每一次读操做,要么都可以读到最新写入的数据,要么错误。可用性,是指对于每一次请求,都可以获得一个及时的、非错的响应,可是不保证请求的结果是基于最新写入的数据。前面也提到,本文对一致性可用性的讨论是基于replica set的,是不是shared cluster并不影响。另外,讨论是基于单个客户端的状况,若是是多个客户端,彷佛是隔离性的问题,不属于CAP理论范畴。基于对write concern、read concern、read reference的理解,咱们能够得出如下结论。
回过来来看,MongoDB所说的高可用性是更普世意义上的可用性:经过数据的复制和自动failover,即便发生物理故障,整个集群仍是可以在短期内回复,继续工做,况且恢复也是自动的。在这个意义上,确实是高可用的。