atomic applications,atomic品牌

  

  专注Java领域优质技术,欢迎关注。   

  

  作者:马丁克莱普曼译者:卢潘   

  

  Kirito:这篇文章的原作者是Martin Kleppmann,他写了一本《Designing Data-Intensive Applications》的书。无论是这本书还是这篇文章,都可以从独特的视角阐释那些可能被大多数人误解的观点,让读者清醒。在此之前,我隐约对文章中提到的一些CAP误区嗤之以鼻。这篇文章让我更加确信自己之前的零碎认知。毫不夸张的说,这应该是我看过的最通俗深刻的CAP科普文章了。   

  

  在杰夫霍奇斯(Jeff Hodges)给年轻人做分布式系统笔记的精彩博文中,他建议我们用CAP定理来评论系统。很多人听了这个建议,把自己的系统描述成' CP '(网络分区一致但不可用)、' AP '(网络分区可用但不一致)或者有时候是' CA '(声明'我五年前没看过Coda的文章')。   

  

  我同意杰夫的所有观点。只是他关于CAP定理的观点,我肯定不同意。CAP定理本身过于简单化,被广泛误解,所以在描述系统时用处不大。所以,我要求大家不要再引用和讨论CAP定理了。相反,我们应该使用更精确的术语来理解我们系统的权衡。   

  

  是的,我意识到我不希望别人再讨论这个话题,这很讽刺,但我正在写这个话题的博文。但至少这样,当有人问我为什么不喜欢讨论CAP定理的时候,我可以把这篇文章的链接给他。还有,很抱歉这篇文章有点恶意,但至少这篇恶意有文献参考。)   

  

  

CAP 用的是非常精确的定义

  

  

  如果你想引用CAP这个定理(而不是一个数据库营销用的模糊概念),你需要用一个非常精确的定义。数学要求精确。这个证明只有当你的话和定理证明里的定义一样的时候才有意义。CAP的证明使用了非常具体的定义。   

  

  一致性意味着CAP中的线性化。而这是一种非常特殊(也非常强烈)的一致性。尤其是酸中的C虽然是稠度,但和这里的稠度无关。稍后我会解释线性化是什么意思。在CAP中,可用性被定义为“如果一个正常工作的数据库节点接收到请求,则每个请求必须返回一个无错误的结果”。注意,这里的一些节点能够处理这个请求是不够的。任何工作节点都必须能够处理这个请求。许多自称“高可用性”的系统通常不符合可用性的定义。分区容差基本上意味着通信是在异步网络中进行的。信息可能会延迟或丢失。互联网和我们所有的数据中心都有这个属性。所以在这件事上我们别无选择。   

  

  还要注意,CAP描述的不是任何旧系统,而是一个非常特殊的系统:   

  

  CAP系统的模型是一个只能读写单一数据的寄存器。仅此而已。CAP没有提到任何涉及多个对象的事务。它们根本不在这个定理的范围之内,除非你能把这些问题归结为单个寄存器问题。CAP定理只考虑了网络分区失效的情况(比如节点还在运行,但是它们之间的网络已经不工作了)。这种失败肯定会发生,但不是唯一会出错的地方。节点可能会崩溃或重新启动,您可能没有足够的磁盘空间,您可能会遇到软件错误,等等。在构建分布式系统时,你需要考虑更多的问题。如果过于关注CAP,就容易忽略其他重要问题。而CAP根本没有提到潜伏期。通常人们实际上更关心延迟而不是可用性。事实上,满足CAP可用性的系统可以花任意长的时间来回复请求,同时保持可用性属性。让我冒一次险。我猜如果你的系统花两分钟加载一个页面,你的用户不会称之为“可用”。   

  

  如果你的话符合CAP证书中的精确定义,那么它适用于你。但是如果你的一致性和可用性有其他的含义,那么你就不能指望CAP仍然适用于你。当然,这并不意味着你可以通过重新定义一些词语来做一些不可能的事情!只是说明你不能依赖CAP给你提供指导,也不能通过CAP捍卫自己的观点。   

  

  如果上限定理不适用,那么就意味着你要自己考虑取舍。你必须根据自己对一致性和可用性的定义来思考这些属性,如果能证明你的定理就更好了。但是请不要叫它CAP定理,因为这个名字已经有人用了。   

  

  

可线性化

  

  

  如果你对线性化(即CAP中的一致性)不熟悉,那么我来简单解释一下。的正式定义不是特别直观,但其核心思想被非正式地描述为:   

  

  如果操作B成功地完成了操作A,那么整个系统必须处于操作A已经完成或为操作B更新的状态.   

  

  为了解释得更清楚,我们来看一个例子。这个例子中的系统没有被线性化。   

  

  看下图(我还没发行的书的预览):   

