请不要再宣称数据库是 CP 或者 AP

CAP 用的是非常精确的定义

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

  • 一致性(Consistency) 在CAP中是可线性化的意思(linearizability)。而这个是非常特殊(而且非常强)的一致性。尤其是虽然 ACID 中的 C 也是一致性(Consistency),但是和这里的一致性没有任何关系。我会在后面解释可线性化是什么意思。
  • 可用性(Availability)在 CAP 中是定义为"每一个请求(request)如果被一个工作中的[数据库]节点收到,那一定要返回[非错误]的结果"。注意到,这里一部分节点可以处理这个请求是不充分的。任意一个工作中的节点都要可以处理这个请求。所以很多自称"高度可用"的系统通常并没有满足这里的可用性的定义。
  • 分区容错(Partition Tolerance)基本上就是说通信是在异步的网络中。信息是可能延迟送达或者被丢失的。互联网还有我们所有的数据中心都有这个属性。所以我们在这件事上并没有选择。

还有就是注意到 CAP 并没有描述任意一个老的系统,而是一个非常特殊的系统:

  • CAP 系统的模型是一个只能读写单个数据的寄存器。这就是全部。CAP 没有提到任何关于关系到多个事物(object)的事务(transaction)。他们根本就不在这个定理的范围之内,除非你可以把这些问题约化到一个单个寄存器的问题。
  • CAP 定理只考虑了网络分区这一种故障情况(比如节点们还在运行,但是他们之间的网络已经不工作了)。这种故障绝对会发生,但是这不是唯一会出故障的地方。节点可以整个崩溃(crash)或者重启,你可能没有足够的磁盘空间,你可能会遇到一个软件故障(bug),等等。在建分布式系统的时候,你需要考虑到更多得多的问题。如果太关注 CAP 就容易导致忽略了其他重要的问题。
  • 还有 CAP 根本没有提到延迟(latency)。而常常人们其实对关心延迟比可用性更多。事实上,满足 CAP 可用性的系统可以花任意长的时间来回复一个请求,而且同时保持可用性这个属性。我来冒险说一句,我猜如果你的系统要花两分钟来加载一个页面,你的用户是不会称它是“可用的”。

如果你的用词是符合 CAP 证明中的精确定义的,那么它对你来说是适用的。但是如果你的一致性还有可用性是有其他意思的,那么你不能期待 CAP 对你还是适用的。当然,这并不意味着你通过重新定义一些词汇就可以做到一些不可能的事情!这只是说你不能靠 CAP 来给你提供指导方向,而且你不能通过 CAP 来为你的观点来辩解。

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

可线性化

如果你对可线性化不是很熟悉(也就是 CAP 中的一致性),那么让我来简短地解释一下。正式的定义不是特别直观,但是关键的思想用非正式的描述就是:

如果B操作在成功完成 A 操作之后,那么整个系统对 B 操作来说必须表现为 A 操作已经完成了或者更新的状态。

为了可以解释的更清楚一些,让我们来看一个例子。在这个例子中的系统并不是可线性化的。

看下面这个图(我还没有发行的书的预览):

这张图展示了 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 了,因为根本不合理。

原文地址:https://www.cnblogs.com/CQqf2019/p/10947945.html

时间: 2024-10-18 03:10:37

请不要再宣称数据库是 CP 或者 AP的相关文章

sqlserver数据库脱机时发生异常:由于无法在数据库 'SMS' 上放置锁,ALTER DATABASE 失败。请稍后再试。 ALTER DATABASE 语句失败。 (.Net SqlClient Data Provider)

sqlserver数据库脱机时发生异常,如下: =================================== 设置脱机 对于 数据库“SMS”失败.  (Microsoft.SqlServer.Smo) ------------------------------ 有关帮助信息,请单击: http://go.microsoft.com/fwlink?ProdName=Microsoft+SQL+Server&ProdVer=10.50.2500.0+((KJ_PCU_Main).110

EXCEL导入GridView,然后再汇入数据库.

