知道什么是CAP吗?不知道还敢称程序员?搞笑呢?

2023-05-16

分布式系统(distributed system)正变得越来越重要,大型网站几乎都是分布式的。

分布式系统的最大难点,就是各个节点的状态如何同步。CAP 定理是这方面的基本定理,也是理解分布式系统的起点。

本文介绍该定理。它其实很好懂,而且是显而易见的。下面的内容主要参考了 Michael Whittaker 的文章。

一、分布式系统的三个指标

 

1998年,加州大学的计算机科学家 Eric Brewer 提出,分布式系统有三个指标。

  • Consistency

  • Availability

  • Partition tolerance

它们的第一个字母分别是 C、A、P。

Eric Brewer 说,这三个指标不可能同时做到。这个结论就叫做 CAP 定理。

二、Availability

Availability 中文叫做"可用性",意思是只要收到用户的请求,服务器就必须给出回应。

用户可以选择向 G1 或 G2 发起读操作。不管是哪台服务器,只要收到请求,就必须告诉用户,到底是 v0 还是 v1,否则就不满足可用性。

三、Consistency

Consistency 中文叫做"一致性"。意思是,写操作之后的读操作,必须返回该值。举例来说,某条记录是 v0,用户向 G1 发起一个写操作,将其改为 v1。

接下来,用户的读操作就会得到 v1。这就叫一致性。

问题是,用户有可能向 G2 发起读操作,由于 G2 的值没有发生变化,因此返回的是 v0。G1 和 G2 读操作的结果不一致,这就不满足一致性了。

为了让 G2 也能变为 v1,就要在 G1 写操作的时候,让 G1 向 G2 发送一条消息,要求 G2 也改成 v1。

这样的话,用户向 G2 发起读操作,也能得到 v1。

 

四、Partition tolerance

先看 Partition tolerance,中文叫做"分区容错"。

大多数分布式系统都分布在多个子网络。每个子网络就叫做一个区(partition)。分区容错的意思是,区间通信可能失败。比如,一台服务器放在中国,另一台服务器放在美国,这就是两个区,它们之间可能无法通信。

上图中,G1 和 G2 是两台跨区的服务器。G1 向 G2 发送一条消息,G2 可能无法收到。系统设计的时候,必须考虑到这种情况。

一般来说,分区容错无法避免,因此可以认为 CAP 的 P 总是成立。CAP 定理告诉我们,剩下的 C 和 A 无法同时做到。

五、Consistency 和 Availability 的矛盾

一致性和可用性,为什么不可能同时成立?答案很简单,因为可能通信失败(即出现分区容错)。

如果保证 G2 的一致性,那么 G1 必须在写操作时,锁定 G2 的读操作和写操作。只有数据同步后,才能重新开放读写。锁定期间,G2 不能读写,没有可用性。

如果保证 G2 的可用性,那么势必不能锁定 G2,所以一致性不成立。

综上所述,G2 无法同时做到一致性和可用性。系统设计时只能选择一个目标。如果追求一致性,那么无法保证所有节点的可用性;如果追求所有节点的可用性,那就没法做到一致性。

六、可用性高于一致性需求场景

读者问,在什么场合,可用性高于一致性?

举例来说,发布一张网页到 CDN,多个服务器有这张网页的副本。后来发现一个错误,需要更新网页,这时只能每个服务器都更新一遍。

一般来说,网页的更新不是特别强调一致性。短时期内,一些用户拿到老版本,另一些用户拿到新版本,问题不会特别大。当然,所有人最终都会看到新版本。所以,这个场合就是可用性高于一致性。

ACID原则

即 Atomicity(原子性)、Consistency(一致性)、Isolation(隔离性)、Durability(持久性)。

ACID 原则描述了对分布式数据库的一致性需求,同时付出了可用性的代价。

  • Atomicity:每次操作是原子的,要么成功,要么不执行;

  • Consistency:数据库的状态是一致的,无中间状态;

  • Isolation:各种操作彼此互相不影响;

    数据库事务的隔离级别有4个,由低到高依次为

    Read uncommitted(未授权读取、读未提交)、

    Read committed(授权读取、读提交)、

    Repeatable read(可重复读取)、

    Serializable(序列化)、

    这四个级别可以逐个解决脏读、不可重复读、幻象读这几类问题。

  • Durability:状态的改变是持久的,不会失效。

 

BASE原则

BASE(Basic Availiability,Soft state,Eventually Consistency),牺牲掉对一致性的约束(最终一致性),来换取一定的可用性。