https://tupian.lamuhao.com/pic/img.php?k=atomic applications,atomic品牌1.jpg">

  


  

这张图展示了 Alice 还有 Bob, 他们在同一个房间,都在用他们的手机查询 2014 年世界杯的决赛结果。就在最终结果刚发布之后,Alice 刷新了页面,看到了宣布冠军的消息,而且很兴奋地告诉了 Bob。Bob 马上也重新加载了他手机上的页面,但是他的请求被送到了一个数据库的拷贝,还没有拿到最新的数据,结果他的手机上显示决赛还正在进行。

  

如果 Alice 和 Bob 同时刷新,拿到了不一样的结果,并不会太让人意外。因为他们不知道具体服务器到底是先处理了他们中哪一个请求。但是Bob知道他刷新页面是在 Alice 告诉了他最终结果之后的。所以他预期他查询的结果一定比 Alice 的更新。事实是,他却拿到了旧的结果。这就违反了可线性化。

  

只有 Bob 通过另外一个沟通渠道从 Alice 那里知道了结果, Bob 才能知道他的请求一定在 Alice 之后。如果 Bob 没有从 Alice 那里听到比赛已经结束了,他就不会知道他看到的结果是旧的。

  

如果你在建一个数据库,你不知道用户们会有什么另外的沟通渠道。所以,如果你想提供可线性化(CAP 的一致性),你就需要让你的数据库看起来就好像只有一个拷贝,虽然实际上可能有多个备份在多个地方。

  

这是一个非常昂贵的属性,因为它要求你做很多协调工作。甚至你电脑上的 CPU 都不提供本地内存的可线性化访问! 在现代的 CPU 上,你需要用 memory barrier 指令来达到可线性化访问。甚至测试一个系统是不是可线性化的也是很困难的。

  

CAP 可用性

让我们来简短的讨论一下为什么在网络分区的情况下,我们要放弃可用性和一致性中的一个。

  

举个例子,你的数据库有两个拷贝在两个不同的数据中心。具体怎么做备份并不重要,可以是 single-master,或者多个 leader,或者基于 quorum 的备份(Dynamo 使用的方式)。要求是当数据被写到一个数据中心的时候,他也一定要被写到另一个数据中心。假设 client 只连接到其中一个数据中心,而且连接两个数据中心的网络故障了。

  

那么现在假设网络中断了,这就是我们所说的网络分区的意思。接下来怎么样呢?

  

  

显然你有两个选择:

  

你的应用还是被允许写到数据库,所以两边的数据库还是完全可用的。但是一旦两个数据库之间的网络中断了,任何一个数据中心的写操作就不会在另一个数据中心出现。这就违反了可线性化(用之前的例子,Alice 可能链接到了一号数据中心,而 Bob 连接到了二号数据中心)。如果你不想失去可线性化,你就必须保证你的读写操作都在同一个数据中心,你可能叫这它 leader。另一个数据中心,因为网络故障不能被更新,就必须停止接收读写操作,直到网络恢复,两边数据库又同步了之后。所以虽然非 leader 的数据库在正常运行着,但是他却不能处理请求,这就违反了 CAP 的可用性定义。

  

(而这个,其实就是 CAP 定理的证明。这就是全部了。这里的例子用到了两个数据中心,但是对于一个数据中心内的网络故障也是同样适用的。我只是觉得用两个数据中心这样更容易考虑这个问题。)

  

注意到上面第二点,就算它违反了 CAP 的可用性,但我们还是在成功地处理着请求。所以当一个系统选择了可线性化(也就是说不是 CAP 可用的),这并不一定意味着网络分区一定会造成应用停运。如果你可以把用户的流量转移到leader数据库,那么用户根本就不会注意到任何问题。

  

实际应用中的可用性和 CAP 可用性并不相同。你应用的可用性多数是通过 SLA 来衡量的(比如 99.9% 的正确的请求一定要在一秒钟之内返回成功),但是一个系统无论是否满足 CAP 可用性其实都可以满足这样的 SLA。

  

实际操作中,跨多个数据中心的系统经常是通过异步备份(asynchronous replication)的,所以不是可线性化的。但是做出这个选择的原因经常是因为远距离网络的延迟,而不是仅仅为了处理数据中心的网络故障。

  

很多系统既不是可线性化的也不是 CAP 可用的

在 CAP 对可用性还有一致性严格的定义下,系统们表现怎么样?

  