原文:EXCEL导入GridView,然后再汇入数据库. 近日项目中有一个多笔料号要输入,我做了一个用javascript复制输入框的功能,可以输入多笔料号. 但是使用者反馈,料号太多,可能几百个料号在EXCEL文件里,这样输入很慢,需要增加功能.于是想到了SQLSERVER导入EXCEL的功能. declare @strExcelName varchar(50) set @strExcelName='c:\sl.xls' exec('select * into ##tmp from openr

机房收费系统合作——再看数据库设计

机房合作我负责了最简单的D层,接口层,工厂层.反正D层是我来写,于是数据库索性也就顺便设计了.已经是第三次敲机房收费系统了,每次都是相隔半年左右吧.需求搞得透透的了,数据库也就好设计了.基本跟第二次没什么大的区别,就是把Student表和Card表分开了. 重构的时候,我的数据库几乎什么都用到了:事务,存储过程,触发器,视图,联合查询等等.所以,这次设计数据库还是SO Easy的..并且,为了让婵婵和牛迁迁师哥写的方便,我把组合查询都写成了存储过程!!!!费了一番功夫,但是D层简单了不少.还记得

再看数据库——(2)视图

概念 *是从用户使用数据库的观点来说的. *从一个或多个表(视图)中导出来的 *一个虚表,或者说查询表 为什么要用视图呢? 一是简单,看到的就是需要的.视图不仅可以简化用户对数据的理解,也可以简化他们的操作.那些被经常使用的查询可以被定义为视图,从而使得用户不必为以后的操作每次指定全部的条件. 二是 安全,通过视图用户只能查询和修改他们所能见到的数据,但不能授权到数据库特定行和特定的列上.通过视图,用户可以被限制在数据的不同子集上:使用权限可被限制在另一视图的一个子集上,或是一些视图和基表合并后

微信公众号开发,微信手机端一直提示该公众号暂时无法提供服务,请稍候再试

遇到这个问题,一直在找原因,百度了很多,排除了防火墙,以及策略的问题,还有微信服务器屏蔽了IP. 之前的项目都是相同的代码,但是这次是另外一个项目,部署到客户那边,微信的消息管理,无法接收消息.(即微信服务器无法POST到配置好的URL上) 后面发现在客户的服务器上面,用域名和外网的IP无法访问,而在外网却可以用域名访问,而外网IP的也不能访问,当时一脸懵逼,好在经过公司的小伙伴一眼就看出了可能是代理服务器的问题.用cmd命令查看了下,发现是经过多层才能访问得到. 运行cmd,输入tracert

微信H5支付 提示"网络环境未能通过安全验证,请稍后再试"

按照官方https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=15_4# 接入微信的H5支付,提示"网络环境未能通过安全验证,请稍后再试",经排查发现服务器有前端机子,原先获取IP的方法有误,不能正确获取客户的IP.更改获取IP的方法,问题解决. 附获取IP的PHP函数: function ip() { if(getenv('HTTP_CLIENT_IP') && strcasecmp(getenv('HTTP_CL

再看数据库——(1)存储过程

当我们执行一个比较简单的操作时,一般都是用SQL语句.但是当逻辑越来越复杂时,仅仅使用顺序执行的SQL语句序列表现出很大的局限性.这时,就可以考虑用另一种方法了,那就是存储过程. 现在数据库系统往往采用客户机/服务器计算模式,很多工作可以在客户端完成,也可以在服务器端完成.客户机/服务器系统与传统的数据库系统的一个很重要的区别是,在传统的数据库系统中只存放数据,所有的应用程序都在用户端,都与用户实际运行的应用程序捆绑在一起:而客户机/服务器系统中,在数据库中还存放程序,由于这种程序以数据库对象的

微信发送消息后显示该公共号暂时无法提供服务,请稍后再试!

用户发送信息,公众账号会提示"该公众号暂时无法提供服务,请稍后再试".遇到这个问题,我们可以排除一下是否我们返回的数据有问题. 首先代码返回一个空的字符串,如果还是出现这样的提示,请看看签名验证是否失败,调试一下,看看log.     如果没有出现这样的提示,说明我们返回的结果格式有问题. 下面参考一下我返回的错误格式: <xml> <toUserName><![CDATA[o_ZKtuBGHyFi2YcpSKlugRAomGrU]]></to

微信支付出现支付请求参数错误,请核实再试或交易出错,请稍后再试的可能原因

微信支付经常会出现有些奇怪找不到原因的问题,下面就是其中的一个例子当你的微信支付出现 “支付请求参数错误,请核实再试”或 “交易出错,请稍后再试”的错误的时候,不访从下面几点找原因1,确保你的微信的app_id,app_secret,parent_id,parent_key,parent_sign_key的信息是否填写正确了.2,请检查package的参数是否错误或为空等情况,具体设置可参考开发文档.3,微信的金额是以分为单位的,所以请检查一下你传入的金额最小值是不是大于1(千万不要传小数点进去