目录
- Paxos
- Raft
- 节点间是如何通讯的
- 什么是任期与任期编号
- 选举有哪些规则
- 随机超时时间
- Raft日志
- 成员变更
- Nacos中Raft的运用(cp模式)
- Distro
- ZAB协议
- 博客
Paxos
paxos算法是由兰伯特与1990年提出的一个分布式系统的共识算法。分布式系统的共识算法通俗易懂的来说就是让分布式系统中的所有的服务器就一个问题达成一个一致性的方案。而Zookeeper的基础就是paxos算法。
1、首先paxos算法中有三个不同的角色,分别为提案者、接受者、learner
当客户端发来一个写请求后,提案者收到后会发起一个提案(Proposal),每个提案都有一个自己的全局唯一ID,而且这个ID是自增的不会减少。随后这个提案者会将这个提案发送给每个接受者的队列中,接受者从自己的队列中取出这个提案,将这个提案与自己记录的提案的ID进行比较,如果队列中的这个提案的ID比自己记录的ID大的话接受者就会接收这个提案给提案者发送一个ACK,如果发现队列中的ID不大于自己记录的ID的话,就会否决认为这个提案已经被人提交过了,不会给提案者发送ACK。
Zookeeper用的Paxos算法。
Raft
Raft概述 :
Raft 算法属于 Multi-Paxos 算法,它是在兰伯特 Multi-Paxos 思想的基础上,做了一些简化和限制,比如增加了日志必须是连续的,只支持领导者、跟随者和候选人三种状态,在理解和算法实现上都相对容易许多。
除此之外,Raft 算法是现在分布式系统开发首选的共识算法。绝大多数选用 Paxos 算法的系统(比如 Cubby、Spanner)都是在 Raft 算法发布前开发的,当时没得选;而全新的系统大多选择了 Raft 算法(比如 Etcd、Consul、CockroachDB)。
掌握这个算法,可以得心应手地处理绝大部分场景的容错和一致性需求,比如分布式配置系统、分布式 NoSQL 存储等等,轻松突破系统的单机限制。
如果要用一句话概括 Raft 算法,我觉得是这样的:从本质上说,Raft 算法是通过一切以领导者为准
的方式,实现一系列值的共识和各节点日志的一致。这句话比较抽象,我来做个比喻,领导者就是 Raft 算法中的霸道总裁,通过霸道的“一切以我为准”的方式,决定了日志中命令的值,也实现了各节点日志的一致。
Raft成员信息 :
Raft成员,又叫做服务器节点状态:领导者(Leader)、跟随者(Follower)和候选人(Candidate) 3 种状态。
-
跟随者(Follower):就相当于普通群众,默默地接收和处理来自领导者的消息,当等待领导者心跳信息超时的时候,就主动站出来,推荐自己当候选人。
-
候选人(Candidate):候选人将向其他节点发送请求投票(RequestVote)RPC 消息,通知其他节点来投票,如果赢得了大多数选票,就晋升当领导者。
-
领导者(Leader):蛮不讲理的霸道总裁,一切以我为准,平常的主要工作内容就是 3 部分,处理写请求、管理日志复制和不断地发送心跳信息,通知其他节点“我是领导者,我还活着,你们现在不要发起新的选举,找个新领导者来替代我。
Raft 算法是强领导者模型,集群中只能有一个“霸道总裁”。
选举领导者的过程:
假设现有三个节点A、B、C
-
首先,在初始状态下,集群中所有的节点都是跟随者的状态。Raft 算法实现了随机超时时间的特性。也就是说,每个节点等待领导者节点心跳信息的超时时间间隔是随机的。集群中没有领导者时,而节点 A 的等待超时时间最小(150ms),它会最先因为没有等到领导者的心跳信息,发生超时。
-
这个时候,节点 A 就增加自己的任期编号(初始为0,每次选举加1),并推举自己为候选人,先给自己投上一张选票,然后向其他节点发送请求投票 RPC 消息,请它们选举自己为领导者。
-
如果其他节点接收到候选人 A 的请求投票 RPC 消息,在编号为 1 的这届任期内,也还没有进行过投票,那么它将把选票投给节点 A,并增加自己的任期编号。
-
如果某个候选人在选举超时时间内赢得了大多数的选票(大于集群数的一半),那么它就会成为本届任期内新的领导者。
-
节点 A 当选领导者后,他将周期性地发送心跳消息,通知其他服务器我是领导者,阻止跟随者发起新的选举,篡权。
自此一个简单的选举便完成了,但是在选举过程中还有一些问题需要解决(选举过程四连问):
- 节点间是如何通讯的?
- 什么是任期?
- 选举有哪些规则?
- 随机超时时间又是什么?
节点间是如何通讯的
在 Raft 算法中,服务器节点间的沟通联络采用的是远程过程调用(RPC),在领导者选举中,需要用到这样两类的 RPC:
-
请求投票(RequestVote)RPC,是由候选人在选举期间发起,通知各节点进行投票;
-
日志复制(AppendEntries)RPC,是由领导者发起,用来复制日志和提供心跳消息。
我想强调的是,日志复制 RPC 只能由领导者发起,这是实现强领导者模型的关键之一,希望能注意这一点,后续能更好地理解日志复制,理解日志的一致是怎么实现的。
什么是任期与任期编号
议会选举中的领导者是有任期的,领导者任命到期后,要重新开会再次选举。Raft 算法中的领导者也是有任期的,每个任期由单调递增的数字(任期编号)标识,比如节点 A 的任期编号是 1。任期编号是随着选举的举行而变化的,这是在说下面几点。
-
跟随者在等待领导者心跳信息超时后,推举自己为候选人时,会增加自己的任期号,比如节点 A 的当前任期编号为 0,那么在推举自己为候选人时,会将自己的任期编号增加为 1。
-
如果一个服务器节点,发现自己的任期编号比其他节点小,那么它会更新自己的编号到较大的编号值。比如节点 B 的任期编号是 0,当收到来自节点 A 的请求投票 RPC 消息时,因为消息中包含了节点 A 的任期编号,且编号为 1,那么节点 B 将把自己的任期编号更新为 1。
我想强调的是,与现实议会选举中的领导者的任期不同,Raft 算法中的任期不只是时间段,而且任期编号的大小,会影响领导者选举和请求的处理。
-
在 Raft 算法中约定,如果一个候选人或者领导者,发现自己的任期编号比其他节点小,那么它会立即恢复成跟随者状态。比如分区错误恢复后,任期编号为 3 的领导者节点 B,收到来自新领导者的,包含任期编号为 4 的心跳消息,那么节点 B 将立即恢复成跟随者状态。
-
还约定如果一个节点接收到一个包含较小的任期编号值的请求,那么它会直接拒绝这个请求。比如节点 C 的任期编号为 4,收到包含任期编号为 3 的请求投票 RPC 消息,那么它将拒绝这个消息。
在这里,可以看到,Raft 算法中的任期比议会选举中的任期要复杂。同样,在 Raft 算法中,选举规则的内容也会比较多。
选举有哪些规则
在议会选举中,比成员的身份、领导者的任期还要重要的就是选举的规则,比如一人一票、弹劾制度等。“无规矩不成方圆”,在 Raft 算法中,也约定了选举规则,主要有这样几点:
-
领导者周期性地向所有跟随者发送心跳消息(即不包含日志项的日志复制 RPC 消息),通知大家我是领导者,阻止跟随者发起新的选举。
-
如果在指定时间内,跟随者没有接收到来自领导者的消息,那么它就认为当前没有领导者,推举自己为候选人,发起领导者选举。
-
在一次选举中,赢得大多数选票的候选人,将晋升为领导者。
-
在一个任期内,领导者一直都会是领导者,直到它自身出现问题(比如宕机),或者因为网络延迟,其他节点发起一轮新的选举。
-
在一次选举中,每一个服务器节点最多会对一个任期编号投出一张选票,并且按照“先来先服务”的原则进行投票。比如节点 C 的任期编号为 3,先收到了 1 个包含任期编号为 4 的投票请求(来自节点 A),然后又收到了 1 个包含任期编号为 4 的投票请求(来自节点 B)。那么节点 C 将会把唯一一张选票投给节点 A,当再收到节点 B 的投票请求 RPC 消息时,对于编号为 4 的任期,已没有选票可投了。
-
日志完整性高的跟随者(也就是最后一条日志项对应的任期编号值更大,索引号更大),拒绝投票给日志完整性低的候选人。比如节点 B 的任期编号为 3,节点 C 的任期编号是 4,节点 B 的最后一条日志项对应的任期编号为 3,而节点 C 为 2,那么当节点 C 请求节点 B 投票给自己时,节点 B 将拒绝投票。
**总结:**选举是跟随者发起的,推举自己为候选人;大多数选票是指集群成员半数以上的选票;大多数选票规则的目标,是为了保证在一个给定的任期内最多只有一个领导者。
其实在选举中,除了选举规则外,我们还需要避免一些会导致选举失败的情况,比如同一任期内,多个候选人同时发起选举,导致选票被瓜分,选举失败。那么在 Raft 算法中,如何避免这个问题呢?答案就是随机超时时间。
随机超时时间
当集群某个节点在自己的超时等待期间没有收到来自领导者的心跳消息时,
在议会选举中,常出现未达到指定票数,选举无效,需要重新选举的情况。在 Raft 算法的选举中,也存在类似的问题,那它是如何处理选举无效的问题呢?
其实,Raft 算法巧妙地使用随机选举超时时间的方法,把超时时间都分散开来,在大多数情况下只有一个服务器节点先发起选举,而不是同时发起选举,这样就能减少因选票瓜分导致选举失败的情况。
在 Raft 算法中,随机超时时间是有 2 种含义的,这里是很多同学容易理解出错的地方,需要注意一下:
-
跟随者等待领导者心跳信息超时的时间间隔,是随机的;
-
如果候选人在一个随机时间间隔内,没有赢得过半票数,那么选举无效了,然后候选人发起新一轮的选举,也就是说,等待选举超时的时间间隔,是随机的。
Raft总结:
Raft 算法和兰伯特的 Multi-Paxos 不同之处,主要有 2 点。首先,在 Raft 中,不是所有节点都能当选领导者,只有日志较完整的节点(也就是日志完整度不比半数节点低的节点),才能当选领导者;其次,在 Raft 中,日志必须是连续的。
Raft 算法通过任期、领导者心跳消息、随机选举超时时间、先来先服务的投票原则、大多数选票原则等,保证了一个任期只有一位领导,也极大地减少了选举失败的情况。
本质上,Raft 算法以领导者为中心,选举出的领导者,以“一切以我为准”的方式,达成值的共识,和实现各节点日志的一致。
Raft日志
在 Raft 算法中,副本数据是以日志的形式存在的,领导者接收到来自客户端写请求后,处理写请求的过程就是一个复制和应用(Apply)日志项到状态机的过程。
那 Raft 是如何复制日志的呢?又如何实现日志的一致的呢?这些内容是 Raft 中非常核心的内容,首先,咱们先来理解日志,这是你掌握如何复制日志、实现日志一致的基础。
日志组成/font>
副本数据是以日志的形式存在的,日志是由日志项组成,日志项究竟是什么样子呢?
其实,日志项是一种数据格式,它主要包含用户指定的数据,也就是指令(Command),还包含一些附加信息,比如索引值(Log index)、任期编号(Term)。那该怎么理解这些信息呢?
-
指令:一条由客户端请求指定的、状态机需要执行的指令。你可以将指令理解成客户端指定的数据。
-
索引值:日志项对应的整数索引值。它其实就是用来标识日志项的,是一个连续的、单调递增的整数号码。
-
任期编号:创建这条日志项的领导者的任期编号。
从图中可以看到,一届领导者任期,往往有多条日志项。而且日志项的索引值是连续的,这一点需要注意。
日志如何复制
可以把 Raft 的日志复制理解成一个优化后的二阶段提交(将二阶段优化成了一阶段.),减少了一半的往返消息,也就是降低了一半的消息延迟。日志复制的具体过程如下:
-
首先,领导者进入第一阶段,通过日志复制(AppendEntries)RPC 消息,将日志项复制到集群其他节点上。
-
接着,如果领导者接收到大多数的“复制成功”响应后,它将日志项应用到它的状态机,并返回成功给客户端。如果领导者没有接收到大多数的“复制成功”响应,那么就返回错误给客户端。
当一条请求发送给leader时,leader会写一条日志(uncommit),之后复制给follower(uncommit),当过半后,leader会变为committed,那么follower啥时候变为committed,并apply到状态机呢???
领导者将日志项应用到它的状态机,怎么没通知跟随者应用日志项呢?
这是 Raft 中的一个优化,领导者不直接发送消息通知其他节点应用指定日志项。因为领导者的日志复制 RPC 消息或心跳消息,包含了当前最大的,将会被提交(Commit)的日志项索引值。所以通过日志复制 RPC 消息或心跳消息,跟随者就可以知道领导者的日志提交位置信息。
因此,当其他节点接受领导者的心跳消息,或者新的日志复制 RPC 消息后,就会将这条日志项应用到它的状态机。而这个优化,降低了处理客户端请求的延迟,将二阶段提交优化为了一段提交,降低了一半的消息延迟。
为了帮助理解,画了一张过程图,然后再走一遍这个过程,这样可以更加全面地掌握日志复制。
完整复制流程如下:
-
接收到客户端请求后,领导者基于客户端请求中的指令,创建一个新日志项,并附加到本地日志中。
-
领导者通过日志复制 RPC,将新的日志项复制到其他的服务器。
-
当领导者将日志项,成功复制到大多数的服务器上的时候,领导者会将这条日志项应用到它的状态机中。
-
领导者将执行的结果返回给客户端。
-
当跟随者接收到心跳信息,或者新的日志复制 RPC 消息后,如果跟随者发现领导者已经提交了某条日志项,而它还没应用,那么跟随者就将这条日志项应用到本地的状态机中。
不过,这是一个理想状态下的日志复制过程。在实际环境中,复制日志的时候,你可能会遇到进程崩溃、服务器宕机等问题,这些问题会导致日志不一致。那么在这种情况下,Raft 算法是如何处理不一致日志,实现日志的一致的呢?
实现日志的一致
在 Raft 算法中,领导者通过强制跟随者直接复制自己的日志项,处理不一致日志。也就是说,Raft 是通过以领导者的日志为准,来实现各节点日志的一致的。具体有 2 个步骤。
-
首先,领导者通过日志复制 RPC 的一致性检查,找到跟随者节点上,与自己相同日志项的最大索引值。也就是说,这个索引值之前的日志,领导者和跟随者是一致的,之后的日志是不一致的了。
-
然后,领导者强制跟随者更新覆盖的不一致日志项,实现日志的一致。
流程具体图解:
为了方便演示,我们引入 2 个新变量
-
PrevLogEntry:表示当前要复制的日志项,前面一条日志项的索引值。比如在图中,如果领导者将索引值为 8 的日志项发送给跟随者,那么此时 PrevLogEntry 值为 7。
-
PrevLogTerm:表示当前要复制的日志项,前面一条日志项的任期编号,比如在图中,如果领导者将索引值为 8 的日志项发送给跟随者,那么此时 PrevLogTerm 值为 4。
复制流程:
-
领导者通过日志复制 RPC 消息,发送当前最新日志项到跟随者(为了演示方便,假设当前需要复制的日志项是最新的),这个消息的 PrevLogEntry 值为 7,PrevLogTerm 值为 4。
-
如果跟随者在它的日志中,找不到与 PrevLogEntry 值为 7、PrevLogTerm 值为 4 的日志项,也就是说它的日志和领导者的不一致了,那么跟随者就会拒绝接收新的日志项,并返回失败信息给领导者。
-
这时,领导者会递减要复制的日志项的索引值,并发送新的日志项到跟随者,这个消息的 PrevLogEntry 值为 6,PrevLogTerm 值为 3。
-
如果跟随者在它的日志中,找到了 PrevLogEntry 值为 6、PrevLogTerm 值为 3 的日志项,那么日志复制 RPC 返回成功,这样一来,领导者就知道在 PrevLogEntry 值为 6、PrevLogTerm 值为 3 的位置,跟随者的日志项与自己相同。
-
领导者通过日志复制 RPC,复制并更新覆盖该索引值之后的日志项(也就是不一致的日志项),最终实现了集群各节点日志的一致。
从上面步骤中你可以看到,领导者通过日志复制 RPC 一致性检查,找到跟随者节点上与自己相同日志项的最大索引值,然后复制并更新覆盖该索引值之后的日志项,实现了各节点日志的一致。需要你注意的是,跟随者中的不一致日志项会被领导者的日志覆盖,而且领导者从来不会覆盖或者删除自己的日志。
日志总结:
在 Raft 中,副本数据是以日志的形式存在的,其中日志项中的指令表示用户指定的数据。
兰伯特的 Multi-Paxos 不要求日志是连续的,但在 Raft 中日志必须是连续的。而且在 Raft 中,日志不仅是数据的载体,日志的完整性还影响领导者选举的结果。也就是说,日志完整性最高的节点才能当选领导者。
Raft 是通过以领导者的日志为准,来实现日志的一致的。可以看到,值的共识和日志的一致都是由领导者决定的,领导者的唯一性很重要,那么如果我们需要对集群进行扩容或缩容,比如将 3 节点集群扩容为 5 节点集群,这时候是可能同时出现两个领导者的。这是为什么呢?在 Raft 中,又是如何解决这个问题的呢?
日志原文
成员变更
在日常工作中,集群中的服务器数量是会发生变化的。 Raft 的领导者选举,建立在“大多数”的基础之上,那么当成员变更时,集群成员发生了变化,就可能同时存在新旧配置的 2 个“大多数”,出现 2 个领导者,破坏了 Raft 集群的领导者唯一性,影响了集群的运行。
首先理解一个名称:配置。配置是成员变更中一个非常重要的概念,建议你这么理解:它就是在说集群是哪些节点组成的,是集群各节点地址信息的集合。比如节点 A、B、C 组成的集群,那么集群的配置就是[A, B, C]集合。
假设我们有一个由节点 A、B、C 组成的 Raft 集群,现在我们需要增加数据副本数,增加 2 个副本(也就是增加 2 台服务器),扩展为由节点 A、B、C、D、E, 5 个节点组成的新集群:
在这个时候,Raft 算法是如何保障在集群配置变更时,集群能稳定运行,不出现 2 个领导者呢?带着这个问题,我们正式进入今天的学习。
在集群中进行成员变更的最大风险是,可能会同时出现 2 个领导者。比如在进行成员变更时,节点 A、B 和 C 之间发生了分区错误,节点 A、B 组成旧配置中的“大多数”,也就是变更前的 3 节点集群中的“大多数”,那么这时的领导者(节点 A)依旧是领导者。
另一方面,节点 C 和新节点 D、E 组成了新配置的“大多数”,也就是变更后的 5 节点集群中的“大多数”,它们可能会选举出新的领导者(比如节点 C)。那么这时,就出现了同时存在 2 个领导者的情况。
如果出现了 2 个领导者,那么就违背了“领导者的唯一性”的原则,进而影响到集群的稳定运行。
解决办法一: 我们在启动集群时,配置是固定的,不存在成员变更,在这种情况下,Raft 的领导者选举能保证只有一个领导者。也就是说,这时不会出现多个领导者的问题,那我可以先将集群关闭再启动新集群啊。也就是先把节点 A、B、C 组成的集群关闭,然后再启动节点 A、B、C、D、E 组成的新集群。
存在问题:
每次变更都要重启集群,意味着在集群变更期间服务不可用,肯定不行啊,太影响用户体验了。抛弃该方法
解决办法二: 单节点变更
单节点变更,就是通过一次变更一个节点实现成员变更。如果需要变更多个节点,那你需要执行多次单节点变更。比如将 3 节点集群扩容为 5 节点集群,这时你需要执行 2 次单节点变更,先将 3 节点集群变更为 4 节点集群,然后再将 4 节点集群变更为 5 节点集群。下图示例:
回到开篇的思考题,看看如何用单节点变更的方法,解决这个问题。为了演示方便,我们假设节点 A 是领导者。
目前的集群配置为[A, B, C],我们先向集群中加入节点 D,这意味着新配置为[A, B, C, D]。成员变更,是通过这么两步实现的:
-
第一步,领导者(节点 A)向新节点(节点 D)同步数据;
-
第二步,领导者(节点 A)将新配置[A, B, C, D]作为一个日志项,复制到新配置中所有节点(节点 A、B、C、D)上,然后将新配置的日志项应用(Apply)到本地状态机,完成单节点变更。
在变更完成后,现在的集群配置就是[A, B, C, D],我们再向集群中加入节点 E,也就是说,新配置为[A, B, C, D, E]。成员变更的步骤和上面类似:
-
第一步,领导者(节点 A)向新节点(节点 E)同步数据;
-
第二步,领导者(节点 A)将新配置[A, B, C, D, E]作为一个日志项,复制到新配置中的所有节点(A、B、C、D、E)上,然后再将新配置的日志项应用到本地状态机,完成单节点变更。
这样一来,我们就通过一次变更一个节点的方式,完成了成员变更,保证了集群中始终只有一个领导者,而且集群也在稳定运行,持续提供服务。
不管旧的集群配置是怎么组成的,旧配置的“大多数”和新配置的“大多数”都会有一个节点是重叠的。 也就是说,不会同时存在旧配置和新配置 2 个“大多数”?
成员变更原文
Nacos中Raft的运用(cp模式)
Nacos集群采用了Raft算法实现。它是一种比较简单的选举算法,用于选举出Nacos集群中最重要的Leader(领导)节点。
在Nacos集群中,每个节点都有以下三种角色中的一种:
- Leader:领导者(主节点),集群中最重要的角色,用于向其他节点下达指令。(一把手,请求接收并转发给其他节点处理,对能力的要求很高)
- Candidate:参选者,参与竞选Leader的节点。(相当于岗位中的副职,等一把手GG的时候可以接替)
- Follower:跟随者,用于接收来自Leader和Candidate的请求并进行处理。(相当于干活的大头兵,负责站队和干活)
Leader选举三个时机:
-
当Nacos节点启动后,还没有产生Leader时启动选举工作。
-
集群成员总量发生变化时重新选举。
-
当Leader停止服务后重新选举。
数据同步(cp模式):
nacos集群在选举之后,其他节点需要从leader节点同步数据,leader会在心跳间隔时间的时候会给其他节点发送数据,心跳间隔大概几百毫秒。
如果客户端发来数据,nacos集群两阶段提交;首先客户端发送的请求需要转交给leader处理,leader第一步将数据写入到文件中,源码中raftCore中onPublish方法,然后调用write,写入到nacos目录中/data/naming/data中,然后发送数据给从节点,从节点收到数据后,也存入文件中,并且清掉自己的旧数据;然后发送ack给leader确认已经写入文件,然后leader再往自己本地缓存写一份,然后发送commit提交请求给从节点,从节点收到leader消息后也会写入缓存然后进行commit处理。leader只要保证半数从节点写成功就返回给客户端,保证数据的一致性。
如果某节点挂了,则等节点恢复之后,等心跳的时候leader发送数据同步给该节点。
如果leader挂了,则需要重新选举,此时,所有节点又进入休眠,等休眠起来在发送选举投票,半数投票原则。
Raft动画演示
动画解析
Raft原文-图解Raft选举
Raft详细解析
Raft日志
Nacos选举示例
Nacos下的Raft
Distro
Distro协议。Distro是阿里巴巴的私有协议,是Nacos在AP下的协议。Distro 协议被定位为 临时数据的一致性协议 :该类型协议, 不需要把数据存储到磁盘或者数据库 ,因为临时数据通常和服务器保持一个session会话, 该会话只要存在,数据就不会丢失 。
Distro 协议保证写必须永远是成功的,即使可能会发生网络分区。当网络恢复时,把各数据分片的数据进行合并。
Distro 协议具有以下特点:
- 专门为了注册中心而创造出的协议;
- 客户端与服务端有两个重要的交互,服务注册与心跳发送;
- 客户端以服务为维度向服务端注册,注册后每隔一段时间向服务端发送一次心跳,心跳包需要带上注册服务的全部信息,在- 客户端看来,服务端节点对等,所以请求的节点是随机的;
- 客户端请求失败则换一个节点重新发送请求;
- 服务端节点都存储所有数据,但每个节点只负责其中一部分服务,在接收到客户端的“写”(注册、心跳、下线等)请求后,- 服务端节点判断请求的服务是否为自己负责,如果是,则处理,否则交由负责的节点处理;
- 每个服务端节点主动发送健康检查到其他节点,响应的节点被该节点视为健康节点;
- 服务端在接收到客户端的服务心跳后,如果该服务不存在,则将该心跳请求当做注册请求来处理;
- 服务端如果长时间未收到客户端心跳,则下线该服务;
- 负责的节点在接收到服务注册、服务心跳等写请求后将数据写入后即返回,后台异步地将数据同步给其他节点;
- 节点在收到读请求后直接从本机获取后返回,无论数据是否为最新。
ZAB协议
Zab协议 的全称是Zookeeper Atomic Broadcast(Zookeeper原子广播)。Zookeeper 是通过 Zab 协议来保证分布式事务的最终一致性。
Zab协议是为分布式协调服务Zookeeper专门设计的一种支持崩溃恢复的原子广播协议,是Zookeeper保证数据一致性的核心算法。Zab借鉴了Paxos算法,但又不像Paxos那样,是一种通用的分布式一致性算法。它是特别为Zookeeper设计的支持崩溃恢复的原子广播协议。
在Zookeeper中主要依赖Zab协议来实现数据一致性,基于该协议,zk实现了一种主备模型(即Leader和Follower模型)的系统架构来保证集群中各个副本之间数据的一致性。这里的主备系统架构模型,就是指只有一台客户端(Leader)负责处理外部的写事务请求,然后Leader客户端将数据同步到其他Follower节点。
Zookeeper 客户端会随机的链接到 zookeeper 集群中的一个节点,如果是读请求,就直接从当前节点中读取数据;如果是写请求,那么节点就会向 Leader 提交事务,Leader 接收到事务提交,会广播该事务,只要超过半数节点写入成功,该事务就会被提交。
模型图:
1)Zab 协议需要确保那些已经在 Leader 服务器上提交(Commit)的事务最终被所有的服务器提交。
2)Zab 协议需要确保丢弃那些只在 Leader 上被提出而没有被提交的事务。
Zab 协议实现的作用
- 使用一个单一的主进程(Leader)来接收并处理客户端的事务请求(也就是写请求),并采用了Zab的原子广播协议,将服务器数据的状态变更以事务proposal(事务提议)的形式广播到所有的副本(Follower)进程上去。
- 保证一个全局的变更序列被顺序引用。
Zookeeper是一个树形结构,很多操作都要先检查才能确定是否可以执行,比如P1的事务t1可能是创建节点"/a",t2可能是创建节点"/a/bb",只有先创建了父节点"/a",才能创建子节点"/a/b"。
为了保证这一点,Zab要保证同一个Leader发起的事务要按顺序被apply,同时还要保证只有先前Leader的事务被apply之后,新选举出来的Leader才能再次发起事务。 - 当主进程出现异常的时候,整个zk集群依旧能正常工作。
ZAB 支持 3 种成员身份(领导者、跟随者、观察者)。
-
领导者(Leader): 作为主(Primary)节点,在同一时间集群只会有一个领导者。需要你注意的是,所有的写请求都必须在领导者节点上执行。
-
跟随者(Follower):作为备份(Backup)节点, 集群可以有多个跟随者,它们会响应领导者的心跳,并参与领导者选举和提案提交的投票。需要你注意的是,跟随者可以直接处理并响应来自客户端的读请求,但对于写请求,跟随者需要将它转发给领导者处理。
-
观察者(Observer):作为备份(Backup)节点,类似跟随者,但是没有投票权,也就是说,观察者不参与领导者选举和提案提交的投票。可以对比着 Paxos 中的学习者来理解。
需要注意的是,虽然 ZAB 支持 3 种成员身份,但是它定义了 4 种成员状态。
-
LOOKING:选举状态,该状态下的节点认为当前集群中没有领导者,会发起领导者选举。
-
FOLLOWING :跟随者状态,意味着当前节点是跟随者。
-
LEADING :领导者状态,意味着当前节点是领导者。
-
OBSERVING: 观察者状态,意味着当前节点是观察者。
为什么多了一种成员状态呢?这是因为 ZAB 支持领导者选举,在选举过程中,涉及了一个过渡状态(也就是选举状态)。
zab原理解析-知乎
ZAB协议概述
共识算法 ZAB
博客
Nacos下的共识算法/AP+CP原理