CAP 理论和 BASE 理论

楔子

很多人可能都有这样的感觉,每次要开发分布式系统的时候,就会遇到一个非常棘手的问题,那就是如何根据业务特点,为系统设计合适的分区容错一致性模型,以实现集群能力。这个问题棘手在当发生分区错误时,应该如何保障系统稳定运行,不影响业务。

这个时候我们需要使用 CAP 理论,你可能会问了:为什么 CAP 理论可以解决这个问题呢?因为在我看来,CAP 理论是一个很好的思考框架,它对分布式系统的特性做了高度抽象, 比如抽象成了一致性、可用性和分区容错性,并对特性间的冲突(也就是 CAP 不可能三角)做了总结。一旦掌握它,你就像拥有了引路人,自然而然就能根据业务场景的特点进行权衡,设计出适合的分区容错一致性模型。

那么问题来了:这里说的的一致性、可用性和分区容错性是什么呢?它们之间有什么关系?我们又该如何使用 CAP 理论来思考和设计分区容错一致性模型呢?

CAP 理论

CAP 三指标

我刚刚提到,CAP 理论对分布式系统的特性做了高度抽象,形成了三个指标:

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

一致性

一致性说的是客户端的每次读操作,不管访问哪个节点,要么读到的都是同一份最新的数据,要么读取失败。

你可以把一致性看作是分布式系统对访问本系统的客户端的一种承诺:不管你访问哪个节点,要么我给你返回的都是绝对一致的数据,要么你都读取失败。可以看到,一致性强调的不是数据完整,而是各节点间的数据一致。

为了更好地理解一致性这个指标,我们举一个具体的例子。比如两个节点的 KV 存储,原始的 KV 记录为 X = 1。

紧接着,客户端向节点 1 发送写请求 SET X = 2。

如果节点 1 收到写请求后,只将节点 1 的 X 值更新为 2,然后返回成功给客户端,这个时候节点 2 的 X 值还是 1,那么两个节点是非一致性的。

如果节点 1 收到写请求后,通过节点间的通讯,同时将节点 1 和节点 2 的 X 值都更新为 2,然后返回成功给客户端,那么在完成写请求后,两个节点的数据就是一致的了,之后,不管客户端访问哪个节点,读取到的都是同一份最新数据。

一致性这个指标,描述的是分布式系统非常重要的一个特性,强调的是数据的一致。也就是说,在客户端看来,集群和单机在数据一致性上是一样的。

不过集群毕竟不是单机,当发生分区故障的时候,有时不能仅仅因为节点间出现了通讯问题,节点中的数据会不一致,就拒绝写入新数据,之后在客户端查询数据时,就一直返回给客户端出错信息。这句话怎么理解呢?举个例子。

业务集群中的一些关键系统,比如名字路由系统,如果仅仅因为发生了分区故障、导致节点中的数据会不一致,集群就拒绝写入新的路由信息,那么之后当客户端查询相关路由信息时,系统就一直返回给客户端信息不存在,那么相关的服务都将因为获取不到指定路由信息而不可用、 瘫痪,这可以说是灾难性的故障了。

这个时候,我们就需要牺牲数据的一致性,每个节点使用本地数据来响应客户端请求,来保证服务可用,这就是我们要说的另外一个指标,可用性。

可用性

可用性说的是任何来自客户端的请求,不管访问哪个节点,都能得到响应数据,但不保证是同一份最新数据。你也可以把可用性看作是分布式系统对访问本系统的客户端的另外一种承诺:我尽力给你返回数据,不会不响应你,但是我不保证每个节点给你的数据都是最新的。 这个指标强调的是服务可用,但不保证数据的一致。

比如客户端可以选择向节点 1 或节点 2 发起读操作,如果不管节点间的数据是否一致,只要节点服务器收到请求就响应 X 的值,那么两个节点之间是满足可用性的。

分区容错性

最后的分区容错性说的是,当节点间出现任意数量的消息丢失或高延迟的时候,系统仍然可以继续提供服务。也就是说,分布式系统在告诉客户端:不管我的内部出现什么样的数据同步问题,我会一直运行,提供服务。这个指标,强调的是集群对分区故障的容错能力。

比如当节点 1 和节点 2 通信出问题的时候,如果系统仍能提供服务,那么,两个节点是满足分区容错性的。

因为分布式系统与单机系统不同,它涉及到多节点间的通讯和交互,节点间的分区故障是必然发生的,所以在分布式系统中分区容错性是必须要考虑的。

了解完一致性、可用性和分区容错性,那么在设计分布式系统时,是选择一致性?还是可用性?还是分区容错性?还是都可以选择呢?这三个特性有什么冲突么?这些问题就与下面要说的「CAP 不可能三角」有关了。