CAP指导分布式软件实施

在一个分布式系统中(指互相连接并共享数据的节点集合)中,当涉及到读写操作时,只能保证一致性(Consistence)、可用性(Availability)、分区容错性(Partition Tolerance)三者中的两个,另外一个须被牺牲。

服务注册中心,是选择 CP 还是选择 AP?

 

服务注册中心解决的问题

在讨论 CAP 之前先明确下服务注册中心主要是解决什么问题:

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

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

目前作为注册中心的一些组件大致有:

  • Dubbo 的 Zookeeper

  • Spring Cloud 的 Eureka,Consul

  • RocketMQ 的 nameServer

  • HDFS 的 nameNode

目前微服务主流是 Dubbo 和 Spring Cloud,使用更多的是 Zookeeper 和 Eureka,我们就来看看应该根据 CAP 理论怎么去选择注册中心。(Spring Cloud 也可以用 ZK,不过不是主流不讨论)

Zookeeper 选择 CP

Zookeeper 保障 CP,即任何时刻对 Zookeeper 的访问请求能得到一致性的数据结果,同时系统对网络分割具备容错性,但是它不能保障每次服务的可用性。

从实际情况来分析,在使用 Zookeeper 获取服务列表时,如果 ZK 正在选举或者 ZK 集群中半数以上的机器不可用,那么将无法获取数据。所以说,ZK 不能保障服务可用性。

Eureka 选择 AP

Eureka 保障 AP,Eureka 在设计时优先保障可用性,每一个节点都是平等的。

一部分节点挂掉不会影响到正常节点的工作,不会出现类似 ZK 的选举 Leader 的过程,客户端发现向某个节点注册或连接失败,会自动切换到其他的节点。

只要有一台 Eureka 存在,就可以保证整个服务处在可用状态,只不过有可能这个服务上的信息并不是新的信息。

ZK 和 Eureka 的数据一致性问题

先要明确一点,Eureka 的创建初心就是为一个注册中心,但是 ZK 更多是作为分布式协调服务的存在。

只不过因为它的特性被 Dubbo 赋予了注册中心,它的职责更多是保障数据(配置数据,状态数据)在管辖下的所有服务之间保持一致。

所以这个就不难理解为何 ZK 被设计成 CP 而不是 AP,ZK 核心的算法 ZAB,就是为了解决分布式系统下数据在多个服务之间一致同步的问题。

更深层的原因,ZK 是按照 CP 原则构建,也就是说它须保持每一个节点的数据都保持一致。

如果 ZK 下节点断开或者集群中出现网络分割(例如交换机的子网间不能互访),那么 ZK 会将它们从自己的管理范围中剔除,外界不能访问这些节点,即使这些节点是健康的可以提供正常的服务,所以导致这些节点请求都会丢失。

而 Eureka 则没有这方面的顾虑,它的节点都是相对独立,不需要考虑数据一致性的问题,这个应该是 Eureka 的诞生就是为了注册中心而设计。

相对 ZK 来说剔除了 Leader 节点选取和事务日志机制,这样更有利于维护和保障 Eureka 在运行的健壮性。

再来看看,数据不一致性在注册服务中会给 Eureka 带来什么问题,无非就是某一个节点被注册的服务多,某个节点注册的服务少,在某一个瞬间可能导致某些 IP 节点被调用数多,某些 IP 节点调用数少的问题。

也有可能存在一些本应该被删除而没被删除的脏数据。

服务注册应该选择 AP 还是 CP

对于服务注册来说,针对同一个服务,即使注册中心的不同节点保存的服务注册信息不相同,也并不会造成灾难性的后果。

对于服务消费者来说,能消费才是最重要的,就算拿到的数据不是新的数据,消费者本身也可以进行尝试失败重试。总比为了追求数据的一致性而获取不到实例信息整个服务不可用要好。

所以,对于服务注册来说,可用性比数据一致性更加的重要,选择 AP。

分布式锁,是选择 CA 还是选择 CP?

这里实现分布式锁的方式选取了三种:

  • 基于数据库实现分布式锁

  • 基于 Redis 实现分布式锁

  • 基于 Zookeeper 实现分布式锁

#

基于数据库实现分布式锁

构建表结构:

利用表的 UNIQUE KEY idx_lock(method_lock)作为主键,当进行上锁时进行 Insert 动作,数据库成功录入则以为上锁成功,当数据库报出 Duplicate entry 则表示无法获取该锁。

