# 分布式 CAP 和 BASE 理论

# CAP 理论

https://zh.wikipedia.org/zh-cn/CAP 定理

一致性(Consistency)

写操作之后进行读操作,无论在哪个节点都需要返回写操作的一致值。

可用性(Availability)

非故障的节点在合理的时间内返回合理的响应

分区容错性(Partition Tolerance)

区间通信可能失败,当网络出现分区后,系统依然能够继续履行职责。

为什么不能选择 CA 架构

在分布式的环境下,网络无法做到 100% 可靠,有可能出现故障,因此分区(P)是一个必须的选项。
如果选择了 CA 而放弃了 P,若发生分区现象:
为了保证 C,系统需要禁止写入,此时就与 A 发生冲突,
为了保证 A,会出现正常的分区可以写入数据,有故障的分区不能写入数据,则与 C 就冲突了。
因此分布式系统理论上不可能选择 CA 架构,而必须选择 CP 或 AP 架构。

# CAP 的实际应用

# 注册中心

注册中心解决服务注册和服务发现的问题

服务注册:实例将自身服务信息注册到注册中心,这部分信息包括服务的主机 IP 和服务的 Port,以及暴露服务自身状态和访问协议信息等。

服务发现:实例请求注册中心所依赖的服务信息,服务实例通过注册中心,获取到注册到其中的服务实例的信息,通过这些信息去请求它们提供的服务。

目前作为注册中心的一些组件大致有:dubbo 的 zookeeper,springcloud 的 eureka,consul,rocketMq 的 nameServer,hdfs 的 nameNode。

zookeeper 选择 CP

zookeep 保证 CP,即任何时刻对 zookeeper 的访问请求能得到一致性的数据结果,同时系统对网络分割具备容错性,但是它不能保证每次服务的可用性。从实际情况来分析,在使用 zookeeper 获取服务列表时,如果 zk 正在选举或者 zk 集群中半数以上的机器不可用,那么将无法获取数据。所以说,zk 不能保证服务可用性。

eureka 选择 AP

eureka 保证 AP,eureka 在设计时优先保证可用性,每一个节点都是平等的,一部分节点挂掉不会影响到正常节点的工作,不会出现类似 zk 的选举 leader 的过程,客户端发现向某个节点注册或连接失败,会自动切换到其他的节点,只要有一台 eureka 存在,就可以保证整个服务处在可用状态,只不过有可能这个服务上的信息并不是最新的信息。

zookeeper 和 eureka 的数据一致性问题

先要明确一点,eureka 的创建初心就是为一个注册中心,但是 zk 更多是作为分布式协调服务的存在,只不过因为它的特性被赋予了注册中心,它的职责更多是保证数据(配置数据,状态数据)在管辖下的所有服务之间保持一致,所有这个就不难理解为何 zk 被设计成 CP 而不是 AP,zk 最核心的算法 ZAB,就是为了解决分布式系统下数据在多个服务之间一致同步的问题。

更深层的原因,zookeeper 是按照 CP 原则构建,也就是说它必须保持每一个节点的数据都保持一致,如果 zookeeper 下节点断开或者集群中出现网络分割(例如交换机的子网间不能互访),那么 zk 会将它们从自己的管理范围中剔除,外界不能访问这些节点,即使这些节点是健康的可以提供正常的服务,所以导致这些节点请求都会丢失。

而 eureka 则完全没有这方面的顾虑,它的节点都是相对独立,不需要考虑数据一致性的问题,这个应该是 eureka 的诞生就是为了注册中心而设计,相对 zk 来说剔除了 leader 节点选取和事务日志极致,这样更有利于维护和保证 eureka 在运行的健壮性。

image

数据不一致性在注册服务中中会给 eureka 带来什么问题,无非就是某一个节点被注册的服务多,某个节点注册的服务少,在某一个瞬间可能导致某些 ip 节点被调用数少,某些 ip 节点调用数少的问题。也有可能存在一些本应该被删除而没被删除的脏数据。

image

所以对于注册中心来说,使用 AP 更合适。

# 分布式锁

redis 实现分布式锁

setnx key value ExpireTime

image

所以 redis 的复制模式是属于 AP 的模式。保证可用性,在主从复制中 “主” 有数据,但是可能 “从” 还没有数据,这个时候,一旦主挂掉或者网络抖动等各种原因,可能会切换到 “从” 节点,这个时候可能会导致两个业务县城同时获取得两把锁。

其实并不是 redis 的缺陷,只是 redis 采用了 AP 模型,它本身无法确保我们对一致性的要求。redis 官方推荐 redlock 算法来保证,问题是 redlock 至少需要三个 redis 主从实例来实现,维护成本比较高,相当于 redlock 使用三个 redis 集群实现了自己的另一套一致性算法,比较繁琐。

zookeeper 实现分布式锁