CAP 不可能三角

CAP 不可能三角说的是对于一个分布式系统而言,一致性(Consistency)、可用性 (Availability)、分区容错性(Partition Tolerance)3 个指标不可兼得,只能在 3 个指标中选择两个。

我们都知道,只要有网络交互就一定会有延迟和数据丢失,而这种状况我们必须接受,还必须保证系统不能挂掉。所以就像上面提到的,节点间的分区故障是必然发生的。也就是说,分区容错性(P)是前提,是必须要保证的,不能说某些节点之间无法正常通信(发生网络分区)就导致整个集群不可用。

现在就只剩下一致性(C)和可用性(A)可以选择了:要么选择一致性,保证数据绝对一 致;要么选择可用性,保证服务可用。那么 CP 和 AP 的含义是什么呢?

  • 当选择了一致性(C)的时候,如果因为消息丢失、延迟过高发生了网络分区,部分节点无法保证特定信息是最新的,那么这个时候,当集群节点接收到来自客户端的写请求时,因为无法保证所有节点都是最新信息,所以系统将返回写失败错误,也就是说集群拒绝新数据写入。
  • 当选择了可用性(A)的时候,系统将始终处理客户端的查询,返回特定信息,如果发生了网络分区,一些节点将无法返回最新的特定信息,它们将返回自己当前的相对新的信息。

这里需要强调一点,大部分人对 CAP 理论有个误解,认为无论在什么情况下,分布式系统都只能在 C 和 A 中选择 1 个。其实,在不存在网络分区的情况下,也就是分布式系统正常运行时(这也是系统在绝大部分时候所处的状态),就是说在不需要 P 时,C 和 A 能够同时保证。只有当发生分区故障的时候,也就是说需要 P 时,才会在 C 和 A 之间做出选择。

小结

以上就是 CAP 理论的具体内容,以及 CAP 理论的应用,重点如下:

  • CA 模型:不支持分区容错,只支持一致性和可用性。这在分布式系统中不存在,因为不支持分区容错性,也就意味着不允许分区异常,设备、网络永远处于理想的可用状态,从而让整个分布式系统满足一致性和可用性。但由于分布式系统是由众多节点通过网络通信连接构建的,设备故障、网络异常是客观存在的,而且分布的节点越多,范围越广,出现故障和异常的概率也越大。因此对于分布式系统而言,分区容错性(P)是无法避免的,如果避免了 P,只能把分布式系统回退到单机单实例系统,就比如单机版关系型数据库 MySQL,如果 MySQL 要考虑主备或集群部署时,那么它也必须考虑 P。
  • CP 模型:因为分区容错客观存在,即相当于放弃系统的可用性,换取一致性。采用 CP 模型的分布式系统,一旦因为消息丢失、延迟过高发生了网络分区,就会持续阻塞整个服务,直到分区问题解决,才恢复对外服务,这样可以保证数据的一致性。选择 CP 一般都是对数据一致性特别敏感,尤其是在支付交易领域,Hbase 等分布式数据库领域,都要优先保证数据的一致性,在出现网络异常时,系统就会暂停服务处理。还有用来分发及订阅元数据的 Zookeeper、Etcd 等等,也是优先保证 CP 的。
  • AP 模型:由于分区容错 P 客观存在,即相当于放弃系统数据的一致性,换取可用性。在系统遇到分区异常时,某些节点之间无法通信,数据处于不一致的状态,为了保证可用性,服务节点在收到用户请求后立即响应,那只能返回各自新老不同的数据。这种舍弃一致性,而保证系统在分区异常下的可用性,在互联网系统中非常常见。比如微博多地部署,如果不同区域的网络中断,区域内的用户仍然发微博、相互评论和点赞,但暂时无法看到其他区域用户发布的新微博和互动状态。还有类似 12306 这种火车购票系统,在节假日高峰期抢票时,偶尔也会遇到,反复看到某车次有余票,但每次真正点击购买时,却提示说没有余票。所以相比 CP,采用 AP 模型的分布式系统,更注重服务的高可用。用户访问系统的时候,都能得到响应数据,不会出现响应错误,但当出现分区故障时,相同的读操作,访问不同的节点,得到响应数据可能不一样。典型应用就比如 Cassandra、DynamoDB、Redis 等 NoSQL。

