分布式一致性算法 raft

文章目录

      • 主节点选举
        • 投票前的准备
        • 开始投票
        • 投票节点收到投票请求的回复
        • 节点收到其他节点的投票请求
      • 主从同步,日志复制
      • 安全性和成员变化

主节点选举

每个节点有三种角色:
follower: 从节点,被动回复leader和candidate的request。
leader: 主节点,处理client的写request
candidate: leader的候选人

follower 会接受 leader 的心跳包,如果没有收到 leader 的心跳包,将会在随机时间之后变成 candidate 开始竞选 leader。同时选举出来的新主节点也是通过发送心跳来让其余的节点从 candidate 状态转换为 follower 的。

投票前的准备

  1. 每个节点有一个固定的ID以在多个节点之间进行识别。

  2. 每个节点有一个任期号 term,从零开始,以后逐渐单向往上递增。服务器重启后需要知道当前的term 才可以正确的跟其它节点交流,所以 term 是必须持久化的.

  3. 每个节点都可以向其他节点发起投票,要求其他的节点选择自己作为主节点。

  4. 为了避免所有节点同时发起投票,每个节点会分配一个随机的选举超时时间 electionTimeout,也就是一个等待时间,等待时间过了之后才可以发起投票。所以每个节点发起投票的时间都是不一样的。这样降低了提示投票导致选票被瓜分的情况。等待时间超时之后继续随机一个等待时间并开始下一轮选举。

开始投票

那么当一个节点的等待时间到了之后,该节点会转换为 Candidate,

  1. 先给自己投一票,并自增terms, 自增 term 表示当前任期内我已经投过票了(投给自己),所以任期结束。重置选举超时计时器 electionTimeout。
  2. 通知其他节点,要求他们选择自己,通知的时候需要带上自己的 term,ID,最新的日志 Index 。

投票节点收到投票请求的回复

有2种情况

  1. 成功了。拿到一个节点的选票。

  2. 失败了。此时要将自己的 term 更新为对方回复的 term。

而每收到一个节点的回复,要判断是不是以下情况:

  1. 当前是否已经获得了半数以上节点的票,是的话赢得选举,成为leader;向其他节点发送心跳,宣誓主权。

  2. 没有获得半数以上节点的票。electionTimeout 时间到了,在超时之后自增当前任期 terms,重新随机一个 electionTimeout 并开始下一个任期的选举;

当然在这个过程中有可能发生:收到其他节点的心跳请求,说明leader已经选出了,那结束选举,自己变成 follower,选举结束;

节点收到其他节点的投票请求

  1. 如果对方的 term 小于自己的,则拒绝投票,并将自己的 term 返回给对方,让投票的人跟上时间(更新自己的term);如果大于,则立刻转为 follower,并返回成功,告诉对方我已经成为你的 follower 了。

  2. 如果1的情况都不符合,说明2者的任期相同。判断当前任期内是否已经投过票了。是的话也要拒绝投票,因为同一任期内不能多次投票。

  3. 如果步骤2中还没有结果,此时再比较对方的数据和自己的哪一个更新(根据日志的最新时间),如果对方没有自己新,也要拒绝投票。

  4. 上述条件都满足,将票投给对方,返回成功。并等待 electionTimeout。

最终一定会有一个节点胜出。
当选出了主节点之后,主节点会跟从节点都保持心跳,从节点每次收到心跳都要重置自己的等待定时器,这样只要主节点跟从节点之间不失联,从节点就永远不会发生选举。
而一旦失联,其余的从节点立刻就根据自己的等待时间 electionTimeout 再次开始选举了。

上述过程中,主节点就是 leader, 从节点就是 follower,从节点失去主节点的心跳开始竞争选举的阶段就是 candidate。

整个过程中有三种RPC:

  1. RequestVote RPC:投票用的RPC
  2. AppendEntries RPC:主从保活的心跳RPC(同时同步也用这个RPC)
  3. InstallSnapshot RPC:主节点给落后太多的从节点发送快照。

整个过程有三个定时器:

  1. BroadcastTime : 主节点定时发送给从节点的心跳定时时间
  2. Election Timeout :从节点等待进行选举的超时时间
  3. MTBT : 指的是单个服务器发生故障的间隔时间的平均数

三个定时器的超时时间应该是:
BroadcastTime << ElectionTimeout << MTBF
心跳时间一定要小于从节点等待的超时时间,从节点每次收到心跳都会重置这个等待时间。

一般BroadcastTime大约为0.5毫秒到20毫秒,ElectionTimeout一般在10ms到500ms之间。大多数服务器的MTBF都在几个月甚至更长。

主从同步,日志复制

leader 选出来之后,如何保证主从之间的同步?
复制状态机通常都是基于复制日志实现的,每个节点都有各自的日志文件,比如在Redis 中是 AOF 文件。
在不同服务器上的不同文件中,每条日志记录都有一个任期号和递增的索引。
如果不同文件的索引和任期号都相同,说明这2个日志一样。也就说明2台服务器上的数据是一致的。

那么会有2种情况:

  1. 正常运行的情况,在主从数据都一致的情况下:
    客户端每发送一个写命令给主节点,主节点会将写命令 append 到主节点的日志文件最后,然后将命令广播给所有的从节点。当所有的从节点都收到这条命令之后,将执行结果返回给客户端。如果这个过程有的从节点因为网络原因没有收到,也会回给客户端,但是对于失败的命令会不断重试。
    因为主机节点拥有所有从节点的最新的日志 index 。当从节点回复了之后就更新其日志 index。

  2. 从节点崩溃很久之后重启,导致的主从数据差异很大:

  3. 主节点崩溃,导致重新选举。

同时主从之间的心跳会不断的发送主节点的log

有一个问题,主节点和从节点之间的日志不同步,主节点领先从节点N条数据,然后主节点挂掉,新的主节点被选中,此时新主的数据其实不是全局最新的。

安全性和成员变化

如果重新选举之后的主节点落后于从节点,从节点多余的数据将会全部抹掉。
如果从节点的日志比主节点少,主节点会减少日志索引,直到找到最终的一致的地方

如果一个跟随者的日志和领导人不一致,那么在下一次的附加日志 RPC 时的一致性检查就会失败。在被跟随者拒绝之后,领导人就会减小 nextIndex 值并进行重试。最终 nextIndex 会在某个位置使得领导人和跟随者的日志达成一致。当这种情况发生,附加日志 RPC 就会成功,这时就会把跟随者冲突的日志条目全部删除并且加上领导人的日志。一旦附加日志 RPC 成功,那么跟随者的日志就会和领导人保持一致,并且在接下来的任期里一直继续保持

https://github.com/maemual/raft-zh_cn/blob/master/raft-zh_cn.md

http://xiaorui.cc/2016/07/08/技术分享-《分布式一致性算法实现原理》/

https://www.infoq.cn/article/raft-paper

你可能感兴趣的:(算法)