零、问题
1.cap理论关注的粒度是什么?
2.memcache集群符合cap理论吗?mysql呢?kafka呢?
一、简单介绍
2000年作为一个猜想被加州大学伯克利分校的计算机科学家埃里克·布鲁尔提出。
2002 年被麻省理工学院的赛斯·吉尔伯特(Seth Gilbert)和南希·林奇(Nancy Lynch)证明,使之成为分布式计算领域公认的一个定理。
布鲁尔在提出 CAP 猜想的时候,并没有详细定义 Consistency、Availability、Partition Tolerance 三个单词的明确定义,因此如果我们去查询 CAP 定义的时候会感到比较困惑,因为不同的资料对 CAP 的详细定义有一些细微的差别。
比如:

二、CAP理论
参考Robert Greiner(robertgreiner.com/about/)

Robert Greiner 对 CAP 的理解也经历了一个过程,他写了两篇文章来阐述 CAP 理论,第一篇被标记为“outdated”(有一些中文翻译文章正好参考了第一篇),我们将对比前后两篇解释的差异点来更加深入地理解 CAP 理论。
概念
第一版解释:
对于一个分布式计算系统,不可能同时满足一致性(Consistence)、可用性(Availability)、分区容错性(Partition Tolerance)三个设计约束。
第二版解释:
在一个分布式系统(指互相连接并共享数据的节点的集合)中,当涉及读写操作时,只能保证一致性(Consistence)、可用性(Availability)、分区容错性(Partition Tolerance)三者中的两个,另外一个必须被牺牲。
对比:
有几个很关键的差异点:第二版定义了什么才是 CAP 理论探讨的分布式系统,强调了两点:互联 和 共享数据。
分布式系统并不一定会互联和共享数据。最简单的例如 Memcache 的集群,相互之间就没有连接和共享数据,因此 Memcache 集群这类分布式系统就不符合 CAP 理论探讨的对象;而 MySQL 集群就是互联和进行数据复制的,因此是 CAP 理论探讨的对象。第二版强调了 “读写操作”,这点其实是和上一个差异点一脉相承的。也就是说,CAP 关注的是对数据的读写操作,而不是分布式系统的所有功能。例如,ZooKeeper 的选举机制就不是 CAP 探讨的对象。相比来说,第二版的定义更加精确。
一致性
第一版解释:所有节点在同一时刻都能看到相同的数据。
第二版解释:对某个指定的客户端来说,读操作保证能够返回最新的写操作结果
对比:
1)第一版从节点 node 的角度描述,第二版从客户端 client 的角度描述。相比来说,第二版更加符合我们观察和评估系统的方式,即站在客户端的角度来观察系统的行为和特征。
2)第一版强调同一时刻拥有相同数据(same time + same data),第二版并没有强调这点。why?事务!虽然事务中各node节点的数据不一致,但对client来说读到的数据是一致的。
可用性
第一版解释:每个请求都能得到成功或者失败的响应。
第二版解释:非故障的节点在合理的时间内返回合理的响应(不是错误和超时的响应)。
对比:
第一版的定义明显是有问题的 1)请求得到了失败的响应还能算可用吗?2)即使是成功的响应也不一定是正确的,比如本来应该返回 100,但实际上返回了 90,这就是成功的响应,但并没有得到正确的结果。这个能算可用吗?(前段时间java换qconf,结果数据错了,一上线线下一堆人反馈问题)
相比之下,第二版的解释明确了1)节点不能故障(都故障了 还谈啥可用性) 2)响应不能超时、不能出错,但可以是合理的,注意没有说“正确”的结果。例如,应该返回 100 但实际上返回了 90,肯定是不正确的结果,但可以是一个合理的结果。(数据库同步延时)
分区容忍性
第一版解释:出现消息丢失或者分区错误时系统能够继续运行。
第二版解释:当出现网络分区后,系统能够继续“履行职责”。
对比:
1)第一版用的是 work,第二版用的是 function。work 强调“运行”,只要系统不宕机,我们都可以说系统在 work,返回错误也是 work,拒绝服务也是 work;而 function 强调“发挥作用”“履行职责”,这点和可用性是一脉相承的。也就是说,只有返回 合理的响应 才是 function。相比之下,第二版解释更加明确。
2)第一版描述分区用的是消息丢失或者分区错误,第二版直接用 网络分区,不管是什么原因,可能是丢包,也可能是连接中断,还可能是拥塞,只要导致了网络分区,就通通算在里面。
总结一下:CAP定理就是在一个分布式系统(指互相连接并共享数据的节点的集合)中,当涉及读写操作时,只能保证一致性(Consistence)、可用性(Availability)、分区容错性(Partition Tolerance)三者中的两个,另外一个必须被牺牲。其中,一致性是指对某个指定的客户端来说,读操作保证能够返回最新的写操作结果;可用性是指非故障的节点在合理的时间内返回合理的响应(不是错误和超时的响应);分区容忍性是指当出现网络分区后,系统能够继续“履行职责”。
三、CAP应用
虽然 CAP 理论定义是三个要素中只能取两个,但放到分布式环境下来思考,我们会发现必须选择 P(分区容忍),因为网络本身无法做到 100% 可靠,有可能出故障,所以分区是一个必然的现象。如果我们选择了 CA 而放弃了 P,那么当发生分区现象时,为了保证 C,系统需要禁止写入,当有写入请求时,系统返回 error(例如,当前系统不允许写入),这又和 A 冲突了,因为 A 要求返回 no error 和 no timeout。因此,分布式系统理论上不可能选择 CA 架构,只能选择 CP 或者 AP 架构。
CP
如下图所示,为了保证一致性,当发生分区现象后,N1 节点上的数据已经更新到 y,但由于 N1 和 N2 之间的复制通道中断,数据 y 无法同步到 N2,N2 节点上的数据还是 x。这时客户端 C 访问 N2 时,N2 需要返回 Error,提示客户端 C“系统现在发生了错误”,这种处理方式违背了可用性(Availability)的要求,因此 CAP 三者只能满足 CP。
AP
如下图所示,为了保证可用性,当发生分区现象后,N1 节点上的数据已经更新到 y,但由于 N1 和 N2 之间的复制通道中断,数据 y 无法同步到 N2,N2 节点上的数据还是 x。这时客户端 C 访问 N2 时,N2 将当前自己拥有的数据 x 返回给客户端 C 了,而实际上当前最新的数据已经是 y 了,这就不满足一致性(Consistency)的要求了,因此 CAP 三者只能满足 AP。注意:这里 N2 节点返回 x,虽然不是一个“正确”的结果,但是一个“合理”的结果,因为 x 是旧的数据,并不是一个错乱的值,只是不是最新的数据而已。
四、CAP关键细节点
CAP 关注的粒度是数据,而不是整个系统
这句话是理解和应用 CAP 理论非常关键的一点。CAP 理论的定义和解释中,用的都是 系统啊、节点啊 这类系统级的概念,这就给很多人造成了很大的误导,认为我们在进行架构设计时,整个系统要么选择 CP,要么选择 AP。但在实际设计过程中,每个系统不可能只处理一种数据,而是包含多种类型的数据,有的数据必须选择 CP,有的数据必须选择 AP。而如果我们做设计时,从整个系统的角度去选择 CP 还是 AP,就会发现顾此失彼,无论怎么做都是有问题的。
以一个简单的用户管理系统为例,用户管理系统包含用户账号数据(用户 ID、密码)、用户信息数据(昵称、兴趣、爱好、性别、自我介绍等)。通常情况下,用户账号数据会选择 CP,而用户信息数据会选择 AP,如果限定整个系统为 CP,则不符合用户信息数据的应用场景;如果限定整个系统为 AP,则又不符合用户账号数据的应用场景。所以在 CAP 理论落地实践时,我们需要将系统内的数据按照不同的应用场景和要求进行分类,每类数据选择不同的策略(CP 还是 AP),而不是直接限定整个系统所有数据都是同一策略。
CAP是忽略网络延迟的
这是一个非常隐含的假设,布鲁尔在定义一致性时,并没有将延迟考虑进去。也就是说,当事务提交时,数据能够瞬间复制到所有节点。但实际情况下,从节点 A 复制数据到节点 B,总是需要花费一定时间的。如果是相同机房,耗费时间可能是几毫秒;如果是跨地域的机房,例如北京机房同步到广州机房,耗费的时间就可能是几十毫秒。这就意味着,CAP 理论中的 C 在实践中是不可能完美实现的,在数据复制的过程中,节点 A 和节点 B 的数据并不一致。(回顾下一致性:一致性是指对某个指定的客户端来说,读操作保证能够返回最新的写操作结果;)
正常运行情况下,不存在 CP 和 AP 的选择,可以同时满足 CA。
CAP 理论告诉我们分布式系统只能选择 CP 或者 AP,但其实这里的前提是系统发生了“分区”现象。如果系统没有发生分区现象,也就是说 P 不存在的时候,我们没有必要放弃 C 或者 A,应该 C 和 A 都可以保证,这就要求架构设计的时候既要考虑分区发生时选择 CP 还是 AP,也要考虑分区没有发生时如何保证 CA。同样以用户管理系统为例,即使是实现 CA,不同的数据实现方式也可能不一样。比如:我们系统中bike_last_order中的order_end_time通过kafka实现CA。更多的地方是使用数据库的主从同步实现CA。还有利用接口实现CA的。比如用户支付成功后回调我们的举报接口告诉我们用户支付成功。
放弃并不等于什么都不做,需要为分区恢复后做准备。
CAP 理论告诉我们三者只能取两个,需要“牺牲”(sacrificed)另外一个,这里的“牺牲”是有一定误导作用的,因为“牺牲”让很多人理解成什么都不做。实际上,CAP 理论的“牺牲”只是说在分区过程中我们无法保证 C 或者 A,但并不意味着什么都不做。因为在系统整个运行周期中,大部分时间都是正常的,发生分区现象的时间并不长。
例如,99.99% 可用性(俗称 4 个 9)的系统,一年运行下来,不可用的时间只有 50 分钟;99.999%(俗称 5 个 9)可用性的系统,一年运行下来,不可用的时间只有 5 分钟。分区期间放弃 C 或者 A,并不意味着永远放弃 C 和 A,我们可以在分区期间进行一些操作,从而让分区故障解决后,系统能够重新达到 CA 的状态。最典型的就是在分区期间记录一些日志,当分区故障解决后,系统根据日志进行数据恢复,使得重新达到 CA 状态。我们前段时间迁表停服期间就采用了这种方式,当时针对部分内网接口比如用户支付成功后回调我们接口,我们采用了AP,这时候我们先记录了日志,最后在系统恢复的时候,我们通过日志恢复了C。
五、引申
ACID中的C是什么?
在事务开始之前和事务结束以后,数据库的完整性没有被破坏。
原子性和一致性都强调数据库从一个状态变更到另一个状态(或不变更)。但侧重点不同:原子性关注状态,要么全部成功,要么全部失败,不存在部分成功的状态。而一致性关注数据的可见性,中间状态的数据对外部不可见,只有最初状态和最终状态的数据对外可见
BASE理论
BASE 是指基本可用(Basically Available)、软状态( Soft State)、最终一致性( Eventual Consistency),核心思想是即使无法做到强一致性(CAP 的一致性就是强一致性),但应用可以采用适合的方式达到最终一致性。
-
基本可用(Basically Available)分布式系统在出现故障时,允许损失部分可用性,即保证核心可用。这里的关键词是“部分”和“核心”,具体选择哪些作为可以损失的业务,哪些是必须保证的业务,是一项有挑战的工作。例如,对于一个用户管理系统来说,“登录”是核心功能,而“注册”可以算作非核心功能。因为未注册的用户本来就还没有使用系统的业务,注册不了最多就是流失一部分用户,而且这部分用户数量较少。如果用户已经注册但无法登录,那就意味用户无法使用系统。例如,充了钱的游戏不能玩了、云存储不能用了……这些会对用户造成较大损失,而且登录用户数量远远大于新注册用户,影响范围更大。
-
软状态指的是:允许系统中的数据存在中间状态,并认为该状态不影响系统的整体可用性,即允许系统在多个不同节点的数据副本存在数据延时。(上述表迁移案例中在还未利用日志实现最终一致性之前其实是处于一种软状态)
-
最终一致性(Eventual Consistency)系统中的所有数据副本经过一定时间后,最终能够达到一致的状态。
这里的关键词是“一定时间” 和 “最终”,“一定时间”和数据的特性是强关联的,不同的数据能够容忍的不一致时间是不同的。比如账号数据的同步时延要求较短,微博发帖数据延时可以适当放松。
BASE 理论本质上是对 CAP 的延伸和补充,更具体地说,是对 CAP 中 AP 方案的一个补充。就是在C不能保证的时候,我们可以通过一定的方式先做个记录,待系统恢复后通过一定的手段最终实现C。
KAFKA发消息时如何谈CAP?
Kafka producer有三种ack机制
0
意味着producer不等待broker同步完成的确认,继续发送下一条(批)信息
提供了最低的延迟。但是最弱的持久性,当服务器发生故障时,就很可能发生数据丢失。如果此时发生P,则满足AP,牺牲了C。
1
意味着producer要等待leader成功收到数据并得到确认,才发送下一条message。此选项提供了较好的持久性较低的延迟性。
Partition的Leader死亡,follwer尚未复制,数据就会丢失。如果此时发生P,则满足AP,牺牲了C。
-1
意味着producer得到follwer确认,才发送下一条数据。如果此时发生P,则满足CP,牺牲了A。