这篇文章着重点不在于科普,毕竟关于CAP、BASE的理论的文章,网上很多。所以本文科普篇幅尽量小(只包含概念描述)。主要从几个侧面的问题来描述CAP,进而描述ACID、BASE理念。然后加入一点点调料,如何动态的切换一致性强度。

本文通过以下几个问题,从侧面描述。文中个人观点较多,看官理性对待。

  • 为什么CAP三者不可兼得?
  • 为什么总把ACID与CP、BASE与AP放在一起,它们有什么关联?
  • eureka属于AP系统吗?它明明没有放弃一致性啊?
  • 不考虑一致性的系统,有什么存在的意义呢?

CAP定理科普

CAP定理,指的是在一个分布式系统中,一致性(Consistency)、可用性(Availability)、分区容错性(Partition tolerance)。这三个要素最多只能同时实现两点,不可能三者兼顾。

  • 一致性(C):这里是指强一致性。在分布式系统中的所有数据备份,在同一时刻是否同样的值。并不是指整个系统能提供最新的一致的数据。
  • 可用性(A):这里是指100%可用性。客户端无论访问到哪个没有宕机的节点上,都能在有限的时间返回结果,并不是指整个系统处于可用状态。
  • 分区容错性(P):网络中允许丢失一个节点发给另一个节点的任意多的消息,即对网络分区的容忍。在发生网络分区时,能继续保持可用性或者一致性。
    一个系统要求在运行过程中不能发生网络分区,那么这个系统就不具备分区容错性。

CAP定理中的可用性和一致性与用户感知的可用性和一致性不是一个概念。我们追求的应该是用户感知的可用性,CAP中可用性和一致性给我们只是起到指导性的作用。

2017 年,Google 公司的第一代 Spanner 系统已经诞生。Brewer 写了一篇文章讲述了 Google 公司的 Spanner 系统,并且近一步阐述了按照 CAP 定理 Spanner 是一个什么样特性的系统。在文中,Brewer 指出 Spanner 系统说是”实际上的 CA”(effectively CA)系统。从架构上来讲,Spanner 是一个 CP 系统,也就是说当出现网络分区时,Spanner 选择的是保证数据的一致性,放弃可用性的。但实际上,Spanner 是具有非常高可用性效果的一个系统,从架构上 Spanner 没有达到 CAP 定理要求的那种完全可用性,但是也达到非常高的可用性,由于采用多副本的设计,个别副本出现网络分区,并不影响用户能感知到的可用性。按 CAP 定理的定义,当这些个别副本出现网络分区时,这些节点是不可用的,也就是系统没有达到完全可用性。但是此时的用户请求是可以被其他副本服务的,此时服务是可用的,也就是说用户仍然感知到 Spanner 是可用的。所以说用户感知的可用性和 CAP 定理中的可用性不是一个概念。我们追求的应该是用户感知的可用性。

BASE定理科普

BASE是对一致性和可用性权衡所得的结果。其核心思想是:在某些场景中,无需做到强一致性,以保证系统的可用性,同时每个应用可以采用适当的方式使系统数据达到最终一致性。

BASE分别是指:Basically Available, Soft State, Eventual Consistency

  • 基本可用(Basically Available):分布式系统在出现故障的时候,允许损失部分可用性,但不等于系统不可用。例如:响应时间的损失,功能上降级。
  • 软状态(Soft State):允许系统中的数据存在中间状态,并认为该状态不会影响系统整体可用性。即允许节点之间数据同步存在延时
  • 最终一致性(Eventually Consistent):本质是指系统需要使数据最终达到一致性,而不需要实时使数据达到一致性

ACID科普

对于ACID中一致性描述,可能理解都不一样,需要先统一下概念。