因此 CAP 理论就像 PH 试纸一样,可以用来度量分布式系统的酸碱值,帮助我们思考如何设计合适的酸碱度,在一致性和可用性之间进行妥协折中,设计出满足场景特点的分布式系统。最后再提一点,在当前分布式系统开发中,延迟是非常重要的一个指标。比如在 QQ 后台的名字路由系统中,我们通过延迟评估服务可用性,进行负载均衡和容灾;再比如,在 Hashicorp/Raft 实现中,通过延迟评估领导者节点的服务可用性,以及决定是否发起领导者选举;再比如类似 Redis 这种查询量非常大的分布式缓存,它的目的是能够快速地返回结果,所以它是 AP 模型。所以,希望大家在分布式系统的开发中,也能意识到延迟的重要性,能通过延迟来衡量服务的可用性。

所以能否容忍短暂的延迟是关键。

BASE 理论

很多人可能喜欢使用事务型的分布式系统,或者是强一致性的分布式系统,因为使用起来很方便,不需要考虑太多,就像使用单机系统一样。但是学了 CAP 理论后,你肯定知道在分布式系统中要实现强一致性必然会影响可用性。比如在采用两阶段提交协议的集群系统中,因为执行提交操作,需要所有节点确认和投票。

所以,集群的可用性是每个节点可用性的乘积,比如 3 个节点的集群,每个节点的可用性为 99.9%,那么整个集群的可用性为 99.7%,也就是说,每个月约宕机 129.6 分钟,这是非常严重的问题。而解决可用性低的关键在于,根据实际场景,尽量采用可用性优先的 AP 模型。

讲到这儿,可能会有一些小伙伴感到困惑:这也太难了,难道没有现成的库或者方案,来实现合适的 AP 模型?是的,的确没有。因为它是一个动态模型,是基于业务场景特点妥协折中后设计实现的。不过,你可以借助 BASE 理论帮助你达成目的。

BASE 理论是 CAP 理论中的 AP 的延伸,是对互联网大规模分布式系统的实践总结,强调可用性。几乎所有的互联网后台分布式系统都有 BASE 的支持,这个理论很重要,地位也很高。一旦掌握它,你就能掌握绝大部分场景的分布式系统的架构技巧,设计出适合业务场景特点的、高可用性的分布式系统。而它的核心就是基本可用(Basically Available)和最终一致性(Eventually consistent),也有人会提到软状态(Soft state),在我看来,软状态描述的是实现服务可用性的时候系统数据的一种过渡状态,也就是说不同节点间,数据副本存在短暂的不一致。只需要知道软状态是一种过渡状态就可以了,这里不多说。

那么基本可用以及最终一致性到底是什么呢?我们又如何在实践中使用 BASE 理论提升系统的可用性呢?下面就来聊一聊。

实现基本可用的四板斧

首先「基本可用」指的是,当分布式系统在出现不可预知的故障时,允许损失部分功能的可用性,保障核心功能的可用性。就像弹簧一样,遇到外界的压迫,它不是折断,而是变形伸缩,不断适应外力,实现基本的可用。

具体说的话,你可以把基本可用理解成,当系统节点出现大规模故障的时候,比如专线的光纤被挖断、突发流量导致系统过载(出现了突发事件,服务被大量访问),这个时候可以通过服务降级,牺牲部分功能的可用性,保障系统的核心功能可用。就拿 12306 订票系统基本可用的设计为例,这个订票系统在春运期间,因为开始售票后先到先得的缘故,会出现极其海量的请求峰值,如何处理这个问题呢?

首先可以在不同的时间,出售不同区域的票,将访问请求错开,削弱请求峰值。比如在春运期间,深圳出发的火车票在 8 点开售,北京出发的火车票在 9 点开售。这就是我们常说的流量削峰。

另外,你可能已经发现了,在春运期间,自己提交的购票请求,往往会在队列中排队等待处理,可能几分钟或十几分钟后,系统才开始处理,然后响应处理结果,这就是我们熟悉的延迟响应。 你看,12306 订票系统在出现超出系统处理能力的突发流量的情况下,会通过牺牲响应时间的可用性,保障核心功能的运行。所以 12306 通过流量削峰和延迟响应,是不是就实现了基本的可用呢?现在它不会再像最初的时候那样,常常 404 了吧?

再比如,你正负责一个互联网系统,突然出现了网络热点事件,好多用户涌进来,产生了海量的突发流量,系统过载了,大量图片因为网络超时无法显示。那么这个时候你可以通过哪些方法,保障系统的基本可用呢?

  • 体验降级: 比如用小图片来替代原始图片,通过降低图片的清晰度和大小,提升系统的处理能力。
  • 过载保护:比如把接收到的请求放在指定的队列中排队处理,如果请求等待时间超时了(假设是 100ms),这个时候直接拒绝超时请求;再比如队列满了之后,就清除队列中一定数量的排队请求,保护系统不过载,实现系统的基本可用。

