You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

165 lines
11 KiB
Markdown

2 years ago
# 09 | CAP定理三选二架构师必须学会的取舍
你好,我是蔡元楠。
今天我要与你分享的主题是CAP定理。
在分布式系统的两讲中,我们一起学习到了两个重要的概念:可用性和一致性。
而今天,我想和你讲解一个与这两个概念相关,并且在设计分布式系统架构时都会讨论到的一个定理——**CAP定理**CAP Theorem
## CAP定理
CAP这个概念最初是由埃里克·布鲁尔博士Dr. Eric Brewer在2000年的ACM年度学术研讨会上提出的。
如果你对这次演讲感兴趣的话,可以翻阅他那次名为“[Towards Robust Distributed Systems](https://people.eecs.berkeley.edu/~brewer/cs262b-2004/PODC-keynote.pdf)”的演讲deck。
在两年之后塞思·吉尔伯特Seth Gilbert和麻省理工学院的南希·林奇教授Nancy Ann Lynch在他们的论文“Brewers conjecture and the Feasibility of Consistent, Available, Partition-Tolerant Web Services”中证明了这一概念。
![](https://static001.geekbang.org/resource/image/2b/8f/2bfd96a97ce8d38834105964d0cb0e8f.png)
他们在这篇论文中证明了在任意的分布式系统中一致性Consistency可用性Availability和分区容错性Partition-tolerance这三种属性最多只能同时存在两个属性。
下面,我来为你解读一下这三种属性在这篇论文里的具体意思。
## C属性一致性
一致性在这里指的是**线性一致性Linearizability Consistency**。在线性一致性的保证下所有分布式环境下的操作都像是在单机上完成的一样也就是说图中Sever A、B、C的状态一直是一致的。
![](https://static001.geekbang.org/resource/image/17/41/17a4df9ff551c932bf60ca459fe3b641.jpg)
打个比方现在有两个操作Operation操作A和操作B都需要在同一个分布式系统上完成。
我们假设操作A作用在系统上的时候所看见的所有系统状态State叫作状态A。而操作B作用在系统上的时候所看见的所有系统状态叫作状态B。
如果操作A是在操作B之前发生的并且操作A成功了。那么系统状态B必须要比系统状态A更加新。
可能光看理论的话你还是会觉得这个概念有点模糊,那下面我就以一个具体例子来说明吧。
假设我们设计了一个分布式的购物系统在这个系统中商品的存货状态分别保存在服务器A和服务器B中。我们把存货状态定义为“有货状态”或者“无货状态”。在最开始的时候服务器A和服务器B都会显示商品为有货状态。
![](https://static001.geekbang.org/resource/image/ed/38/ed38011a91fdd19021b6450415f5a738.jpg)
等一段时间过后,商品卖完了,后台就必须将这两台服务器上的商品状态更新为无货状态。
因为是在分布式的环境下商品状态的更新在服务器A上完成了显示为无货状态。而服务器B的状态因为网络延迟的原因更新还未完成还是显示着有货状态。
这时恰好有两个用户使用着这个购物系统先后发送了一个查询操作Query Operation到后台服务器中查询商品状态。
我们假设是用户A先查询的这个查询操作A被发送到了服务器A上面并且成功返回了商品是无货状态的。用户B在随后也对同一商品进行查询而这个查询操作B被发送到了服务器B上面并且成功返回了商品是有货状态的。
![](https://static001.geekbang.org/resource/image/c3/08/c3421ee2650ba291bbf630448d3f5f08.jpg)
我们知道对于整个系统来说商品的系统状态应该为无货状态。而操作A又是在操作B之前发送并且成功完成的所以如果这个系统有线性一致性这个属性的话操作B所看到的系统状态理论上应该是无货状态。
但在我们这个例子中操作B却返回了有货状态。所以我们说这个分布式的购物系统并不满足论文里所讲到的线性一致性。
聊完了一致性,我们一起来看看可用性的含义。
## A属性可用性
可用性的概念比较简单,在这里指的是**在分布式系统中,任意非故障的服务器都必须对客户的请求产生响应**。
当系统满足可用性的时候,不管出现什么状况(除非所有的服务器全部崩溃),都能返回消息。
![](https://static001.geekbang.org/resource/image/a2/80/a22d6b3032f045565b952076f5f1ce80.jpg)
也就是说,当客户端向系统发送请求,只要系统背后的服务器有一台还未崩溃,那么这个未崩溃的服务器必须最终响应客户端。
## P属性分区容错性
在了解了可用性之后,你还需要了解分区容错性。它分为两个部分,“分区”和“容错”。
在一个分布式系统里,如果出现一些故障,可能会使得部分节点之间无法连通。由于这些故障节点无法联通,造成整个网络就会被分成几块区域,从而使数据分散在这些无法连通的区域中的情况,你可以认为这就是发生了分区错误。
![](https://static001.geekbang.org/resource/image/59/d4/59b52bb37de477286a70c355fe0fe1d4.jpg)
如图所示如果你要的数据只在Sever A中保存当系统出现分区错误在不能直接连接Sever A时你是无法获取数据的。我们要“分区容错”意思是即使出现这样的“错误”系统也需要能“容忍”。也就是说就算错误出现系统也必须能够返回消息。
分区容错性,在这里指的是我们的**系统允许网络丢失从一个节点发送到另一个节点的任意多条消息**。
我们知道,在现代网络通信中,节点出现故障或者网络出现丢包这样的情况是时常会发生的。
如果没有了分区容错性,也就是说系统不允许这些节点间的通讯出现任何错误的话,那我们日常所用到的很多系统就不能再继续工作了。
所以在**大部分情况下系统设计都会保留P属性而在C和A中二选一**。
论文中论证了在任意系统中我们最多可以保留CAP属性中的两种也就是CP或者AP或者CA。关于具体的论证过程如果你感兴趣的话可以自行翻阅论文查看。
你可能会问在我们平常所用到的开发架构中有哪些系统是属于CP系统有哪些是AP系统又有哪些是CA系统呢我来给你介绍一下
* CP系统Google BigTable, Hbase, MongoDB, Redis, MemCacheDB这些存储架构都是放弃了高可用性High Availablity而选择CP属性的。
* AP系统Amazon Dynamo系统以及它的衍生存储系统Apache Cassandra和Voldemort都是属于AP系统
* CA系统Apache Kafka是一个比较典型的CA系统。
我在上面说过P属性在现代网络时代中基本上是属于一个必选项那为什么Apache Kafka会放弃P选择CA属性呢我来给你解释一下它的架构思想。
## 放弃了P属性的Kafka Replication
在Kafka发布了0.8版本之后Kafka系统引入了Replication的概念。Kafka Relocation通过将数据复制到不同的节点上从而增强了数据在系统中的持久性Durability和可用性Availability。在Kafka Replication的系统设计中所有的数据日志存储是设计在同一个数据中心Data Center里面的也就是说在同一个数据中心里网络分区出现的可能性是十分之小的。
它的具体架构是这样的在Kafka数据副本Data Replication的设计中先通过Zookeeper选举出一个领导者节点Leader。这个领导者节点负责维护一组被称作同步数据副本In-sync-replica的节点所有的数据写入都必须在这个领导者节点中记录。
我来举个例子假设现在数据中心有三台服务器一台被选为作为领导者节点另外两台服务器用来保存数据副本分别是Replication1和Replication2它们两个节点就是被领导者节点维护的同步数据副本了。领导者节点知道它维护着两个同步数据副本。
如果用户想写入一个数据假设是“Geekbang”
1. 用户会发请求到领导者节点中想写入“Geekbang”。
2. 领导者节点收到请求后先在本地保存好然后也同时发消息通知Replication1和Replication2。
3. Replication1和Replication2收到消息后也保存好这条消息并且回复领导者节点写入成功。
4. 领导者节点记录副本1和副本2都是健康Healthy并且回复用户写入成功。
红色的部分是领导者节点本地日志,记录着有哪些同步数据副本是健康的。
![](https://static001.geekbang.org/resource/image/d7/ca/d731b39103542c83c98bbe57aca1ecca.jpg)
往后用户如果想查询写入的数据,无论是领导者节点还是两个副本都可以返回正确同步的结果。
那假如分区出现了该怎么办呢例如领导者节点和副本1无法通讯了这个时候流程就变成这样了。
1. 用户会发请求到领导者节点中想写入“Geekbang”。
2. 领导者节点收到请求后先在本地保存好然后也同时发消息通知Replication1和Replication2。
3. 只有Replication2收到消息后也保存好这条消息并且回复领导者节点写入成功。
4. 领导者节点记录副本2是健康的并且回复用户写入成功。
同样,红色的部分是领导者节点本地日志,记录着有哪些同步数据副本是健康的。
![](https://static001.geekbang.org/resource/image/9d/f6/9d97ad203e7869019a84363c847b3cf6.jpg)
如果所有副本都无法通讯的时候Apache Kafka允许系统只有一个节点工作也就是领导者节点。这个时候所有的写入都只保存在领导者节点了。过程如下
1. 用户会发请求到领导者节点中想写入“Geekbang”。
2. 领导者节点收到请求后先在本地保存好然后也同时发消息通知Replication1和Replication2。
3. 没有任何副本回复领导者节点写入成功,领导者节点记录无副本是健康的,并且回复用户写入成功。
![](https://static001.geekbang.org/resource/image/7a/5c/7a0b62273c39bbf0dbda1ca3513f595c.jpg)
当然在最坏的情况下连领导者节点也挂了Zookeeper会重新去寻找健康的服务器节点来当选新的领导者节点。
## 小结
通过今天的学习我们知道在CAP定理中一致性可用性和分区容错性这三个属性最多只能选择两种属性保留。CAP定理在经过了差不多20年的讨论与演化之后大家对这三个属性可能会有着自己的一些定义。
例如在讨论一致性的时候有的系统宣称自己是拥有C属性也就拥有一致性的但是这个一致性并不是论文里所讨论到的线性一致性。
在我看来作为大规模数据处理的架构师我们应该熟知自己的系统到底应该保留CAP中的哪两项属性同时也需要熟知自己所应用到的平台架构是保留着哪两项属性。
## 思考题
如果让你重新设计微博系统中的发微博功能你会选择CAP的哪两个属性呢为什么呢
欢迎你把答案写在留言区,与我和其他同学一起讨论。
如果你觉得有所收获,也欢迎把文章分享给你的朋友。