ACID中的C(一致性)的定义是:如果事务执行前数据库处于一致状态,那么当事务结束的时候,数据库也会处于一致性状态。这个一致性状态包含两层意思:

  • 第一层意思是指,数据库内部的完整性,包含实体完整性、域完整性、参照完整性、用户自定义完整性。使用外键、检查约束等,来保证事务执行中不会产生违背数据完整性的数据。例如:使用唯一约束的列不会出现两个一样的值。
    A transaction must preserve database consistency - if a transaction is run atomically in isolation starting from a consistent database, the database must again be consistent at the end of the transaction.
  • 第二层意思是对开发者的要求,数据库中的每一行和每个值必须与其所描述的现实保持一致。例如:银行转账,不能只一方扣钱或者一方加钱,我们的代码应该是,一方加钱一方扣钱。
    Ensuring consistency for an individual transaction is the responsibility of the application programmer who codes the transaction.

为什么CAP三者不可兼得?

在分布式系统中,各个组建必然部署在不同的节点上,因此必然出现子网络,同时网络本身又是不可靠的,一定存在延迟和数据丢失,即网络分区是必然存在的。所以P(分区容错性)是分布式系统必须要面对和解决的问题(你无法要求在永远不发生网络分区的环境下运行分布式系统)。

因此CAP三者不可兼得,变成如何在C(一致性)、A(可用性)二者进行抉择,可以举个例子来说明:在分布式环境中,为了确保系统可用性,通常会采用将数据复制到多个备份节点,而复制的过程需要通过网络交互。当发生网络分区时,你将面临两个选择:

  1. 如果坚持保持各节点之间的数据一致性(选择C),你需要等待网络分区恢复后,将数据复制完成,才可以向外部提供服务。期间发生网络分区将不能对外提供服务,因为它保证不了数据一致性。
  2. 如果选择可用性(选择A),发生网络分区的节点,依然需要向外提供服务。但是由于网络分区,它同步不了最新的数据,所以它返回数据,可能不是最新的(与其他节点不一致的)数据。

这里需要强调一句,CAP三者不可兼得,仅仅是指在发生网络分区情况下,我们才需要在A和C之间进行抉择,选择保证数据一致还是服务可用。而集群正常运行时,A和C是都可以保证的。

  • CP架构在当发生网络分区时,为了保证返回给客户端数据准确性,为了不破坏一致性,可能会因为无法响应最新数据,而拒绝响应。在网络分区恢复后,完成数据同步,才可处理客户端请求。
  • AP架构在发生网络分区时,发生分区的节点不需要等待数据完成同步,便可处理客户端请求,将尽可能的给用户返回相对新的数据。在网络分区恢复后,完成数据同步。

ACID与CP、BASE与AP,它们的关联关系?

根据上一小节C、A二者不可兼得的原因,我们可以总结AP和CP架构的特性。可以发现CP、AP两者其实是对ACID、BASE的延伸。是在发生网络分区情况下ACID、BASE的表现。

  • CP和ACID都是为了保证数据准确性(两者对准确性定义不同,参考前文科普)。但是两者解决的问题不一样:CP描述的是在发生网络分区时,保证数据准确性。ACID解决的是多个事务并发下,保证数据准确性。
  • AP和BASE都是对可用性的研究,BASE要求的只是基本可用,而AP对一致性的要求更低,所以能保证的可用性更高。

ACID与CP区别

ACID解决的问题是数据库系统中并发执行多个事务时的问题,是数据库领域的传统问题。那么多个事务并发会存在哪些问题?

  • 脏读: 事务T1读取了T2更改的x,但是T2在实际存储数据时可能出错回滚了,这时T1读取的实际是无效的数据,这种情况下就是脏读
  • 不可重复读:是说在T1读取x时,由于中间T2更改了x,所以T1前后两次读取的x值不相同,这就是所谓的不可重复读
  • 幻读:在T1读取符合某个条件的所有记录时,T2增加了一条符合该条件的记录,这就导致T1执行过程中前后读取的记录可能不一致,即T2之后读取时会多出一条记录。

为了解决这些问题,事务提出四种隔离级别来规避上述问题。而解决的就是ACID中的C(一致性),所以ACID中的C(一致性)可以理解为不出现脏读、幻读、不可重复读的问题。可以把它称为“内部一致性”,解决的是数据库内部的一致性问题。

CP中的C(一致性),相对好理解,我把它理解为“外部一致性”。就分布式系统而言的,针对客户端的请求,无论访问那个节点,都能获得最新的相同的值。

