常见选主机制(zab、raft)

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了常见选主机制(zab、raft)相关的知识,希望对你有一定的参考价值。

参考技术A Zab协议有四个阶段

    Leader election

    Discovery (E#epoch establish)

    Synchronization (5X#sync with followers)

    Broadcast

比如3个节点选举leader:编号为1、2、3。1先启动,选择自己为leader,然后2启动首先也选择自己为leader,由于1,2都没过半,选择编号大的为leader,所以1、2都选择2为leader,然后3启动发现1,2已经协商好且数量过半,于是3也选择2为leader,leader选举结束。

1.定义

    Raft是工程上使用较为广泛的强一致、去中心化、高可用的分布式协议。是一种共识算法。

2.选主流程

    Raft算法定义了三种角色:

        Leader(领导者)

        Follower(追随者)

        Candidate(候选者)

    初始状态下集群所有节点都是Follower,每一个节点都有自己的计时器,当计时达到了超时时间(Election Timeout),该节点会转变为Candidate,成为Candidate的节点,会首先给自己投票,然后向集群中其他所有的节点发起请求,要求大家都给自己投票,其他收到投票请求且还未投票的Follower节点会向发起者投票,发起者收到反馈通知后,票数增加。

    得票数超过了集群节点数量的一半,该节点晋升为Leader节点。Leader节点会立刻向其他节点发出通知,告诉大家自己是领导者。收到通知的节点全部变为Follower,并且各自的计时器清零。如果发现有节点票数相同,那么会随机休息一段时间,再次发起投票,由于时间是随机,所以避免再次票数相同的概率

    选出 Leader 后,Leader 通过定期向所有 Follower 发送心跳信息维持其统治。若 Follower 一段时间未收到 Leader 的心跳则认为 Leader 可能已经挂了再次发起选主过程

3.数据同步

    1.由客户端提交数据到Leader节点

    2.Leader 节点接收到的数据处于未提交状态(Uncommitted)

    3.由Leader节点把数据复制到集群内所有的Follower节点

    4.Follower节点们接收到复制的数据,会反馈给Leader节点

    5.如果Leader节点接收到超过半数的Follower反馈,表明复制成功,向 Client 确认数据已接收

    6.一旦向 Client 发出数据接收 Ack 响应后,表明此时数据状态进入已提交(Committed)

    7.再由Leader节点通知集群内所有的Follower节点提交数据,从而完成数据同步流程

3.1 脑裂及解决方法

1. Quorums(法定人数), 过半机制 :默认方式, 比如3个节点的集群,Quorums = 2, 也就是说集群可以容忍1个节点失效,这时候还能选举出1个leader,集群还可用。比如4个节点的集群,它的Quorums = 3,Quorums要超过3,相当于集群的容忍度还是1,如果2个节点失效,那么整个集群还是无效的。

2. 冗余通信:采用多种通信方式,避免因一种通信终端导致脑裂。

    3.智能磁盘锁: 正在服务一方锁住共享磁盘,"裂脑"发生时,让对方完全"抢不走"共享磁盘资源。但使用锁磁盘也会有一个不小的问题,如果占用共享盘的一方不主动"解锁",另一方就永远得不到共享磁盘。现实中假如服务节点突然死机或崩溃,就不可能执行解锁命令。后备节点也就接管不了共享资源和应用服务。于是有人在HA中设计了"智能"锁。即正在服务的一方只在发现心跳线全部断开(察觉不到对端)时才启用磁盘锁。平时就不上锁了。

4. 设置仲裁机制: 例如设置参考IP(如网关IP),当心跳线完全断开时,2个节点都各自ping一下 参考IP,不通则表明断点就出在本端,不仅"心跳"、还兼对外"服务"的本端网络链路断了,即使启动(或继续)应用服务也没有用了,那就主动放弃竞争,让能够ping通参考IP的一端去起服务。更保险一些,ping不通参考IP的一方干脆就自我重启,以彻底释放有可能还占用着的那些共享资源。

3.2 羊群效应

    宕机的那个Broker上的Partition比较多, 会造成多个Watch被触发,造成集群内大量的调整,导致大量网络阻塞

3.3 zookeeper负载过重

    每个Replica都要为此在ZooKeeper上注册一个Watch,当集群规模增加到几千个Partition时  ZooKeeper负载会过重

阿里技面之raft如何选主

背景

一面在考查技术基础首先被问到过raft协议如何选主?主挂了选出的新主如何重新进行日志复制?
raft协议一直都是分布式系统一致性的难点,能讲清楚很不容易,
下面我们就通过现场还原的方式讲讲该如何回答这两个问题的。

现场还原

Q1面试官:那你先说一下raft协议是如何选主吧?

A1 我:为了保证数据一致性,最好的方式是唯一节点去读,唯一节点去写。这样的数据肯定是一致的。但是分布式架构显然不可能一个节点处理。因此raft提出在集群的所有节点中需要有一个节点来充当这个唯一节点,在一段时间内,只有这一个节点负责读写数据,然后其他节点同步数据。这个唯一的节点就叫leader节点,其他负责同步数据的节点叫follower节点,在集群中,还会有其他状态的节点,例如candidate节点,这种节点只有在选主的时候才会涉及。所以三种角色定义如下:

1.跟随者(follower):相当于普通群众,默默地接收和处理来自领导者的消息,当领导者心跳超时的时候,它就会主动站出来,推荐自己当候选人。

2.候选人(candidate):将向其它节点发送请求投票(RequestVote)RPC消息,通知其他节点来投票,如果赢得了大多数选票,就晋升位领导者。

3.领导者(leader):不讲道理的霸道总裁,平常的主要工作内容就是3部分,处理写请求、管理日志复制和不断地发送心跳信息,通知其它结点“我是领导者,不要发起新的选举”。

节点中的主选举和现实生活中的选举十分类似,就是投票,集群中获票数最多的那个,就是leaderer节点。所以为防止出现平局,一般在部署节点的时候,会将节点个数设置为奇数(2n+1)。

那么,这些节点该如何选举呢?我们来看下面这个例子

在集群中,有三个节点A、B、C[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

初始状态下,所有节点都是follower,没有leader。过了一段时间A变成了candidate开始发起选举,凭啥A能变成candidate呢?因为如上图可以看到,集群中每个节点都有一个election timeout,它表示选举超时时间。每个节点等待leader节点心跳信息的election timeout都是150ms~300ms的一个随机数,每当节点的election timeout到了就会触发自己成为candidate。A的选举超时时间先到了(150ms),它会最先因为没有等到leader的心跳信息发生超时。
在这里,只有candidate状态的节点才可以竞选成为leader。B、C这两个follower是没有资格变为leader的。除此以外,每个节点中还有一个字段叫任期编号(term),这个term是一个全局的、连续递增的整数。每进行一次选举term值加1,如果candidate赢得选举,它会称为leader直到任期结束。此时A触发了选举,它就增加自己的term,并推举自己为候选人,先给自己投上一票,然后向其他节点发送请求投票的RPC消息,请它们选举自己为领导者。

当B、C接收到A的请求投票RPC消息后,B、C开始投票,此时只有A一个candidate,在term为1的这届任期内目前也还没有进行过投票,那么它将把选票投给节点A,并增加自己的term。

如果candidate在选举超时时间内赢得了大多数选票,它将成为本届任期内新的leaderer。节点A当选leader后,为了巩固自己的“统治”,防止任期之间其它节点因为自身的election timeout而触发选举。它将周期性地发送RPC心跳信息。B和C收到心跳消息后,会重置election timeout。心跳检测时间很短,要远远小于选举超时时间election timeout。

B和C收到心跳信息后,会发送心跳响应并重置election timeout。

假设A发送的心跳检测信息由于网络原因(延迟、丢包等)没有传送到B、C这两个follower节点。B、C刚好election timeout则触发自身选举。此时C修改自身的任期编号term为2,自身状态变为candidate,并且投了自身一票。发起选举。

这时候C的任期值term设为2大于A的任期值。在Raft协议中但凡收到任期编号值大于自己的节点,都会更改自身节点的term值并切换为follower并重置自己的election timeout,因此,A直接变成follower。

此外,如果一个节点接收到一个包含较小的term值的请求,那么它会直接拒绝这个要求。举个例子,假如节点C的term为4,当收到包含term为3的请求投票RPC消息,那么节点C会拒绝这个消息。

Q2面试官:回答得挺好,那么刚才你提到部署的时候节点数一般是奇数,那么现在就是偶数个节点怎么办?

A2 我:假如现在变成了四个节点A、B、C、D。A和B同时进入了candidate状态并开始发起选举。如果A和B中任意一个获得了超过半数以上的多数票则变为leader。如果它们两个经过一次选举后得到的票数相同或者都没有超过半数则宣告选举失败并结束。等待A和B这两个节点中任意一个节点的election timeout超时,然后发起新一轮选举。A、B票数相同,则之后等待哪个先超时。假如A先超时,则A发起选举,由于A的term显然是最大的,则A会选为leader。

Q3面试官:那再讲下一般是如何日志复制的呢?

A3我:raft除了可以实现一系列值的共识以外还能够达成各节点日志一致。
副本是以日志的形式存在,日志由日志项组成。日志就好比是数据表,日志项(log entry)就好比是表里面的记录。日志项是一种数据格式,它主要包括用户指定的数据,也就是指令(command)。此外还包含一些附加信息,比如索引值,任期编号。如下图所示

指令:客户端请求指定的,状态机需要执行的指令。

索引值:日志项对应的整数索引值。用来标识日志项,是一个连续的、单调递增的整数号码。

任期编号:创建这条日志项的leader的任期编号。

那么如何进行日志复制呢?我们举个例子

  1. 首先,接收到客户端的指令后,为该指令创建一个新日志项并追加到本地日志文件中。

  1. 这里A作为leader,向B、C发送日志复制(Append Entries RPC)请求。这个RPC消息是根据节点不同而消息也不同的。

  1. 当B、C接收到消息后,把命令成功写入本地日志文件后向A发送信息。

  1. A收到大多数follower的回复信息后可以确定一个日志项被safely replicated了,将应用这条日志项到状态机中,将执行结果返回给客户端。如果某个follower遇到异常情况(宕机或者网络丢包),则会一直给这个follower发送日志复制RPC请求。

  2. A向B、C这两个follower发送commit成功的通知

  1. B和C收到日志复制RPC消息后,它们先commit自己的日志数据,然后再通知A自己已更新结束。

到此整个数据同步也就结束了,不过这是一个理想状态下的日志复制过程。在实际环境中,复制日志的时候,你可能会遇到进程崩溃、服务器宕机等问题,这些问题会导致日志不一致。那么在这种情况下

每个节点都有两个索引,一个是当前提交索引值commitIndex,一个是目前数据的最后一行索引值lastAppliedIndex,它表示最后一个应用到状态机的log序号。

leader节点除了需要存储自身节点的commitIndex和lastAppliedIndex之外,还需要知道每个follower的情况。因此,leader多了一张表,这张表记录了所有follower 的存储情况。表有两个属性,一个是nextIndex,每个follower维护一个nextIndex,表示leaderer给每一个follower发送的下一条日志项的索引。另一个是matchIndex记录的是leaderer已知的follower节点的数据。

Q4面试官:换成新主后如何日志达到一致

A4我:首先,leader通过发送日志复制RPC的一致性检查找到follower与自己相同日志项的最大索引值。

然后,leader强制复制follower更新覆盖的不一致日志项。

leader给follower发送日志复制信息中带着(PrevLogTerm, PrevLogEntry)。PrevLogEntry表示当前复制日志项前一条的索引值。
lPrevLogTerm表示当前要复制的日志项前面一条的任期编号。

比如,下图中leader将索引值为8的日志项发送给follower,那么PrevLogTerm值为4,PrevLogEntry为7。

  1. leader通过日志复制RPC消息发送当前日志项到follower。比如上图PrevLogEntry值为7,PrevLogTerm值为4。

  2. 如果follower在它的日志中,找不到与PrevLogEntry为7,PrecvLogTerm值为4的日志项表示它和leader的日志文件不一致了,那么follower拒绝接受新的日志项,并返回失败信息给leader。

  3. leader递减要复制的日志项的索引值,并发送日志项到follower,既值为(6,3)的日志项。

  4. 如果follower在它的日志中找到了PrevLogEntry值为6,PrevLogTerm值为3的日志项,日志复制RPC返回成功,这样leader就知道在PrevLogEntry值为6,PrevLogTerm值为3的位置,follower和leader的日志项相同。

  5. leader发送日志复制RPC消息给follower,follower复制并覆盖该索引之后的日志项,最终实现了集群各节点日志的一致。

以上是关于常见选主机制(zab、raft)的主要内容,如果未能解决你的问题,请参考以下文章

简述 zookeeper 基于 Zab 协议实现选主及事务提交

raft 和 zab协议

《分布式一致性协议raft简介》

分布式系统选主怎么玩

分布式系统选主怎么玩

Raft实战——选主