拿任意一个 single master 的有备份的数据库作为一个例子。这也是标准的数据库设置。在这种情况下,如果用户不能访问 leader,就不能写到数据库。虽然他还能从 follower 那里读到数据,但是他不能写任何数据就说明它不是 CAP 可用的。更不要说这种设置还常常声称自己是“高可用的(high availablity)”。

  

如果以上这种设置不是 CAP 可用的,那是不是就是说他满足 CP(一致)?等一下。如果你是从 follower 那里读到的数据,因为备份是异步的,所以你可能读到旧的数据。所以你的读操作不是可线性化的,所以不满足 CAP 中的一致性。

  

而且支持 snapshot isolation/MVCC 的数据库是故意做成不可线性化的。否则会降低数据库的并发性。比如 PostgreSQL 的 SSI 提供的是可串行化而不是可线性化,Oracle 两者都不支持。仅仅因为数据库标榜自己是 ACID 并不意味着它就满足 CAP 中的一致性。

  

所以这些系统既不是 CAP 一致的,也不是 CAP 可用的。他们既不是 CP 也不是 AP,他们只是 P,不管这是什么意思。(是的,“三选二”也允许你只从三个中选一个,甚至一个都不选!)

  

那 NoSQL 怎么样的?拿 MongoDB 作为一个例子:每一个 shard 都只有一个 leader(至少只要他不在 split-brain 的模式下,它应该是这样的),根据以上的论证,那就说明他不是 CAP 可用的。而且 Kyle 最近发现,设置了最强的一致性,他还是允许非一致性的读操作,所以它也不是 CAP 一致的。

  

那像 Riak, Cassandra 还有 Voldemort 这些声称是 AP 的高可用的 Dynamo 的继承者们又怎么样呢?这取决于你的设置。如果你接受读写只访问一个拷贝(R=W=1),那么这确实是 CAP 可用的。但是如果你要求 quorum 读写(R+W>N),而且你有网络分区,那么那些被分在少部分节点的用户就不能达到 quorum,所以 quorum 操作不是 CAP 可用的(至少暂时是不可用的,直到你在少部分的分区内加入了更多的节点)。

  

你有时候会看到人们声称 quorum 读写可以保证可线性化,但是我觉得依赖这样的声明是不明智的。因为在一些复杂的情况下,read repair 操作和 sloppy quorum 同时发生,就有可能会重写已经被删除了的数据。或者当备份数(replicas)已经低于原来的 W 值(违反了 quorum 的条件),或者当备份数被加到了高于原来的 N 值(还是违反了 quorum 的条件),这些都可以导致不可线性化的访问结果。

  

这些都不是差的系统:他们在实际运用中都很成功。但是目前为止,我们还是不能严格把他们分类为 AP 或者 CP,要么是因为取决于具体的设定,或者是因为这个系统一致性和可用性都不满足。

  

案例分析:ZooKeeper

那 ZooKeeper 又怎么样呢?他用了 consensus 算法,所以人们一般认为他是很清楚的选择了一致性而放弃了可用性(也就是CP 系统)。

  

但是如果你阅读 ZooKeeper 的文档,他们很清楚的说了 ZooKeeper 的默认设置不提供可线性化的读操作。每一个连接到一个服务器的客户端,当你要读的时候,即使别的节点有更新的数据,你只能看到那个服务器本地的数据。这样读操作就比需要收集 quorum 或者访问 leader 要更快。但这也说明 ZooKeeper 默认不满足 CAP 的一致性定义。

  

做可线性化的读操作在 ZooKeeper 中是支持的。你需要在读操作之前发一个 sync 命令。但这不是默认的设置,因为这样读操作会更慢。人们有时候会用 sync 命令,但一般不会是所有的读操作都用。

  

那 ZooKeeper 的可用性呢?他要求达到大多数 quorum,来达到共识,才能处理一个写操作。如果你有网络分区,一边有大多数节点,一边有少部分节点。那么拥有大多数节点的分区还可以继续工作,但是少部分节点的分区就算节点们都正常工作着,还是不能处理写操作。所以 ZooKeeper 的写操作在网络分区的情况下,不满足 CAP 的可用性(即使拥有大多数节点的分区还是可以处理写操作的)。

  

更有意思的是,ZooKeeper 3.4.0 还加入了一个只读的模式。在这个模式下,少部分节点的分区还可以继续处理读操作 -- 不需要 quorum! 这个读操作是满足 CAP 可用性的。所以 ZooKeeper 默认设置既不是一致的(CP)也不是可用的(AP),只是"P"。但是你有选择通过用 sync 命令来让它成为 CP。并且在正确的设置下,读操作(不包括写)其实是 CAP 可用的。

  

