背景

CAP理论 实际上听起来非常简单,但是有时候,遇到一些具体的问题的时候, 还是不能很清晰的分辨出来,到底是CP还是AP,以及一些其他的问题。因此,专门作为"生产者"来学习下,加深理解。

首先,在理论计算机科学中,CAP定理(CAP theorem),又被称作布鲁尔定理(Brewer's theorem),它指出对于一个分布式计算系统来说,不可能同时满足以下三点:

1.一致性(Consistency) (等同于所有节点访问同一份最新的数据副本) 2.可用性(Availability)(每次请求都能获取到非错的响应——但是不保证获取的数据为最新数据) 3.分区容错性(Partition tolerance)(以实际效果而言,分区相当于对通信的时限要求。系统如果不能在时限内达成数据一致性,就意味着发生了分区的情况,必须就当前操作在C和A之间做出选择。)

根据定理,分布式系统只能满足三项中的两项而不可能满足全部三项。

这个定理起源于加州大学柏克莱分校(University of California, Berkeley)的计算机科学家埃里克·布鲁尔在2000年的分布式计算原理研讨会(PODC)上提出的一个猜想。 在2002年,麻省理工学院(MIT)的赛斯·吉尔伯特和南希·林奇发表了布鲁尔猜想的证明,使之成为一个定理。

举例

假设你明天就要放长假了,你想买一本战争与和平的书籍,你最喜欢的在线商城里面只有一本了。

一致性:

Consistency,在Gilbert and Lynch 的论文里,他们也用 “Atomic” 原子性来代替一致性这个单词。 在买书的这个例子里,你要么就是把书放到了购物车,要么就是放失败了,要么付款,要么没付款,不可能说放了一半,或者说买了一半。只有一本书,如果两个客户都准备买,缺乏一致性的话,如果两个人都完成了下单,可能会出问题。比如两个人都下了单,当然,在这个例子中,并不严重。

我们也可以用数据库来解决这个问题,数据库里有个字段减去个1,然后当及其他客户也要付款的时候,我们提示他没了。

数据库看很好用。因为具有ACID的能力。既有一致性,又有原子性,中间状态对第二个客户端是不可见的。是隔离的。因为第二个客户端再下单的时候,另一个用户在事务中的话,就锁住了数据库那条记录。

可用性:

可用性就是说,当你需要的时候,大部分情况下,服务都是可以为你服务的。以买书为例,在用户A开启事务的时候,有那么几毫秒是锁表的,这个阶段,服务可以认为对其他用户是不可用的。并不是说要时时刻刻可用,一般会有个可用率的指标。如果记不住,可以通过这里来计算: https://uptime.is/99.99999

分区容错:

如果你就一个数据库,一个服务端,那一般也都是原子的,如果挂了,服务不可用,但是数据还是一致的。

一旦你把数据和代码逻辑,开始部署在不同的节点上,这时候就存在分区。如Node A 不能和Node B通信来,这种分区问题经常出现。

用图来证明:

基础问题

在一个网络环境下,有两个节点,N1和N2,共享相同的数据V,在买书这个例子中,这个数据里面存储的就是有多少本书,假设初始值是V0,在N1上运行一个买卖算法,A,假设这个算法没有bug,非常正确,可心来,N2也是类似的,叫做B,A写了一个新值到V中,然后B从V中读取。

正常流程

正常流程是这样,A写完之后,N1和N2通过一个消息(非具体的消息),将这个值同步给N2。然后B也就能读到了。

1.A写了一个值V1 2.从N1发了个消息M到N2。 3.B也能从V中读到V1了

但是,现实没有这么美好

分区

网络发生了分区,从N1到N2的消息没有投递成功。这样,到第三步的时候,N2读到了V0这个错误的值。

如果M是一个异步消息,那么N1都没办法知道N2是不是收到了。即使有办法保证M这个消息一定发出去了。那么N1也没办法知道,这个消息是不是被投递了,也不知道N2处理的时候,有没有问题。那么,如果我们把M改成同步消息呢。也不行,因为这意味着将A写值到N1,和从N1到N2更新事件是一个原子操作。

CAP告诉我们,如果我们想要A和B高度可用(低延迟),我们就要N1和N2保持分区容错,比如出现消息丢失,消息未投递,硬件故障,或者处理失败。这种情况下,就会出现有时候一些节点任务V是V0,另一些节点认为是V1.

事务

如果有一个事务,叫做a1,a1可能是一个写操作,a2是一个读操作,在本地系统中,通过数据库或者自己加锁,加隔离是很简单的。可以强制a1写完之后,a2才发生,但是在分布式环境中,一旦加了这些东西,就影响额分区容错和可用性。

处理CAP

  1. CA 不要 P,不要分区容错

不保证分区容错,那么你可以部署在一个台机器上,但是容量受限。并且还是会存在网络问题。分布式环境下,网络分区是必然的。除非你就不想做分布式。

在分布式的环境下,网络无法做到100%可靠,有可能出现故障,因此分区是一个必须的选项,如果选择了CA而放弃了P,若发生分区现象,为了保证C,系统需要禁止写入,此时就与A发生冲突,如果是为了保证A,则会出现正常的分区可以写入数据,有故障的分区不能写入数据,则与C就冲突了。因此分布式系统理论上不可能选择CA架构,而必须选择CP或AP架构。

从Google的经验中可以得到的结论是,无法通过降低CA来提升P。要想提升系统的分区容错性,需要通过提升基础设施的稳定性来保障。

所以,对于一个分布式系统来说。P是一个基本要求,CAP三者中,只能在CA两者之间做权衡,并且要想尽办法提升P。

  1. CP 不要 A 不要可用性

当你想要分区容错的时候,并且可以容忍长时间的停机或者无影响。就可以舍弃可用性。

一个保证了CP而一个舍弃了A的分布式系统,一旦发生网络故障或者消息丢失等情况,就要牺牲用户的体验,等待所有数据全部一致了之后再让用户访问系统。

设计成CP的系统其实也不少,其中最典型的就是很多分布式数据库,他们都是设计成CP的。在发生极端情况时,优先保证数据的强一致性,代价就是舍弃系统的可用性。如Redis、HBase等,

常用的Zookeeper也是在CAP三者之中选择优先保证CP的。ZooKeeper是个CP 的,即任何时刻对ZooKeeper的访问请求能得到一致的数据结果,同时系统对网络分区具备容错性。但是它不能保证每次服务请求的可用性,也就是在极端环境下,ZooKeeper可能会丢弃一些请求,消费者程序需要重新请求才能获得结果。

ZooKeeper 是分布式协调服务,它的职责是保证数据在其管辖下的所有服务之间保持同步、一致。所以就不难理解为什么 ZooKeeper 被设计成CP而不是AP特性的了。从实际情况来分析,在使用 Zookeeper 获取服务列表时,如果 ZooKeeper 正在选举或者 ZooKeeper 集群中半数以上的机器不可用,那么将无法获取数据。所以说,ZooKeeper 不能保证服务可用性。

Eureka 则是一个AP系统,一部分节点挂掉不会影响到正常节点的工作,不会出现类似 ZK 的选举 Leader 的过程,客户端发现向某个节点注册或连接失败,会自动切换到其他的节点。 只要有一台 Eureka 存在,就可以保证整个服务处在可用状态,只不过有可能这个服务上的信息并不是最新的信息。

SofaRegistry 也是一个AP系统。

  1. AP 不要 C 不要一致性

要高可用并允许分区,则需放弃一致性。一旦网络问题发生,节点之间可能会失去联系。为了保证高可用,需要在用户访问时可以马上得到返回,则每个节点只能用本地数据提供服务,而这样会导致全局数据的不一致性。

这种舍弃强一致性而保证系统的分区容错性和可用性的场景和案例非常多。前面我们介绍可用性的时候说到过,很多系统在可用性方面会做很多事情来保证系统的全年可用性可以达到N个9,所以,对于很多业务系统来说,比如淘宝的购物,12306的买票。都是在可用性和一致性之间舍弃了一致性而选择可用性。

举个例子,你在12306买票的时候肯定遇到过这种场景,你购买的时候提示你是有票的(但是可能实际已经没票了),你也正常下单了。但是过了一会系统提示你下单失败,余票不足。这其实就是先在可用性方面保证系统可以正常的服务,然后在数据的一致性方面做了些牺牲,会影响一些用户体验,但是也不至于造成用户流程的严重阻塞。

但是,我们说很多网站牺牲了一致性,选择了可用性,这其实也不准确的。就比如上面的买票的例子,其实舍弃的只是强一致性。退而求其次保证了最终一致性。也就是说,虽然下单的瞬间,关于车票的库存可能存在数据不一致的情况,但是过了一段时间,还是要保证最终一致性的。也就是说,最终不会出现,2个人买到了同样的票。

对于多数大型互联网应用的场景,主机众多、部署分散,而且现在的集群规模越来越大,所以节点故障、网络故障是常态,而且要保证服务可用性达到N个9,即保证P和A,舍弃C(退而求其次保证最终一致性)。虽然某些地方会影响客户体验,但没达到造成用户流程的严重程度。

怎么选择呢

既要又要。那怎么办?

虽然三个不能保证,但我们能不能在一致性上作出一些妥协,不追求时时刻刻的强一致性,转而追求最终一致性,所以引入 BASE 理论。 在分布式事务中,BASE 最重要是为 CAP 提出了最终一致性的解决方案,BASE 强调牺牲高一致性,从而获取可用性,数据允许在一段时间内不一致,只要保证最终一致性就可以了,实现最终一致性。

弱一致性:系统不能保证后续访问返回更新的值。需要在一些条件满足之后,更新的值才能返回。从更新操作开始,到系统保证任何观察者总是看到更新的值的这期间被称为不一致窗口。

最终一致性:这是弱一致性的特殊形式;存储系统保证如果没有对某个对象的新更新操作,最终所有的访问将返回这个对象的最后更新的值。

BASE 模型

BASE 模型是传统 ACID 模型的反面,不同于 ACID,BASE 强调牺牲高一致性,从而获得可用性,数据允许在一段时间内的不一致,只要保证最终一致就可以了。 BASE 模型反 ACID 模型,完全不同 ACID 模型,牺牲高一致性,获得可用性或可靠性:Basically Available 基本可用。 支持分区失败(e.g. sharding碎片划分数据库)Soft state 软状态,状态可以有一段时间不同步,异步。 Eventually consistent 最终一致,最终数据是一致的就可以了,而不是时时一致。

参考

http://www.julianbrowne.com/article/brewers-cap-theorem

https://www.cnblogs.com/13yan/p/9243669.html