zookeeper 的特性

  • 有序节点:当在一个父目录下如 /lock 下创建 有序节点,节点会按照严格的先后顺序创建出自节点 lock000001,lock000002,lock0000003, 以此类推,有序节点能严格保证各个自节点按照排序命名生成。

  • 临时节点:客户端建立了一个临时节点,在客户端的会话结束或会话超时,zookepper 会自动删除该解 ID。

  • 事件监听:在读取数据时,我们可以对节点设置监听,当节点的数据发生变化(1 节点创建 2 节点删除 3 节点数据变成 4 自节点变成)时,zookeeper 会通知客户端。

结合这几个特点,来看下 zk 是怎么组合分布式锁。

image

  1. 业务线程 - 1 业务线程 - 2 分别向 zk 的 /lock 目录下,申请创建有序的临时节点
  2. 业务线程 - 1 抢到 /lock0001 的文件,也就是在整个目录下最小序的节点,也就是线程 - 1 获取到了锁
  3. 业务线程 - 2 只能抢到 /lock0002 的文件,并不是最小序的节点,线程 2 未能获取锁
  4. 业务线程 - 1 与 lock0001 建立了连接,并维持了心跳,维持的心跳也就是这把锁的租期
  5. 当业务线程 - 1 完成了业务,将释放掉与 zk 的连接,也就是释放了这把锁

# BASE 理论

https://dl.acm.org/doi/10.1145/1394127.1394128

基本可用 (Basically Available)

基本可用是指分布式系统在出现不可预知故障的时候,允许损失部分可用性。但是,这绝不等价于系统不可用。

软状态(Soft-state)

软状态指允许系统中的数据存在中间状态(CAP 理论中的 (C) 数据不一致),并认为该中间状态的存在不会影响系统的整体可用性,即允许系统在不同节点的数据副本之间进行数据同步的过程存在延时。

最终一致性(Eventually Consistent)

最终一致性强调的是系统中所有的数据副本,在经过一段时间的同步后,最终能够达到一个一致的状态。因此,最终一致性的本质是需要系统保证最终数据能够达到一致,而不需要实时保证系统数据的强一致性。

BASE 理论是对 CAP 中一致性 C 和可用性 A 权衡的结果,其来源于对大规模互联网系统分布式实践的总结,是基于 CAP 定理逐步演化而来的,它大大降低了我们对系统的要求。

BASE 的核心思想是:即使无法做到强一致性,但每个应用都可以根据自身业务特点,采用适当的方式来使系统达到最终一致性。

分布式一致性的 3 种级别:

  1. 强一致性:系统写入了什么,读出来的就是什么。
  2. 弱一致性:不一定可以读取到最新写入的值,也不保证多少时间之后读取到的数据是最新的,只是会尽量保证某个时刻达到数据一致的状态。
  3. 最终一致性:弱一致性的升级版,系统会保证在一定时间内达到数据一致的状态。

业界比较推崇是最终一致性级别,但是某些对数据一致要求十分严格的场景比如银行转账还是要保证强一致性。

那实现最终一致性的具体方式:

读时修复:在读取数据时,检测数据的不一致,进行修复。比如 Cassandra 的 Read Repair 实现,具体来说,在向 Cassandra 系统查询数据的时候,如果检测到不同节点的副本数据不一致,系统就自动修复数据。

写时修复:在写入数据,检测数据的不一致时,进行修复。比如 Cassandra 的 Hinted Handoff 实现。具体来说,Cassandra 集群的节点之间远程写数据的时候,如果写失败 就将数据缓存下来,然后定时重传,修复数据的不一致性。

异步修复:这个是最常用的方式,通过定时对账检测副本数据的一致性,并修复。

# 总结

ACID 是数据库事务完整性的理论,CAP 是分布式系统设计理论,BASE 是 CAP 理论中 AP 方案的延伸。

在微服务的构建中,永远都逃离不了 CAP 理论,因为网络永远不稳定,硬件总会老化,软件会可能出现 bug,所以分区容错性在微服务中是躲不过的命题,可以这么说,只要是分布式,只要是集群都面临着 AP 或者 CP 的选择,但你很贪心的时候,既要一致性又要可用性,那只能对一致性作出一点妥协,也就是引入了 BASE 理论,在业务允许的情况下实现最终一致性。

究竟是选 AP 还是选 CP,真的在于对业务的了解,例如金钱,库存相关会优先考虑 CP 模型,例如社区发帖相关可以优先选择 AP 模型,这个说白了其实基于对业务的了解是一个选择和妥协的过程。

参考资料

https://dl.acm.org/doi/10.1145/1394127.1394128

https://juejin.cn/post/6844903936718012430#heading-22

https://javaguide.cn/distributed-system/protocol/cap-and-base-theorem.html#base - 理论三要素

https://www.ruanyifeng.com/blog/2018/07/cap.html

-->