分布式系统之 — CAP 定理

前言

在互联网时代,我们的应用都是分布式系统,部署在 N 台机器上。说到分布式系统我们就不得不说分布式系统的祖先——集中式系统。它和分布式系统是两个完全相反的概念,集中式系统就是把所有的程序和功能都放到一台主机上,从而对外提供服务。集中式系统的优点就是容易理解、维护方便,它的的弊端也很明显,如果这个主机出故障了那么整个系统就崩溃了。著名投资家巴菲特有个关于投资的名言:

不要把鸡蛋放在一个篮子里

对于我们的系统而言也是如此,我们不可能保证主机永远不坏、也无法保证自己的程序永远不会出 bug,所以问题是无法避免的,我们只能把“鸡蛋”分散到不同的“篮子”里,降低系统出故障的风险,这就是我们为什么需要分布式系统的原因之一。使用分布式系统的另一个理由就是扩展性,毕竟单台主机都会有性能的极限,分布式系统可以通过增加主机数量来实现横向水平性能的扩展。接下来我们看看分布式系统中的一个基本定理——CAP定理

什么是 CAP 定理

CAP 定理指出对于一个分布式系统来说,不可能同时满足以下三点:

  • 一致性(Consistency)
  • 可用性(Availability)
  • 分区容错性(Partition tolerance)

定理看起来很简单,但是一致性可用性分区容错性究竟是代表什么意思呢?理解定理的最简单的方式就是想象一个有两个节点分别处在不同的分区(PS:可以简单的把分区理解为不同的子网络)的分布式系统。

场景假设

我们假定一个很简单的分布式系统,系统由两个系统 S1S2 组成。两个系统上面有两个相同的变量 K,该变量在两个系统对应的初始值为 V0。系统 S1S2 可以进行通信同时也对外提供服务。我们假定的分布式系统如下所示:

cap-distributed-system.png

客户端 client 可以向 S1S2 任何一个系统发起读和写请求。当一个服务接收到发过来的请求后进行一些相关业务操作,然后返回给客户端 client,发起写请求的过程如下图所示:

cap-client-send-write-request.png

客户端发起读请求的过程如下所示:

cap-client-send-read-request.png

我们的分布式系统模型建立好了,接下来我们通过这个模型来分析CAP 定理中的一致性、可用性和分区容错性的具体含义。

一致性(Consistency)

一致性要求在一个写操作完成之后的任何读操作都必须返回该值或者以后进行写操作的结果。在满足一致性的分布式系统中,客户端发起一个写请求到分布式系统的任何一个子系统中,然后再向该系统中任何一个子系统发起读请求查询该变量对应的值,都会返回上次更新的最新结果。客户端向一个不满足一致性的分布式系统发起写-读请求的过程如下所示:

cap-inconsistent-system.png

当客户端向系统 S1 发送写请求(write V1),得到成功返回响应后,再向系统 S2 发送读请求读取该变量的值,系统 S2 还是返回旧值 V0。另一方面,我们看看客户端向一个满足一致性的分布式系统发起写-读请求的过程:

cap-consistent-system.png

在这个满足一致性的系统中,在上述过程中系统 S1 在返回客户端请求结果之前会先把最新值 V1 发送到系统 S2,然后才返回客户端的写请求结果。因此,当客户端再去请求系统 S2 的时候就会返回最新值 V1

可用性(Availability)

可用性要求在分布式系统中非故障节点收到的每一个请求都必须返回响应。在一个满足可用性的分布式系统中,如果客户端向系统中任意一个节点发送请求并且服务器没有崩溃的情况下,则该节点必须响应客户端,不管是哪个节点,只要收到请求,就必须告诉用户,到底是 V0 还是 V1,否则就不满足可用性,不允许服务器忽略客户端的请求。

分区容错性(Partition tolerance)

分区容错表明当消息从一个节点向另一个节点发送消息的过程中,消息可能会丢失。以实际效果而言,分区相当于对通信的时限要求。系统如果不能在时限内达成数据一致性,就意味着发生了分区的情况,必须就当前操作在一致性可用性之间做出选择。如果所有消息都无法发送的话,分布式系统的各个节点将无法同步消息。如下所示:

cap-partition-tolerance.png

一般来说,分区容错无法避免,因此可以认为CAP定理分区容错性总是成立。CAP 定理告诉我们,剩下的一致性可用性无法同时做到。通常我们为了分区容错,我们的系统必须保证能够在任意网络分区下正常运行。

为何不能同时满足一致性和可用性

我们现在知道了一致性可用性分区容错性所表示的具体含义,接下来看看为什么在一个分布式系统中不能同时满足一致性可用性。我们假定存在一个同时满足这三个特性的系统。首先要做的就是对该系统进行分区,分区后系统如下所示:

cap-partition.png

下一步,客户端向分布式系统的节点 S1 发送一个写请求(write V1),系统只要是可用的,该节点总是会返回响应。但是系统存在网络分区,因此节点 S1 无法将最新值 V1 通知节点 S2 去更新。如下所示:

cap-eight.png

接下来,客户端向分布式系统的节点 S2 发送一个读请求(read K)查询变量 K 的值,同样的,系统只要是可用的,该节点总是会返回响应,但是系统存在网络分区,因此节点 S2 无法从节点 S1 获取到最新值 V1 进行更新。如下所示:

cap-nine.png

客户端已经向节点 S1 发送写请求(write V1)成功后,再向节点 S2 发起读请求,得到的返回值是旧值 V0。这和我们假设的一致性冲突。如果要保证节点 S1 的一致性,那么节点 S1 必须在写操作时,锁定节点 S2 的读操作和写操作。只有当数据同步后,才能重新开放节点 S2 的读写操作。那么在锁定期间,S2不能读写,它就没有可用性了。再来看看,我们如果保证节点 S2 的可用性,那么就不能锁定节点 S2 的读写操作,所以一致性不成立。所以,节点 S2 无法同时做到一致性和可用性。系统设计时只能选择一个目标。如果追求一致性,那么无法保证所有节点的可用性,如果追求所有节点的可用性,那就没法做到一致性了。

总结

CAP定理指明了分布式系统的三大指标一致性(Consistency)可用性(Availability)分区容错性(Partition tolerance)不能同时满足,该定理是分布式系统的基本定理,也是理解分布式系统的起点。(PS: 像我们常用的注册中心 Eureka,因为节点之间的状态同步采用的异步方式,所以不能保证任意时刻各个节点间的状态一定是一致的,只能保证节点间最终状态是一致的。所以按照CAP理论Eureka 的选择就是放弃了一致性,选择可用性分区容错性。)

-------------本文结束感谢您的阅读-------------
mghio wechat
微信公众号「mghio」
请我吃🍗