如何实践CAP 一致性协议及应用

发布时间:2021-09-10 13:47:34 作者:柒染
来源:亿速云 阅读:118

这期内容当中小编将会给大家带来有关如何实践CAP 一致性协议及应用,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。

一、一致性

1.1 CAP 理论

  1. C 一致性:分布式环境中,一致性是指多个副本之间,在同一时刻能否有同样的值

  2. A 可用性:系统提供的服务必须一直处于可用的状态。即使集群中一部分节点故障。

  3. P 分区容错性:系统在遇到节点故障,或者网络分区时,任然能对外提供一致性和可用性的服务。以实际效果而言,分区相当于通信的时限要求。系统如果不能在一定实现内达成数据一致性,也就意味着发生了分区的情况。必须就当前操作在 C 和 A 之前作出选择

1.2 CAP不能同时满足的证明

假设系统中有 5 个节点,n1~n5。n1,n2,n3 在A物理机房。n4,n5 在 B 物理机房。现在发生了网络分区,A 机房和 B 机房网络不通。

保证一致性:此时客户端在 A 机房写入数据,不能同步到B机房。写入失败。此时失去了可用性。

保证可用性:数据在 A 机房的 n1~n3 节点都写入成功后返回成功。数据在 B 机房的 n4~n5 节点也写入数据,返回成功。同一份数据在 A 机房和 B 机房出现了数据不一致的情况。聪明如你,可以想到 zookeeper,当一个节点 down 掉,系统会将其剔出节点,然其它一半以上的节点写入成功即可。是不是 zookeeper 同时满足了 CAP 呢。其实这里有一个误区,系统将其剔出节点。有一个隐含的条件是,系统引入了一个调度者,一个踢出坏节点的调度者。当调度者和 zookeeper 节点出现网络分区,整个系统还是不可用的。

1.3 常见场景

CA without P: 在分布式环境中,P 是不可避免的,天灾(某软公司的Azure被雷劈劈中)人祸(某里公司 A 和 B 机房之间的光缆被挖断)都能导致P。

CP without A:相当于每个写请求都须在Server之前强一致。P (分区)会导致同步时间无限延长。这个是可以保证的。例如数据库的分布式事务,两阶段提交,三阶段提交等。

AP without C: 当网络分区发生,A 和 B 集群失去联系。为了保证高可用,系统在写入时,系统写入部分节点就会返回成功,这会导致在一定时间之内,客户端从不同的机器上面读取到的是数据是不一样的。例如 redis 主从异步复制架构,当 master down 掉,系统会切换到 slave,由于是异步复制,salve 不是最新的数据,会导致一致性的问题。

二、一致性协议

2.1 两阶段提交协议(2PC)

二阶段提交( Two-phaseCommit )是指,在计算机网络以及数据库领域内,为了使基于分布式系统架构下的所有节点在进行事务提交时保持一致性而设计的一种算法( Algorithm )。通常,二阶段提交也被称为是一种协议( Protocol )。在分布式系统中,每个节点虽然可以知晓自己的操作时成功或者失败,却无法知道其他节点的操作的成功或失败。当一个事务跨越多个节点时,为了保持事务的 ACID 特性,需要引入一个作为协调者的组件来统一掌控所有节点(称作参与者)的操作结果并最终指示这些节点是否要把操作结果进行真正的提交(比如将更新后的数据写入磁盘等等)。因此,二阶段提交的算法思路可以概括为:参与者将操作成败通知协调者,再由协调者根据所有参与者的反馈情报决定各参与者是否要提交操作还是中止操作。

2.1.1 两种角色
2.1.2 处理阶段
2.1.3 异常情况处理
2.1.4 缺点

2.2 三阶段提交协议(3PC)

2PC 当时只考虑如果单机故障的情况,是可以勉强应付的。当遇到协调者和参与者同时故障的话,2PC 的理论是不完善的。此时 3PC 登场。