这让人不是很舒服。如果就因为 ZooKeeper 的默认设置不是可线性化的就称他为不一致,那就歪曲了他的功能。他其实可以提供非常强的一致性!他支持 atomic broadcast(这个可以约化为共识问题)以及每个 session 的 causal consistency -- 这比 read your writes, monotonic reads 还有 consistent prefix reads 在一起都要强。他的文档上说 ZooKeeper 提供可串行化的一致性,但这其实是过于谦虚了,因为他其实可以提供更强的一致性。

  

根据 ZooKeeper 的例子,你就会发现就算这系统在网络分区的时候既不是 CP 也不是 AP(甚至在默认设置下,就算没有网络分区,也不是可线性化的),但他还是很合理的。(我猜 ZK 在 Abadi 的 PACELC 的框架下是 PC/EL,但我不觉得这比 CAP 更有启发性。)

  

CP/AP:一个伪二分法

事实上我们都没有成功地把一个数据库无歧义地分类为 AP 或者 CP。这应该告诉我们 CP/AP 根本就不是合适的用来描述系统的标签。

  

我相信我们应该不要再把数据库归类为 AP 或者 CP 了,因为

  

在同一个软件内,你可能有多个一致性属性的选择很多系统在 CAP 的定义下,既不是一致也不可用。然而我从来没有听到别人称这些系统为"P",可能是因为这样不太好看。但这并不差,他很可能是完全合理的设计,他只是不在 CP/AP 这两个分类中。虽然大部分软件都不在 CP/AP 这两类中,但人们还是强行把软件分为这两类。这就导致了,为了适用,不可避免地改变对“一致性”或者“可用性”的定义。不幸的是,如果用词的定义改变了,CAP 定理自己也不适用了,那 CP/AP 区分也就完全没有意义了。把系统分为这两类,导致了很多细节被忽略。在考虑分布式系统设计的时候,会有很多关于容错,延迟,简单模型,运行成本,等等的考虑。把那么多细节编码到一个比特的信息,显然是不可能的。比如说虽然 ZooKeeper 有一个 AP 的只读模式,但这个模式也提供对所有写操作的 total ordering。这比 Riak 或者 Cassandra 这些 AP 系统提供的保障要强得多。所以简单地把他们都归为 AP 一个类别就显得很不合理。甚至 Eric Brewer 承认 CAP 是一个容易误导人的而且过于简化的模型。在 2000 年,CAP 的意义在于让大家开始讨论关于分布式系统的取舍。他在这方面做得很好。但是他不是用来作为一个正式的突破性的结果,也不是一个严格的数据系统的分类方式。15 年之后,我们已经有了多得多的有不一样一致性和容错模型的系统。CAP 已经完成了他自己的使命,现在是时候不要在纠结了。

学会独立思考

如果 CP 和 AP 用来描述和评论系统是不合适的,那么我们应该用什么呢?我不认为有一个唯一的答案。很多人花了很多心思考虑这些问题,也提出了术语和模型来帮助我们理解这些问题。想要学习这些思想,你就需要更深入自己阅读文献。

  

一个很好的起点就是 Doug Terry 的论文。其中他用棒球来解释了各种不一样的最终一致性。可读性很强,而且就算对像我这样不是美国人而且完全不懂棒球也解释的很清晰。如果你对 transaction 的 isolation 模型有兴趣(这和分布式系统的一致性不一样,但是相关),我的小项目 Hermitage 你可以看一下。这篇论文讨论了分布式系统的一致性和 transaction 的 isolation 以及可用性之间的关系。(这篇论文也描述了不同一致性之间的分级。Kyle Kingsbury 很喜欢给别人讲这个。)

  

  


  

当你读到过这些了以后,你应该已经准备好深入阅读论文。我在这篇文章中加入了很多对文献的引用。去看一下,很多专家已经帮你把很多问题都已经解决了。作为最后的手段,如果你不想读论文原文,我建议你看一下我的书。这本书用通俗易懂的方式总结了大多数重要的思想。(你看,我已经竟可能的让这篇文章看上去不是用来推销我的书的。)如果你想学跟多关于怎么正确使用 ZooKeeper,Flavio Junqueira 还有 Benjamin Reed 的书是非常不错的。

  

不管你选择哪一种学习方式,我都鼓励你保持好奇心和耐心,因为这不是容易的学科。但是这是有回报的,因为你学会如果考虑取舍,进而搞清楚什么样的架构对于你的应用是最合适的。但是不管你做什么,请不要再说 CP 还有 AP 了,因为根本不合理。

  

谢谢 Kyle Kingsbury 还有 Camille Fournier 对于这篇文章初稿的评论。当然,所有的错误还有不受欢迎的观点都是我本人的。

  

来源:https://blog.the-pans.com/cap/

相关文章