对于分布式系统的设计,有一个理论是绕不开的,那就是CAP。关于这个理论,想必大家多少都听过,但是如果只是死记硬背,不深刻理解它的话,就会很容易忘记。那么本文重点讲讲CAP和BASE理论,和大家一起再次认识它们。
CAP代表什么含义?
- 一致性(Consistency)
对某个指定的客户端来说,读操作保证能够返回最新的写操作结果。
对于系统执行事务来说,在事务执行过程中,系统其实处于一个不一致的状态,不同的节点的数据并不完全一致,但是这里强调client读操作能够获取最新的写结果就没有问题,因为事务在执行过程中,client是无法读取到未提交的数据的,只有等到事务提交后,client才能读取到事务写入的数据,而如果事务失败则会进行回滚,client也不会读取到事务中间写入的数据。
- 可用性(Availability)
非故障的节点在合理的时间内返回合理的响应(不是错误和超时的响应)。
明确了返回的结果不能超时、不能出错,结果是合理的,注意没有说“正确”的结果。例如,应该返回100但实际上返回了90,肯定是不正确的结果,但可以是一个合理的结果。
我们平时会看到一些 IT 公司的对外宣传,比如系统稳定性已经做到 3 个 9、4 个 9,即 99.9%、99.99%,这里的 N 个 9 就是对可用性的一个描述,叫做 SLA,即服务水平协议。比如我们说月度 99.95% 的 SLA,则意味着每个月服务出现故障的时间只能占总时间的 0.05%,如果这个月是 30 天,那么就是 21.6 分钟。
- 分区容忍性(Partition Tolerance)
当出现网络分区后,系统能够继续“履行职责”。
网络分区现象,可能是丢包,也可能是连接中断,还可能是拥塞,甚至是网络不通等,这些都通通都囊括在网络分区的现象中,在这样的情况下,仍然能够对外提供满足一致性和可用性的服务。
大家都知道在分布式系统中,CAP三个要素不能同时满足,只能满足其中两个,为什么呢?
为什么CAP只能3选2?
在弄明白这个问题之前,我们先要明白一点就是永远可靠的通信在分布式系统中必定不成立的,这不是你想不想的问题,而是只要用到网络来共享数据,分区现象就会始终存在。所以我们必然需要保证P分区容忍这个要素。
现在我们通过反证法来证明 CAP 定理,通过一个实际场景,如果 CAP 三者可同时满足,由于允许 P 的存在,则一定存在 Server 之间的丢包,如下图所示:
因为允许分区容错,Write 操作可能在 Server 1 上成功,在 Server 2 上失败,这时候对于 Client 1 和 Client 2,就会读取到不一致的值,出现不一致的情况。如果要保持X 值的一致性,Write 操作必须同时失败,也就是降低系统的可用性。如果为了保证可用性,那么Client 2 读取到的X值等于0,这就相当于牺牲了一致性。可以看到,在分布式系统中,无法同时满足 CAP 定律中的“一致性”“可用性”和“分区容错性”三者。
AP还是CP?
CAP 理论提醒我们,在架构设计中,不要把精力浪费在如何设计能满足三者的完美分布式系统上,而要合理进行取舍, 网络分区是既成的现实,于是只能在可用性和一致性两者间做出选择。CAP 理论关注的是在绝对情况下,在工程上,可用性和一致性并不是完全对立的,我们关注的往往是如何在保持相对一致性的前提下,提高系统的可用性。 业务上对一致性的要求会直接反映在系统设计中,典型的就是 CP 和 AP 结构。
- CP 架构:对于 CP 来说,放弃可用性,追求一致性和分区容错性。
我们熟悉的 ZooKeeper,就是采用了 CP 一致性,ZooKeeper 是一个分布式的服务框架,主要用来解决分布式集群中应用系统的协调和一致性问题。其核心算法是 Zab,所有设计都是为了一致性。在 CAP 模型中,ZooKeeper 是 CP,这意味着面对网络分区时,为了保持一致性,它是不可用的。
- AP 架构:对于 AP 来说,放弃强一致性,追求分区容错性和可用性,这是很多分布式系统设计时的选择。
和 ZooKeeper 相对的是 Eureka,Eureka 是 Spring Cloud 微服务技术栈中的服务发现组件,Eureka 的各个节点都是平等的,几个节点挂掉不影响正常节点的工作,剩余的节点依然可以提供注册和查询服务,只要有一台 Eureka 还在,就能保证注册服务可用,只不过查到的信息可能不是最新的版本,不保证一致性。
CAP关键细节点
前面整理的介绍了CAP理论,但是还需要强调你可能忽略的几个重要细节。
- CAP关注的粒度是数据,而不是整个系统。
我们可能有一个误区,这个系统是CP还是AP,其实是不对的。实际上CAP关注的是数据,每个系统不可能只处理一种数据,而是包含多种类型的数据,有的数据必须选择CP,有的数据必须选择AP。所以在CAP理论落地实践时,我们需要将系统内的数据按照不同的应用场景和要求进行分类,每类数据选择不同的策略(CP还是AP),而不是直接限定整个系统所有数据都是同一策略。
- CAP是忽略网络延迟的。
在实际情况下,从节点A复制数据到节点B,总是需要花费一定时间的。如果是相同机房,耗费时间可能是几毫秒;如果是跨地域的机房,例如北京机房同步到广州机房,耗费的时间就可能是几十毫秒。这就意味着,CAP理论中的C在实践中是不可能完美实现的,在数据复制的过程中,节点A和节点B的数据并不一致。
这就知道我们对于某些严苛的业务场景,例如和金钱相关的用户余额,或者和抢购相关的商品库存,技术上是无法做到分布式场景下完美的一致性的。而业务上必须要求一致性,因此单个用户的余额、单个商品的库存,理论上要求选择CP而实际上CP都做不到,只能选择CA。也就是说,只能单点写入,其他节点做备份,无法做到分布式情况下多点写入。
- 正常运行情况下,不存在CP和AP的选择,可以同时满足CA。
CAP理论告诉我们分布式系统只能选择CP或者AP,但其实这里的前提是系统发生了“分区”现象。如果系统没有发生分区现象,也就是说P不存在的时候(节点间的网络连接一切正常),我们没有必要放弃C或者A,应该C和A都可以保证,这就要求架构设计的时候既要考虑分区发生时选择CP还是AP,也要考虑分区没有发生时如何保证CA。
- 放弃并不等于什么都不做,需要为分区恢复后做准备。
CAP理论告诉我们三者只能取两个,需要“牺牲”(sacrificed)另外一个,这里的“牺牲”是有一定误导作用的,因为“牺牲”让很多人理解成什么都不做。实际上,CAP理论的“牺牲”只是说在分区过程中我们无法保证C或者A,但并不意味着什么都不做。因为在系统整个运行周期中,大部分时间都是正常的,发生分区现象的时间并不长。例如,99.99%可用性(俗称4个9)的系统,一年运行下来,不可用的时间只有50分钟;99.999%(俗称5个9)可用性的系统,一年运行下来,不可用的时间只有5分钟。分区期间放弃C或者A,并不意味着永远放弃C和A,我们可以在分区期间进行一些操作,比如记录日志等,从而让分区故障解决后,系统能够重新达到CA的状态。
BASE理论又是什么?
大部分分布式系统在设计的过程中放弃一致性,追求分区容错性和可用性,那么是不是意味着不要一致性了?其实不然,BASE理论本质上是对CAP的延伸和补充,更具体地说,是对CAP中AP方案的一个补充,核心就是保证最终一致性。
BASE 理论的核心思想是最终一致性,即使无法做到强一致性(Strong Consistency),但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性(Eventual Consistency)。 接下来我们着重对 Base 理论中的三要素进行讲解。
- 基本可用(Basically Available)
分布式系统在出现故障时,允许损失部分可用性,即保证核心可用。
举个例子,在双十一秒杀活动中,如果抢购人数太多超过了系统的 QPS 峰值,可能会排队或者提示限流,这就是通过合理的手段保护系统的稳定性,保证主要的服务正常,保证基本可用。
- 软状态(Soft State)
软状态则是允许系统中的数据存在中间状态,并认为该状态不影响系统的整体可用性,即允许系统在多个不同节点的数据副本存在数据延时。
- 最终一致性(Eventual Consistency)
数据不可能一直是软状态,必须在一个时间期限之后达到各个节点的一致性,在期限过后,应当保证所有副本保持数据一致性,也就是达到数据的最终一致性。 在系统设计中,最终一致性实现的时间取决于网络延时、系统负载、不同的存储选型、不同数据复制方案设计等因素。
举一个微博系统的例子,用户账号数据最好能在1分钟内就达到一致状态,因为用户在A节点注册或者登录后,1分钟内不太可能立刻切换到另外一个节点,但10分钟后可能就重新登录到另外一个节点了;而用户发布的最新微博,可以容忍30分钟内达到一致状态,因为对于用户来说,看不到某个明星发布的最新微博,用户是无感知的,会认为明星没有发布微博。“最终”的含义就是不管多长时间,最终还是要达到一致性的状态。
总结
BASE 理论是在 CAP 上发展的,CAP 理论描述了分布式系统中数据一致性、可用性、分区容错性之间的制约关系,当你选择了其中的两个时,就不得不对剩下的一个做一定程度的牺牲。 BASE 理论则是对 CAP 理论的实际应用,也就是在分区和副本存在的前提下,通过一定的系统设计方案,放弃强一致性,实现基本可用,这是大部分分布式系统的选择,比如 NoSQL 系统、微服务架构。