分布式系统的CAP理论

309 阅读8分钟

CAP是什么

一个分布式系统最多只能同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance)这三项中的两项

C:数据一致性

一致性指“all nodes see the same data at the same time”,即所有节点在同一时间的数据完全一致。

一致性是因为多个数据拷贝下并发读写才有的问题,因此理解时一定要注意结合考虑多个数据拷贝下并发读写的场景。

对于一致性,可以分为强/弱/最终一致性三类

从客户端角度,多进程并发访问时,更新过的数据在不同进程如何获取的不同策略,决定了不同的一致性。

  • 强一致性

对于关系型数据库,要求更新过的数据能被后续的访问都能看到,这是强一致性。

  • 弱一致性

如果能容忍后续的部分或者全部访问不到,则是弱一致性。

  • 最终一致性

如果经过一段时间后要求能访问到更新后的数据,则是最终一致性。

假设,我们的分布式存储系统有两个节点,每个节点都包含了一部分需要被变化的数据。如果经过一次写请求后,两个节点都发生了数据变化。然后,读请求把这些变化后的数据都读取到了,我们就把这次数据修改称为数据发生了一致性变化。

但是,这还不是完整的一致性。因为系统不可能永久的正常运行下去。

如果系统内部发生了问题从而导致系统的节点无法发生一致性变化会怎么样呢?当我们这样做的时候,就意味着想看到最新数据的读请求们,很可能会看到旧数据,或者说获取到不同版本的数据。此时,为了保证分布式系统对外的数据一致性,于是选择不返回任何数据。

A:可用性

可用性指“Reads and writes always succeed”,即服务在正常响应时间内一直可用。

好的可用性主要是指系统能够很好的为用户服务,不出现用户操作失败或者访问超时等用户体验不好的情况。可用性通常情况下可用性和分布式数据冗余,负载均衡等有着很大的关联。

可用性在 CAP 里就是对结果的要求。它要求系统内的节点们接收到了无论是写请求还是读请求,都要能处理并给回响应结果。只是它有两点必须满足的条件:

条件 1:返回结果必须在合理的时间以内,这个合理的时间是根据业务来定的。业务说必须 100 毫秒内返回,合理的时间就是 100 毫秒,需要 1 秒内返回,那就是 1 秒,如果业务定的 100 毫秒,结果却在 1 秒才返回,那么这个系统就不满足可用性。

条件 2:需要系统内能正常接收请求的所有节点都返回结果。这包含了两重含义:

  1. 如果节点不能正常接收请求了,比如宕机了,系统崩溃了,而其他节点依然能正常接收请求,那么,我们说系统依然是可用的,也就是说,部分宕机没事儿,不影响可用性指标。
  2. 如果节点能正常接收请求,但是发现节点内部数据有问题,那么也必须返回结果,哪怕返回的结果是有问题的。比如,系统有两个节点,其中有一个节点数据是三天前的,另一个节点是两分钟前的,如果,一个读请求跑到了包含了三天前数据的那个节点上,抱歉,这个节点不能拒绝,必须返回这个三天前的数据,即使它可能不太合理。

P:分区容忍性

分区容错性指“the system continues to operate despite arbitrary message loss or failure of part of the system”,即分布式系统在遇到某节点或网络分区故障的时候,仍然能够对外提供满足一致性或可用性的服务。

分布式的存储系统会有很多的节点,这些节点都是通过网络进行通信。而网络是不可靠的,当节点和节点之间的通信出现了问题,此时,就称当前的分布式存储系统出现了分区。但是,值得一提的是,分区并不一定是由网络故障引起的,也可能是因为机器故障。

比如,我们的分布式存储系统有 A、B 两个节点。那么,当 A、B 之间由于可能路由器、交换机等底层网络设备出现了故障,A 和 B 通信出现了问题,但是 A、B 依然都在运行,都在对外提供服务。这时候,就说 A 和 B 发生了分区。

还有一种情况也会发生分区,当 A 出现了宕机,A 和 B 节点之间通信也是出现了问题,那么我们也称 A 和 B 发生了分区。

综上,我们可以知道,只要在分布式系统中,节点通信出现了问题,那么就出现了分区。

那么,分区容忍性是指什么? 它是说,如果出现了分区问题,我们的分布式存储系统还需要继续运行。不能因为出现了分区问题,整个分布式节点全部就熄火了,罢工了,不做事情了。

CAP权衡

大部分人解释这一定律时,常常简单的表述为:“一致性、可用性、分区容忍性三者你只能同时达到其中两个,不可能同时达到”。实际上这是一个非常具有误导性质的说法,而且在 CAP 理论诞生 12 年之后,CAP 之父也在 2012 年重写了之前的论文。

当发生网络分区的时候,如果我们要继续服务,那么强一致性和可用性只能 2 选 1。也就是说当网络分区之后 P 是前提,决定了 P 之后才有 C 和 A 的选择。也就是说分区容错性(Partition tolerance)我们是必须要实现的。

简而言之就是:CAP 理论中分区容错性 P 是一定要满足的,在此基础上,只能满足可用性 A 或者一致性 C。

作为一个分布式系统,它和单机系统的最大区别,就在于网络,现在假设一种极端情况,N1和N2之间的网络断开了,我们要支持这种网络异常,相当于要满足分区容错性,能不能同时满足一致性和响应性呢?还是说要对他们进行取舍。

假设在N1和N2之间网络断开的时候,有用户向N1发送数据更新请求,那N1中的数据V0将被更新为V1,由于网络是断开的,所以分布式系统同步操作M,所以N2中的数据依旧是V0;这个时候,有用户向N2发送数据读取请求,由于数据还没有进行同步,应用程序没办法立即给用户返回最新的数据V1,怎么办呢?

有二种选择,第一,牺牲数据一致性,响应旧的数据V0给用户;第二,牺牲可用性,阻塞等待,直到网络连接恢复,数据更新操作M完成之后,再给用户响应最新的数据V1。

这个过程,证明了要满足分区容错性的分布式系统,只能在一致性和可用性两者中,选择其中一个。

CAP 怎么选择

分布式系统理论上不可能选择 CA 架构,只能选择 CP 或者 AP 架构。 比如 ZooKeeper、HBase 就是 CP 架构,Cassandra、Eureka 就是 AP 架构,Nacos 不仅支持 CP 架构也支持 AP 架构。

为啥不可能选择 CA 架构呢? 举个例子:若系统出现“分区”,系统中的某个节点在进行写操作。为了保证 C, 必须要禁止其他节点的读写操作,这就和 A 发生冲突了。如果为了保证 A,其他节点的读写操作正常的话,那就和 C 发生冲突了。

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

对于涉及到钱财这样不能有一丝让步的场景,C必须保证。网络发生故障宁可停止服务,这是保证CA,舍弃P。貌似这几年国内银行业发生了不下10起事故,但影响面不大,报道也不多,广大群众知道的少。还有一种是保证CP,舍弃A。例如网络故障是只读不写。

另外,需要补充说明的一点是: 如果网络分区正常的话(系统在绝大部分时候所处的状态),也就说不需要保证 P 的时候,C 和 A 能够同时保证。