3PC 就是对 2PC 漏洞的补充协议。主要改动两点:

  1. 在 2PC 的第一阶段和第二阶段插入一个准备阶段,做到就算参与者和协调者同时故障也不阻塞,并且保证一致性。

  2. 在协调者和参与者之间引入超时机制

2.2.1 处理的三个阶段
2.2.2 缺点

由于 3PC 有超时机制的存在,2PC 中未解决的问题,参与者和协调者同时 down 掉,也就解决了。一旦参与者在超时时间内没有收到协调者的消息,就会自己提交。这样也能避免参与者一直占用共享资源。但是其在网络分区的情况下,不能保证数据的一致性。

2.3 Paxos协议

像 2PC 和 3PC 都需要引入一个协调者的角色,当协调者 down 掉之后,整个事务都无法提交,参与者的资源都出于锁定的状态,对于系统的影响是灾难性的,而且出现网络分区的情况,很有可能会出现数据不一致的情况。有没有不需要协调者角色,每个参与者来协调事务呢,在网络分区的情况下,又能最大程度保证一致性的解决方案呢。此时 Paxos 出现了。

Paxos 算法是 Lamport 于 1990 年提出的一种基于消息传递的一致性算法。由于算法难以理解起初并没有引起人们的重视,Lamport在八年后重新发表,即便如此Paxos算法还是没有得到重视。2006 年 Google 的三篇论文石破天惊,其中的 chubby 锁服务使用Paxos 作为 chubbycell 中的一致性,后来才得到关注。

2.3.1 解决了什么问题
2.3.2 两种角色(两者可以是同一台机器)

由于 Paxos 和下文提到的 zookeeper 使用的 ZAB 协议过于相似,详细讲解参照下文, Zookeeper原理部分。

2.4 Raft协议

Paxos 是论证了一致性协议的可行性,但是论证的过程据说晦涩难懂,缺少必要的实现细节,而且工程实现难度比较高广为人知实现只有 zk 的实现 zab 协议。然后斯坦福大学 RamCloud 项目中提出了易实现,易理解的分布式一致性复制协议 Raft。Java,C++,Go 等都有其对应的实现。

2.4.1 基本名词
2.4.2 特性
2.4.3 选主契机
  1. 在超时时间内没有收到 Leader 的心跳

  2. 启动时

2.4.4 选主过程

如何实践CAP 一致性协议及应用

如何实践CAP 一致性协议及应用

如图 raft-2所示,Raft将时间分为多个 term(任期),term 以连续的整数来标识,每个 term 表示一个选举的开始。例如 Follower 节点 1,在 term1 和 term2 连接处的时间,联系不到 Leader,将 currentTerm 编号加1,变成2,进入了到term2任期,在 term2 的蓝色部分选举完成,绿色部分正常工作。

当然一个任期不一定能选出 Leader,那么会将 currentTerm 继续加1,然后继续进行选举,例如图中的 t3。选举的原则是,每一轮选举每个选民一张选票,投票的请求先到且选民发现候选人节点的日志 id 大于等于自己的,就会投票,否者不会投票。获得半数以上的票的节点成为主节点,注意这并不是说选出来的事务 id 一定是最大的,例如下图 raft-1a~f六个节点(正方形框里面的数字是选举的轮数 term)。

在第四轮选举中,a 先发出投票,六台机器中,a~e 都会投 a,即使 f 不投 a,a 也会赢得选举。如果没有事务id(如刚启动时),就遵循投票请求先来先头。然后 Leader 将最新的日志复制到各个节点,再对外提供服务。

当然除了这些选举限制,还会有其他的情况。如 commit 限制等保证,Leader 选举成功一定包含所有的 commit 和 log。

2.4.5 日志复制过程

如何实践CAP 一致性协议及应用

raft 日志写入过程,主节点收到一个 x=1的请求后,会写入本地日志,然后将 x=1的日志广播出去,follower 如果收到请求,会将日志写入本地 log ,然后返回成功。当 leader 收到半数以上的节点回应时,会将此日志的状态变为 commit,然后广播消息让 follwer 提交日志。节点在 commit 日志后,会更新状态机中的 logindex 。