不过这种方式对于单主却无法自动切换主从的 MySQL 来说,基本就无法实现 P 分区容错性(MySQL 自动主从切换在目前并没有很好的解决方案)。

可以说这种方式强依赖于数据库的可用性,数据库写操作是一个单点,一旦数据库挂掉,就导致锁的不可用。这种方式基本不在 CAP 的一个讨论范围。

基于 Redis 实现分布式锁

Redis 单线程串行处理天然就是解决串行化问题,用来解决分布式锁是再适合不过。

实现方式:

setnx key value Expire_time获取到锁 返回 1 , 获取失败 返回 0

为了解决数据库锁的无主从切换的问题,可以选择 Redis 集群,或者是 Sentinel 哨兵模式,实现主从故障转移,当 Master 节点出现故障,哨兵会从 Slave 中选取节点,重新变成新的 Master 节点。

哨兵模式故障转移是由 Sentinel 集群进行监控判断,当 Maser 出现异常即复制中止,重新推选新 Slave 成为 Master,Sentinel 在重新进行选举并不在意主从数据是否复制完毕具备一致性。

所以 Redis 的复制模式是属于 AP 的模式。保障可用性,在主从复制中“主”有数据,但是可能“从”还没有数据。

这个时候,一旦主挂掉或者网络抖动等各种原因,可能会切换到“从”节点,这个时候可能会导致两个业务线程同时获取得两把锁。

这个过程如下:

  • 业务线程 -1 向主节点请求锁

  • 业务线程 -1 获取锁

  • 业务线程 -1 获取到锁并开始执行业务

  • 这个时候 Redis 刚生成的锁在主从之间还未进行同步

  • Redis 这时候主节点挂掉了

  • Redis 的从节点升级为主节点

  • 业务线程 -2 想新的主节点请求锁

  • 业务线程 -2 获取到新的主节点返回的锁

  • 业务线程 -2 获取到锁开始执行业务

  • 这个时候业务线程 -1 和业务线程 -2 同时在执行任务

上述的问题其实并不是 Redis 的缺陷,只是 Redis 采用了 AP 模型,它本身无法保障我们对一致性的要求。

Redis 推荐 Redlock 算法来保障,问题是 Redlock 至少需要三个 Redis 主从实例来实现,维护成本比较高。

相当于 Redlock 使用三个 Redis 集群实现了自己的另一套一致性算法,比较繁琐,在业界也使用得比较少。

能不能使用 Redis 作为分布式锁?这个本身就不是 Redis 的问题,还是取决于业务场景。

我们先要自己确认我们的场景是适合 AP 还是 CP , 如果在社交发帖等场景下,我们并没有很强的事务一致性问题,Redis 提供给我们高性能的 AP 模型是很适合的。

但如果是交易类型,对数据一致性很敏感的场景,我们可能要寻找一种更加适合的 CP 模型。

基于 Zookeeper 实现分布式锁

刚刚也分析过,Redis 其实无法保障数据的一致性,先来看 Zookeeper 是否适合作为我们需要的分布式锁。

首先 ZK 的模式是 CP 模型,也就是说,当 ZK 锁提供给我们进行访问的时候,在 ZK 集群中能保障这把锁在 ZK 的每一个节点都存在。

这个实际上是 ZK 的 Leader 通过二阶段提交写请求来保障的,这个也是 ZK 的集群规模大了的一个瓶颈点。

①ZK 锁实现的原理

说 ZK 的锁问题之前先看看 Zookeeper 中几个特性,这几个特性构建了 ZK 的一把分布式锁。

ZK 的特性如下:

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

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

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

 

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

  • 业务线程 -1,业务线程 -2 分别向 ZK 的 /lock 目录下,申请创建有序的临时节点。

  • 业务线程 -1 抢到 /lock0001 的文件,也就是在整个目录下小序的节点,也就是线程 -1 获取到了锁。

  • 业务线程 -2 只能抢到 /lock0002 的文件,并不是小序的节点,线程 2 未能获取锁。

  • 业务线程 -1 与 lock0001 建立了连接,并维持了心跳,维持的心跳也就是这把锁的租期

  • 当业务线程 -1 完成了业务,将释放掉与 ZK 的连接,也就是释放了这把锁。

#

②ZK 分布式锁的代码实现

ZK 官方提供的客户端并不支持分布式锁的直接实现,我们需要自己写代码去利用 ZK 的这几个特性去进行实现。

究竟该用 CP 还是 AP 的分布式锁