你看,和 12306 的设计类似,只不过你负责的互联网系统是通过牺牲部分功能的可用性,保障核心功能的运行。

说了这么多,主要是想强调:基本可用在本质上是一种妥协,也就是在出现节点故障或系统过载的时候,通过牺牲非核心功能的可用性,保障核心功能的稳定运行。希望你能在后续的分布式系统的开发中,不仅掌握流量削峰、延迟响应、体验降级、过载保护这 4 板斧,更能理解这 4 板斧背后的妥协折中,从而灵活地处理不可预知的突发问题。

了解完基本可用之后,我们再来说说 BASE 理论中另一个非常核心的内容:最终一致性。

最终一致

最终一致性是指,系统中所有的数据副本在经过一段时间的同步后,最终能够达到一个一致的状态。也就是说在数据一致性上,存在一个短暂的延迟。几乎所有的互联网系统采用的都是最终一致性,只有在实在无法使用最终一致性,才使用强一致性或事务,比如:对于决定系统运行的敏感元数据,需要考虑采用强一致性,对于与钱有关的支付系统或金融系统的数据,需要考虑采用事务。

我们可以将强一致性理解为最终一致性的特例,也就是说可以把强一致性看作是不存在延迟的一致性。在实践中我们也可以这样思考:如果业务的某功能无法容忍一致性的延迟(比如分布式锁对应的数据),需要实现的是强一致性;如果能容忍短暂的一致性的延迟(比如 QQ 状态数据),就可以考虑最终一致性。

那么如何实现最终一致性呢?你首先要知道它以什么为准,因为这是实现最终一致性的关键。一般来说,在实际工程实践中有这样几种方式:

  • 以最新写入的数据为准,比如 AP 模型的 KV 存储采用的就是这种方式;
  • 以第一次写入的数据为准,如果你不希望存储的数据被更改,可以以它为准;

那实现最终一致性的具体方式是什么呢?常用的有这样几种。

  • 读时修复:在读取数据时,检测数据的不一致,进行修复。比如 Cassandra 的 Read Repair 实现,具体来说,在向 Cassandra 系统查询数据的时候,如果检测到不同节点的副本数据不一致,系统就自动修复数据;
  • 写时修复:在写入数据,检测数据的不一致时,进行修复。比如 Cassandra 的 Hinted Handoff 实现。具体来说,Cassandra 集群的节点之间远程写数据的时候,如果写失败就将数据缓存下来,然后定时重传,修复数据的不一致性;
  • 异步修复:这个是最常用的方式,通过定时检测副本数据的一致性,并修复;

在这里,我想强调的是因为写时修复不需要做数据一致性对比,性能消耗比较低,对系统运行影响也不大,所以推荐在实现最终一致性时优先实现这种方式。而读时修复和异步修复因为需要做数据的一致性对比,性能消耗比较多,在开发实际系统时,要尽量优化一致性对比的算法,降低性能消耗,避免对系统运行造成影响。并且在实现最终一致性的时候,推荐同时实现自定义写一致性级别(ALL、Quorum、One、Any), 让用户可以自主选择相应的一致性级别,比如可以通过设置一致性级别为 All,来实现强一致性。

小结

BASE 理论是对 CAP 中一致性和可用性权衡的结果,它来源于对大规模互联网分布式系统实践的总结,是基于 CAP 定理逐步演化而来的。它的核心思想是,如果不是必须的话,不推荐实现事务或强一致性,鼓励可用性和性能优先,根据业务的场景特点,来实现非常弹性的基本可用,以及实现数据的最终一致性。

BASE 理论主张通过牺牲部分功能的可用性,实现整体的基本可用,也就是说,通过服务降级的方式,努力保障极端情况下的系统可用性。

ACID 理论是传统数据库常用的设计理念,追求强一致性模型。BASE 理论支持的是大型分布式系统,通过牺牲强一致性获得高可用性。BASE 理论在很大程度上,解决了事务型系统在性能、容错、可用性等方面痛点。另外再多说一句,BASE 理论在 NoSQL 中应用广泛,是 NoSQL 系统设计的事实上的理论支撑。

对于任何集群而言,不可预知的故障的最终后果,都是系统过载。如何设计过载保护,实现系统在过载时的基本可用,是开发和运营互联网后台的分布式系统的重中之重。因此在开发实现分布式系统,要充分考虑如何实现基本可用。

posted @ 2020-03-28 14:34  古明地盆  阅读(1475)  评论(0编辑  收藏  举报