firstLogIndex/lastLogIndex 为节点中开始和结束的索引位置(包含提交,未提交,写入状态机)commitIndex:已提交的索引。applyIndex:已写入状态机中的索引。

日志复制的本质是让 follwer 和 Leader 的已提交的日志顺序和内容都完全一样,用于保证一致性。

具体的原则就是:

 原则1:两个日志在不同的 raft 节点中,如果有两个相同的 term 和 logIndex ,则保证两个日志的内容完全一样。

原则2:两段日志在不同的 raft 节点中,如果起始和终止的的 term,logIndex 都相同,那么两段日志中日志内容完全一样。

如何保证

第一个原则只需要在创建 logIndex 的时候使用新的 logIndex,保证 logIndex 的唯一性。而且创建之后不去更改。那么在 leader 复制到 follwer 之后,logIndex,term 和日志内容都没变。

第二个原则,在 Leader 复制给 Follower 时,要传递当前最新日志 currenTermId 和currentLogIndex,以及上一条日志 preCurrentTermId 和 preCurrentLogIndex。如图 raft-1,在 d 节点,term7,logIndex12。在给节点节点 a 同步时,发送(term7,logIndex11),(term7,logIndex12),a 节点没有找到(term7,logIndex11)的日志,会让Leader,d 节点重新发送。d 节点会重新发(term6,logIndex10)(term7,logIndex11),还是没有(term6,logIndex10)的日志,依然会拒绝同步。接着发(term6,logIndex9)(term6,logIndex10)。现在a节点有了(term6,logIndex9)。那么  leader 节点就会将(term6,logIndex9) ~ (term7,logIndex11)日志内容给节点 a,节点 a 将会和节点 d 有一样的日志。

三、Zookeeper 原理

3.1 概述

Google 的粗粒度锁服务 Chubby 的设计开发者 Burrows 曾经说过:“所有一致性协议本质上要么是 Paxos 要么是其变体”。Paxos 虽然解决了分布式系统中,多个节点就某个值达成一致性的通信协议。但是还是引入了其他的问题。由于其每个节点,都可以提议提案,也可以批准提案。当有三个及以上的 proposer 在发送 prepare 请求后,很难有一个 proposer 收到半数以上的回复而不断地执行第一阶段的协议,在这种竞争下,会导致选举速度变慢

所以 zookeeper 在 paxos 的基础上,提出了 ZAB 协议,本质上是,只有一台机器能提议提案(Proposer),而这台机器的名称称之为 Leader 角色。其他参与者扮演 Acceptor 角色。为了保证 Leader 的健壮性,引入了 Leader 选举机制。

ZAB协议还解决了这些问题

  1. 在半数以下节点宕机,依然能对台提供服务

  2. 客户端所有的写请求,交由 Leader 来处理。写入成功后,需要同步给所有的 follower 和 observer

  3. leader 宕机,或者集群重启。需要确保已经再 Leader 提交的事务最终都能被服务器提交,并且确保集群能快速回复到故障前的状态

3.2 基本概念

3.3 常见的误区

3.4 选举同步过程

3.4.1 发起投票的契机
  1. 节点启动

  2. 节点运行期间无法与 Leader 保持连接,

  3. Leader 失去一半以上节点的连接

3.4.2 如何保证事务

ZAB 协议类似于两阶段提交,客户端有一个写请求过来,例如设置 /my/test 值为 1,Leader 会生成对应的事务提议(proposal)(当前 zxid为 0x5000010 提议的 zxid 为Ox5000011),现将 set/my/test1(此处为伪代码)写入本地事务日志,然后 set/my/test1日志同步到所有的follower。follower收到事务 proposal ,将 proposal 写入到事务日志。如果收到半数以上 follower 的回应,那么广播发起 commit 请求。follower 收到 commit 请求后。会将文件中的 zxid ox5000011 应用到内存中。