BASE与AP区别

BASE强调的是基本可用,允许损失部分可用性。这里的损失是指:

  • 响应时间上的损失:在发生故障时,允许在限定时间之外给用户响应。搜索引擎正常工作0.5秒内给用户返回数据。在部分机房故障后,查询时间增加到1-2秒。
  • 功能上的损失:在请求高峰时,可以选择一部分请求降级。

AP则强调是高可用,对数据一致性要求更低。eureka作为AP系统的代表,在发生网络分区时,eureka会移除注册列表中长时间没有心跳的服务,但是当丢失过多客户端时,该节点会进入自我保护,将不会移除过期的服务,并同时接收新服务注册,但不会同步到其他节点。在该种模式下,eureka集群剩下最后一个节点,也可以向外提供服务。

eureka属于AP系统吗?它明明没有放弃一致性啊?

描述AP和CP时,通常都会以eureka和zookeeper来具体。eureka是AP的代表作,zookeeper则是CP的代表作。二者之所以这样归类,是因为:

  • eureka各节点互相独立、平等的,各节点都提供查询和注册服务(读、写请求)。当发生网络分区,eureka各节点依旧可以接收和注册服务。并且当丢失过多客户端时,节点会进入自我保护(接收新服务注册、不删除过期服务)。在该种模式下,eureka集群剩下最后一个节点,也可以向外提供服务。尽管向外提供的数据可能是过期的数据。
  • zookeeper采用的过半原则,由leader处理写请求。当发生网络分区时,leader由于丢失过半的follower,从而处理不了客户端的请求,需要重新选举新leader,期间服务将不可用。糟糕的是,如果集群中没有过半的节点存活,将选举不出新leader,服务将一直处于不可用状态。

回答eureka没有放弃一致性的问题,还得回顾A、C之间的抉择。这二者需要二选一的情况下,一定是发生了网络分区的情况。eureka集群正常运行时,各节点之间可以正常通讯、保持心跳、复制数据,以此保持数据的一致性。但发生网络分区时,eureka确实选择了可用性,而放弃了一致性。

NWR

NWR是一种在分布式存储系统中用于控制一致性级别的一种策略。这个三个字母分别代表着:

  • N:分布式系统中,一个有多少个副本数据
  • W:处理一次写请求,需要更新多少个副本数据
  • R:处理一次读请求,需要读取多少个副本数据

NWR分别设置不同的值时,将会产生不同的一致性效果。

  • W+R>N,整个系统对于客户端的请求能保证强一致性。因为写请求和读请求一定存在一个相交的副本,读取的时候返回该副本的数据即可。
  • W+R<=N,整个系统对于客户端的请求则不能保证强一致性。

基于NWR的性质,我们可以动态的调节系统的一致性效果。还可以根据业务场景动态调整响应速度。以5节点集群为例,在保证强一致性的情况下,需要提高读请求的效率,则可以设置R=2、W=4或者R=1、W=5。当需要提高写请求效率时,则可以设置W=2、R=4或者W=1、R=5。

W、R的大小,直接影响其对应的处理效率。主要注意,读写请求的效率取决于最慢的副本处理速度。

建议阅读

CAP争论及历史:https://blog.csdn.net/chen77716/article/details/30635543

CAP,ACID,我们能做什么:http://hcoona.github.io/Tips/CAP-ACID-what-can-we-do/

理解数据库的事务,ACID,CAP和一致性:https://www.jianshu.com/p/2c30d1fe5c4e

nosql不应该放弃一致性:https://www.infoq.cn/article/rhzs0KI2G*Y2r9PMdeNv

做几个小题目吧

paxos算法,可以容忍少数集合节点宕机,我们可以认为paxos提供了比较高的可用性服务,同时又实现了一致性。那么是不是和CAP定理违背了呢?

没有违背。CAP定理中可用性是100%可用。
当发生网络分区,使paxos集群形成一个多数派集群和一个少数派集群时。
当客户端访问多数派节点时,虽然能收到正常的反馈,但是少数派集群的节点则不能。