首先得了解清楚我们使用分布式锁的场景,为何使用分布式锁,用它来帮我们解决什么问题,先聊场景后聊分布式锁的技术选型。

无论是 Redis,ZK,例如 Redis 的 AP 模型会限制很多使用场景,但它却拥有了几者中很高的性能。

Zookeeper 的分布式锁要比 Redis 可靠很多,但他繁琐的实现机制导致了它的性能不如 Redis,而且 ZK 会随着集群的扩大而性能更加下降。

简单来说,先了解业务场景,后进行技术选型。

分布式事务,是怎么从 ACID 解脱,投身 CAP/BASE

如果说到事务,ACID 是传统数据库常用的设计理念,追求强一致性模型,关系数据库的 ACID 模型拥有高一致性+可用性,所以很难进行分区。

在微服务中 ACID 已经是无法支持,我们还是回到 CAP 去寻求解决方案,不过根据上面的讨论,CAP 定理中,要么只能 CP,要么只能 AP。

如果我们追求数据的一致性而忽略可用性这个在微服务中肯定是行不通的,如果我们追求可用性而忽略一致性,那么在一些重要的数据(例如支付,金额)肯定出现漏洞百出,这个也是无法接受。所以我们既要一致性,也要可用性。

都要是无法实现的,但我们能不能在一致性上作出一些妥协,不追求强一致性,转而追求最终一致性,所以引入 BASE 理论。

在分布式事务中,BASE 重要是为 CAP 提出了一致性的解决方案,BASE 强调牺牲高一致性,从而获取可用性,数据允许在一段时间内不一致,只要保障一致性就可以了。

实现一致性

弱一致性: 系统不能保障后续访问返回更新的值。需要在一些条件满足之后,更新的值才能返回。

从更新操作开始,到系统保障任何观察者总是看到更新的值的这期间被称为不一致窗口。

一致性: 这是弱一致性的特殊形式;存储系统保障如果没有对某个对象的新更新操作,所有的访问将返回这个对象的更新的值。

BASE 模型

BASE 模型是传统 ACID 模型的反面,不同于 ACID,BASE 强调牺牲高一致性,从而获得可用性,数据允许在一段时间内的不一致,只要保障最终一致就可以了。

BASE 模型反 ACID 模型,完全不同 ACID 模型,牺牲高一致性,获得可用性或可靠性:Basically Available 基本可用。

支持分区失败(e.g. sharding碎片划分数据库)Soft state 软状态,状态可以有一段时间不同步,异步。

Eventually consistent 一致,最后数据是一致的就可以了,而不是时时一致。

完!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