上面说的是正常的情况。有两种情况。第一种 Leader 写入本地事务日志后,没有发送同步请求,就 down 了。即使选主之后又作为 follower 启动。此时这种还是会日志会丢掉(原因是选出的 leader 无此日志,无法进行同步)。第二种 Leader 发出同步请求,但是还没有 commit 就 down 了。此时这个日志不会丢掉,会同步提交到其他节点中。

3.4.3 服务器启动过程中的投票过程

现在 5 台 zk 机器依次编号 1~5

  1. 节点 1 启动,发出去的请求没有响应,此时是 Looking 的状态

  2. 节点 2 启动,与节点 1 进行通信,交换选举结果。由于两者没有历史数据,即 zxid 无法比较,此时 id 值较大的节点 2 胜出,但是由于还没有超过半数的节点,所以 1 和 2 都保持 looking 的状态

  3. 节点 3 启动,根据上面的分析,id 值最大的节点 3 胜出,而且超过半数的节点都参与了选举。节点 3 胜出成为了 Leader

  4. 节点 4 启动,和 1~3 个节点通信,得知最新的 leader 为节点 3,而此时 zxid 也小于节点 3,所以承认了节点 3 的 leader 的角色

  5. 节点 5 启动,和节点 4 一样,选取承认节点 3 的 leader 的角色

3.4.4 服务器运行过程中选主过程

如何实践CAP 一致性协议及应用

1. 节点 1 发起投票,第一轮投票先投自己,然后进入 Looking 等待的状态。

2. 其他的节点(如节点 2 )收到对方的投票信息。节点 2 在 Looking 状态,则将自己的投票结果广播出去(此时走的是上图中左侧的 Looking 分支);如果不在 Looking 状态,则直接告诉节点 1 当前的 Leader 是谁,就不要瞎折腾选举了(此时走的是上图右侧的 Leading/following 分支)。

3. 此时节点 1,收到了节点 2 的选举结果。如果节点 2 的 zxid 更大,那么清空投票箱,建立新的投票箱,广播自己最新的投票结果。在同一次选举中,如果在收到所有节点的投票结果后,如果投票箱中有一半以上的节点选出了某个节点,那么证明 leader 已经选出来了,投票也就终止了。否则一直循环。

zookeeper 的选举,优先比较大 zxid,zxid 最大的节点代表拥有最新的数据。如果没有 zxid,如系统刚刚启动的时候,则比较机器的编号,优先选择编号大的。

3.5 同步的过程

在选出 Leader 之后,zk 就进入状态同步的过程。其实就是把最新的 zxid 对应的日志数据,应用到其他的节点中。此 zxid 包含 follower 中写入日志但是未提交的 zxid 。称之为服务器提议缓存队列 committedLog 中的 zxid。

同步会完成三个 zxid 值的初始化。

peerLastZxid:该 learner 服务器最后处理的 zxid。 minCommittedLog:leader服务器提议缓存队列 committedLog 中的最小 zxid。 maxCommittedLog:leader服务器提议缓存队列 committedLog 中的最大 zxid。

系统会根据 learner 的 peerLastZxid和 leader 的 minCommittedLogmaxCommittedLog做出比较后做出不同的同步策略。

3.5.1 直接差异化同步

场景: peerLastZxid介于 minCommittedLogZxidmaxCommittedLogZxid间。

此种场景出现在,上文提到过的,Leader 发出了同步请求,但是还没有 commit 就 down 了。 leader 会发送 Proposal 数据包,以及 commit 指令数据包。新选出的 leader 继续完成上一任 leader 未完成的工作。

例如此刻 Leader 提议的缓存队列为 0x20001,0x20002,0x20003,0x20004,此处 learn 的 peerLastZxid 为 0x20002,Leader会将 0x20003 和 0x20004 两个提议同步给 learner。

3.5.2 先回滚在差异化同步/仅回滚同步

此种场景出现在,上文提到过的,Leader 写入本地事务日志后,还没发出同步请求,就 down 了,然后在同步日志的时候作为 learner 出现。

