CAP协议以及算法
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了CAP协议以及算法相关的知识,希望对你有一定的参考价值。
参考技术A两阶段提交
Two-phase Commit(2PC):保证一个事务跨越多个节点时保持 ACID 特性;
两类节点:协调者(Coordinator)和参与者(Participants),协调者只有一个,参与者可以有多个。
过程:
需要注意的是,在准备阶段,参与者执行了事务,但是还未提交。只有在提交阶段接收到协调者发来的通知后,才进行提交或者回滚。
存在的问题
Paxos(Lamport):
分布式系统中的节点通信存在两种模型: 共享内存 (Shared memory)和 消息传递 (Messages passing)。
基于消息传递通信模型的分布式系统,不可避免的会发生以下错误:进程可能会慢、被杀死或者重启,消息可能会延迟、丢失、重复,在基础Paxos场景中,先不考虑可能出现消息篡改即 拜占庭错误 的情况。
Paxos算法解决的问题是在一个可能发生上述异常的 分布式系统 中如何就某个值达成一致,保证不论发生以上任何异常,都不会破坏决议的一致性。
主要有三类节点:
过程:
规定一个提议包含两个字段:[n, v],其中 n 为序号(具有唯一性),v 为提议值。
下图演示了两个 Proposer 和三个 Acceptor 的系统中运行该算法的初始过程,每个 Proposer 都会向所有 Acceptor 发送提议请求。
当 Acceptor 接收到一个提议请求,包含的提议为 [n1, v1],并且之前还未接收过提议请求,那么发送一个提议响应,设置当前接收到的提议为 [n1, v1],并且保证以后不会再接受序号小于 n1 的提议。
如下图,Acceptor X 在收到 [n=2, v=8] 的提议请求时,由于之前没有接收过提议,因此就发送一个 [no previous] 的提议响应,并且设置当前接收到的提议为 [n=2, v=8],并且保证以后不会再接受序号小于 2 的提议。其它的 Acceptor 类似。
如果 Acceptor 接受到一个提议请求,包含的提议为 [n2, v2],并且之前已经接收过提议 [n1, v1]。如果 n1 > n2,那么就丢弃该提议请求;否则,发送提议响应,该提议响应包含之前已经接收过的提议 [n1, v1],设置当前接收到的提议为 [n2, v2],并且保证以后不会再接受序号小于 n2 的提议。
如下图,Acceptor Z 收到 Proposer A 发来的 [n=2, v=8] 的提议请求,由于之前已经接收过 [n=4, v=5] 的提议,并且 n > 2,因此就抛弃该提议请求;Acceptor X 收到 Proposer B 发来的 [n=4, v=5] 的提议请求,因为之前接收到的提议为 [n=2, v=8],并且 2 <= 4,因此就发送 [n=2, v=8] 的提议响应,设置当前接收到的提议为 [n=4, v=5],并且保证以后不会再接受序号小于 4 的提议。Acceptor Y 类似。
当一个 Proposer 接收到超过一半 Acceptor 的提议响应时,就可以发送接受请求。
Proposer A 接受到两个提议响应之后,就发送 [n=2, v=8] 接受请求。该接受请求会被所有 Acceptor 丢弃,因为此时所有 Acceptor 都保证不接受序号小于 4 的提议。
Proposer B 过后也收到了两个提议响应,因此也开始发送接受请求。需要注意的是,接受请求的 v 需要取它收到的最大 v 值,也就是 8。因此它发送 [n=4, v=8] 的接受请求。
Acceptor 接收到接受请求时,如果序号大于等于该 Acceptor 承诺的最小序号,那么就发送通知给所有的 Learner。当 Learner 发现有大多数的 Acceptor 接收了某个提议,那么该提议的提议值就被 Paxos 选择出来。
Raft(14年): 简化,更容易理解,也更容易实现。
引入主节点,通过竞选。
节点类型:Follower、Candidate 和 Leader
Leader 会周期性的发送心跳包给 Follower。每个 Follower 都设置了一个随机的竞选超时时间,一般为 150ms~300ms,如果在这个时间内没有收到 Leader 的心跳包,就会变成 Candidate,进入竞选阶段。
流程:
① 下图表示一个分布式系统的最初阶段,此时只有 Follower,没有 Leader。Follower A 等待一个随机的竞选超时时间之后,没收到 Leader 发来的心跳包,因此进入竞选阶段。
② 此时 A 发送投票请求给其它所有节点。
③ 其它节点会对请求进行回复,如果超过一半的节点回复了,那么该 Candidate 就会变成 Leader。
④ 之后 Leader 会周期性地发送心跳包给 Follower,Follower 接收到心跳包,会重新开始计时。
① 如果有多个 Follower 成为 Candidate,并且所获得票数相同,那么就需要重新开始投票,例如下图中 Candidate B 和 Candidate D 都获得两票,因此需要重新开始投票。
② 当重新开始投票时,由于每个节点设置的随机竞选超时时间不同,因此能下一次再次出现多个 Candidate 并获得同样票数的概率很低。
① 来自客户端的修改都会被传入 Leader。注意该修改还未被提交,只是写入日志中。
② Leader 会把修改复制到所有 Follower。
③ Leader 会等待大多数的 Follower 也进行了修改,然后才将修改提交。
④ 此时 Leader 会通知的所有 Follower 让它们也提交修改,此时所有节点的值达成一致。
Raft协议
分布式领域CAP理论说明了一致性,可用性,分区容错性只可同时满足两个,不可能同时满足。这篇我们讲讲一致性协议的一个实现:Raft一致性协议。一致性算法允许相关联的一组机器在少量成员出现错误(机器宕机等)的情况下依然保证工作。比较著名的一致性算法是Paxos,但是Paxos是一种负责的算法,据说当时没有几个人能理解,而且作者本身也没有从算法层面进行证明,作者只是进行了阐述,直到20实际90年代Paxos作者才重新发表论文证明Paxos算法的正确性。Raft也是一种一致性算法,算法保证了跟Paxos一样的结果,但是相比Paxos更容易理解,因此Raft算法更方便实现。
Raft基础概念
在Raft协议中节点的状态分三种:
Leader:集群的主,写入和读取都从leader上进行,定期向其他节点发送心跳。
Candidate:候选人,选主过程中的状态,一个节点可以从Candidate成为leader,也可以成为follower。
Follower:从节点,接受Leader的日志复制请求,更新自己的状态机。
Rafe协议的机制从另一个角度来看,将连续的时间分成了任意长度的周期(term),每个周期的标识是连续递增的term_id。每个周期的开始阶段是Leader elect,当leader被选出,这个周期内所有的client请求由leader处理。Term的概念在raft中是一个逻辑时钟,让节点可以识别过时的信息(比如过时的leader)。每个几点都保存了一个当前term的变量,这个变量在每一次通信中相互交换确认,一个follower收到新的term,则更新自己的term;candidate或者leader发现自己term陈旧,则转换到follower状态;一个节点收到比自己term旧的term请求,则拒绝响应。
图1 term示意
Leader在一个固定时间内向所有follower发送心跳请求,或者向follower发送日志复制请求,follower被动响应leader的请求。Follower也有一个定时器,定时结束前没有收到心跳或者日志复制请求,follower状态转换为candidate,发起新的leader elect过程,向其他节点发送投票请求。Candidate收到大多数节点的同意则转换为leader状态。如果在一个时间内candidate没有收到大多数节点的回应则重新发起选举过程。
当一个节点处于Leader状态时,它处理所有的client请求,直到发现有节点处于比自己的term_id更新的term,自己转换成follower。
图2 节点状态转换
选主过程
Raft使用心跳机制触发选主过程。节点起始状态为follower,在一个时间(election timeout)内收到leader或者candidate请求,将保持follower状态,若在此时间内没有收到其他请求,此节点将认为没有leader,发起选举过程。
发起选主过程的节点首先增加自己的term,并装换成candidate状态,并行向集群其他节点发送投票请求。节点的candidate状态将一直持续直到以下三种请况出现(a)自己赢得选举(b)另一个节点声称自己是leader(c)election timeout后没有成为leader。
candidate得到大多数的投票后成为leader。集群中的每个节点在一个term内只会投票给一个candidate(先到先得原则, 为了保证安全性投票的另一个原则是candidate的term比自己的大,并且比自己已经投过的term大)。在等待投票过程中,如果收到其他请求,并且请求的term至少和自己的term一样大,将自己转换成follower。
Raft使用随机时间长度(大概150-300ms)作为election timeout,使用随机的时间值保证大部分情况下只有一个节点发起选主过程。
日志复制
当成为了leader之后,节点开始回应client的请求(command)。Leader首先将command append到自己的log中,然后并行向其他节点发送AppendEntries请求。如果大多数的节点返回成功,leader将上个log commit。如果follower失败了(follower宕机, 网络丢包等),leader会一直尝试直到该follower成功。
每个Log Entry包含一个状态机命令以及term号,每个log entry还有一个index标识自己的位置。Leader决定什么时候commit一个log entry是安全的,Raft保证了commited log entry是不变的,并且最终会被所有的follower执行。
Leader跟踪所有follower下一个commit log的index,leader的AppendEntries总是将follower的下一个log entry发送。AppendEntries请求总会包含当前log上一个log的term和index,这样的作用是follower会检查上一个term和index处是否有log entry,若没有follower认为当前不应该处理此次AppenEntries请求。在这种情况下leader会将该follower的next commit index回退,直到某次请求成功,然后可以安全的将后面的log entry复制过去。这个机制保证了在leader crash的情况下数据一致性。这个leader保证一致性的机制可以做些改进以减少follower拒绝AppendEntries的次数。但是论文作者认为可能没有必要,因为在实践中失败的情况很少。
安全性保证
Raft限制一个candidate成为leader除非它的log包含了所有已经committed log entry。Candidate会向所有的节点请求投票(RequestVote),这就意味着,每个committed entry必须在至少其中一个节点上committed。如果candidate的log至少比大多数的节点更新或者一样,说明candidate拥有了所有committed entries。请求投票请求(RequestVote)包含了candidate的最后一个log entry的index和term。节点首先比较term,如果请求的term一致再比较index。
Follower和candidate失败的情形
Follower和candidate失败的情况比较简单,之后的所有请求都会失败,但是leader会持续发送相同的请求直到成功,Raft请求具有幂等性,因此多次请求没有任何影响。
时间和可用性
Raft的一个要求是安全性不依赖于时间。Leader选举是raft协议中时间影响较大的地方。可以认为满足一下不等式的系统,使用raft可以选出并维持一个稳定的leader。
broadcastTime << electionTimeout << MTBF
这个不等式中,broadcastTime是一个节点并行发送RPC到每一个节点并收到响应的时间。electionTimeout是前文提到的随机时间值。MTBF是一个节点两失败之间的平均时间。
BroadcastTime需要比electionTimeout小一个数量级,这样leader可以可靠的通过心跳防止follower发起elect过程。ElectionTimeout应该是MTBF小几个数量级。
broadcastTime和MTBF依赖于底层系统,electionTimeout我们可以选择。由于Raft请求会要求持久化,因此根据存储系统的不同broadcastTime可能范围大概是0.5ms-20ms。因此electionTimeout时间值可能为10ms-500ms。
集群节点变更
集群的配置更新过程Raft使用了两阶段提交法,raft称之为 joint consensus。
1, 所有的log被复制到所有的节点上(新旧配置,这里是集群配置作为一个特殊的log entry)
2,任一节点可以被选为leader
3,选出leader和commit的限制:必须是新的大多数和旧的大多数节点
配置变更过程
Leader收到配置变更从Cold到Cnew, leader将这些配置保存在一个log entry中, 使用前述方式复制到所有节点。每个节点收到配置变更的log,会更新自己的配置及时当前log没有commit,每个节点都按照最新的配置运行。这样可以让旧节点知道新节点的存在。同时意味着新节点跟旧节点开始关联,双方不能单方面做出决定。然后leader可以安全的创建一个描述Cnew的log entry并复制到集群,同样的该log也会立即生效即使没有commit。第二个log entry才是“commit”了集群配置的变更,第一个log entry只是知会了Cnew的存在。
这其中有三个问题:
1:新加入一个节点,没有任何log。解决方法是先同步数据再修改配置
2:leader不再新的配置中,在Cnew被提交后,leader会立即转换为follower状态,在此前作为leader时,正常处理client请求,但是在计算majorities时不算自己。
3:被摘掉的节点由于收不到心跳,他们会超时发起election过程。解决方法是集群中的节点在小于election Timeout最小间隔时间内不会响应投票请求。
PS:文章是阅读Raft论文根据理解写的,文中配图来源为Raft论文。点击阅读原文打开Raft论文连接。
以上是关于CAP协议以及算法的主要内容,如果未能解决你的问题,请参考以下文章