知道什么是CAP吗?不知道还敢称程序员?搞笑呢? 的相关文章

  • 二、Redis安装配置(云服务器、vmware本地虚拟机)

    一 自己购买服务器 自己购买阿里云 青牛云 腾讯云或华为云服务器 xff0c 自带CentoOS或者Ubuntu环境 xff0c 直接开干 二 Vmware本地虚拟机安装 1 VMWare虚拟机的安装 xff0c 不讲解 xff0c 默认懂
  • 【MySQL基础】数据类型

    文章目录 整数类型浮点类型定点数类型日期和时间类型字符串类型文本类型二进制字符串类型JSON 类型位类型ENUM类型SET类型空间类型 整数类型 整数类型一共有 5 种 xff0c 包括 TINYINT SMALLINT MEDIUMINT
  • ubuntu16.04备份和迁移

    ubuntu16 04备份和迁移 背景实践1 备份整个系统2 重装Ubuntu16 043 恢复系统 题外话 xff1a 修改主机名参考文章 背景 此文用来快速记录备份和恢复的过程步骤 xff0c 具体命令意思不做过多介绍 因为不想新设备重
  • c++20协程基础概念

    c 43 43 协程介绍 前言 官方文档地址 本文主要对c 43 43 reference做翻译 不会逐字翻译 xff0c 同时对其中的概念以及协程运行过程做对应的解释 因为是学习过程中的记录 xff0c 如有问题 xff0c 希望大家能够
  • Flask 与 Django 框架对比

    详细分析了两种 Python Web框架 xff1a Flask 与 Django 从开发难易度 应用架构 性能 可扩展性以及适用范围等方面进行了详细说明 Django 中级教程在 B 站上线 xff0c 深入解析 Django 体系架构
  • STM32F103C8T6基础开发教程(HAL库)—点亮第一颗LED灯

    STM32F103C8T6基础开发教程目录 STM32F103C8T6基础开发教程 xff08 HAL库 xff09 开发环境配置STM32F103C8T6基础开发教程 xff08 HAL库 xff09 Keil添加注释的快捷键STM32F
  • C++实现插入排序算法(直接插入排序、折半插入排序、希尔排序)

    排序算法分为五大类 xff0c 一共是有九种 xff0c 如下 xff1a 插入类 xff1a 直接插入排序 折半插入排序 希尔排序 交换类 xff1a 冒泡排序 快速排序 选择类 xff1a 简单选择排序 堆排序 归并类 xff1a 二路
  • C++实现二路归并排序算法

    排序算法分为五大类 xff0c 一共是有九种 xff0c 如下 xff1a 插入类 xff1a 直接插入排序 折半插入排序 希尔排序 交换类 xff1a 冒泡排序 快速排序 选择类 xff1a 简单选择排序 堆排序 归并类 xff1a 二路
  • C语言实现-学生信息管理系统

    通过C语言实现一个学生信息管理系统 xff0c 要求如下 xff1a xff08 1 xff09 用户采用自己账号和密码登录系统 xff1b xff08 2 xff09 学生信息和账号密码通过文件的形式存储 xff1b xff08 3 xf
  • 通过python画矢量图(matplotlib,有代码)

    python画矢量图 xff08 有代码 xff09 python的matplotlib可以保存的文件格式word可以插入哪些图片格式呢代码中文乱码问题 有些同学因为文章的要求 xff0c 图片插入到word里的时候需要足够清晰 xff0c
  • Java实现LRU

    首先看看什么是LRU LRU是Least Recently Used的缩写 xff0c 即最近最少使用 xff0c 是一种常用的页面置换算法 xff0c 选择最近最久未使用的页面予以淘汰 该算法赋予每个页面一个访问字段 xff0c 用来记录
  • 域名cdn加速(apache与nginx)

    一 xff1a 由于公司业务属于请求量比较大的吧 xff0c 每个月几亿条 xff0c 考虑到安全性 xff0c 所以需要域名由http改为https cdn加速才可以支成撑业务 二 xff1a 之前的系统是使用lamp配置的 xff0c
  • ubuntu系统安装完nvidia显卡驱动后黑屏,不能进入系统

    我之前安装了系统里建议安装的nvidia 380显卡驱动 xff0c 为了安装更高版本的CUDA xff0c 我将nvidia显卡驱动升级到了430 xff0c 但是重启电脑进入Ubuntu系统时黑屏 xff0c 进不去系统界面 xff0c
  • 黑盒模糊测试之AFL++

    git clone depth 1 https github com AFLplusplus AFLplusplus cd AFLplusplus make Build Summary 43 afl fuzz and supporting
  • centos7安装MySQL5.7

    一 下载mysql5 7 1 下载地址 mysql 5 7 28 1 el7 x86 64 rpm bundle tar 2 上传至服务器 3 解压压缩包 解压命令 tar xvf mysql 5 7 28 1 el7 x86 64 rpm
  • 打包VSCode源码为安装程序(.exe)

    参考博客 GitHub vscode里的Packaging部分 xff1a https github com microsoft vscode wiki How to Contribute 这里提供了vscode打包后可以发布的平台代码 x
  • Ubuntu的安装卡在安装界面 (解决方法记录)

    安装过程 在 Install Ubuntu 的grub 选项上点击e 在 Linux 系统的启动参数 倒数第二行 中加入 nomodset 安装界面分辨率会有问题 xff0c 但这个之后再解决 安装过程中 xff0c 如果需要拖拽窗口 Al
  • 树莓派ubuntuMATE 安装xrdp来进行显示图形的远程调试

    树莓派ubuntuMATE 安装xrdp来进行显示图形的远程调试 引 在树莓派上调试显示图形界面的项目 xff0c 纠结配显示器的问题 pi本身有hdmi的接口 xff0c 如果有多余的显示器直接连接的那最好 xff0c 倘若接口不合适也可
  • C与C++源文件的拼接

    C 43 43 与C处理函数名 如果C 43 43 两个cpp源文件中函数名称相同 xff0c 会出现如下错误 xff08 ave就是函数名 xff09 34 int cdecl ave void 34 ave 64 64 YAHXZ 已经
  • 【Android-Socket】Socket通信笔记(单例模式,线程管理,AsyncTask)

    扉 本作学习视频来源 https www bilibili com video BV1Nx411r7Pr t 61 940 amp p 61 11界面参考 https blog csdn net fszeng2011 article det

随机推荐