例如即将要 down 掉的 leader 节点 1,已经处理了 0x20001,0x20002,在处理 0x20003 时还没发出提议就 down 了。后来节点 2 当选为新 leader,同步数据的时候,节点 1 又神奇复活。如果新 leader 还没有处理新事务,新 leader 的队列为,0x20001, 0x20002,那么仅让节点 1 回滚到 0x20002 节点处,0x20003 日志废弃,称之为仅回滚同步。如果新 leader 已经处理 0x30001 , 0x30002 事务,那么新 leader 此处队列为0x20001,0x20002,0x30001,0x30002,那么让节点 1 先回滚,到 0x20002 处,再差异化同步0x30001,0x30002。

3.5.3 全量同步

peerLastZxid小于 minCommittedLogZxid或者 leader 上面没有缓存队列。leader 直接使用 SNAP 命令进行全量同步。

四、使用 Raft + RocksDB 有赞分布式 KV 存储服务

当前开源的缓存 kv 系统,大都是 AP 系统,例如设置主从同步集群 redis,master 异步同步到 slave。虽然在 master 停止服务后,slave 会顶上来。但是在 master 写入了数据,但是还没来得及同步到 slave 就 down 了,然后 slave 被选为主节点继续对外提供服务的情况下,会丢失部分数据。这对于要求强一致性的系统来说是不可接受的。例如很多场景下 redis 做分布式锁,有天然的缺陷在里面,如果 master 停止服务,这个锁不很不可靠的,虽然出现的几率很小,但一旦出现,将是致命的错误。

为了实现 CP 的 KV 存储系统,且要兼容现有的 redis 业务。有赞开发了 ZanKV(先已开源ZanRedisDB)。

如何实践CAP 一致性协议及应用

如何实践CAP 一致性协议及应用

底层的存储结构是 RocksDB(底层采用 LSM 数据结构)。一个 setx=1的会通过 redis protocol 协议传输,内容会通过 Raft 协议,同步写入到其他的节点的 RocksDB。有了Raft 理论的加持,RocksDB优秀的存储性能,即使遇到网络分区,master 节点 down 掉, slave 节点 down 掉,等一系列异常情况,其都能轻松应对。在扩容方面,系统用选择维护映射表的方式来建立分区和节点的关系,映射表会根据一定的算法并配合灵活的策略生成,来达到方便扩容。具体原理可参见《使用开源技术构建有赞分布式KV存储服务》。

从三个方面介绍了一致性,首先是描述分布架构中的核心理论-CAP,以及其简单的证明。第二部分介绍了 CAP 里面协议,重点介绍了 Raft 协议。第三部分,重点介绍了常用的 zookeeper 原理。

为了保证数据 commit 之后不可丢,系统都会采用(WAL write ahead log)(在每次修改数据之前先写操作内容日志,然后再去修改数据。即使修改数据时异常,也可以通过操作内容日志恢复数据)

分布式存储系统中,是假设机器是不稳定,随时都有可能 down 掉的情况下来设计的。也就是说就算机器 down 掉了,用户写入的数据也不能丢,避免单点故障。为此每一份写入的数据,需要在多个副本中同时存放。例如 zk 节点数据复制,etcd 的数据复制。而复制数据给节点又会带来一致性的问题,例如主节点和从节点数据不一致改如何去同步数据。也会带来可用性的问题,如 leader 节点 down 掉,如何快速选主,恢复数据等。好在已有成熟的理论如 Paxos 协议,ZAB 协议 Raft 协议等做为支撑。

上述就是小编为大家分享的如何实践CAP 一致性协议及应用了,如果刚好有类似的疑惑,不妨参照上述分析进行理解。如果想知道更多相关知识,欢迎关注亿速云行业资讯频道。

推荐阅读:
  1. CAP定理怎么理解
  2. 分析CAP和Paxos共识算法

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

cap

上一篇:SpringCloud之Zuul路径匹配的示例分析

下一篇:怎么通过重启路由的方法切换IP地址

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》