raft

共识算法,可以理解为是为了实现分布式一致性协议而产生的一系列流程与规则。当分布在不同地域的节点都按照这套规则进行协商交互之后,最终总能就某个/某些问题得到一致的决策,从而实现分布式系统中不同节点的一致性

有三个子问题,领导者选举,日志复制,安全性

复制状态机目标,一个初始相同的状态机,输入相同的操作日志,能够得到相同的结果,图中2阶段leader生成操作的log并传递给各个follower,follower根据log进行操作得到相同结果

 共识算法的目的就是实现复制状态机

raft简化了服务器的状态,在任何情况下,raft服务器的状态都处于folower,candidate,leader三者之一

 

服务器状态。关注者只响应来自其他服务器的请求。如果一个追随者没有收到任何信息,他就会成为候选人并发起选举。一名候选人如果获得了整群人的多数票,就会成为新的领导人。领导者通常会运作到失败或者到指定时间。

这样raft就将时间分割成任意长度的任期,每个任期中只会有一个leader,每个任期会有一个任期号 

 

领导者选举

心跳机制,leader会周期性的向所有的follower发送心跳,来维持自己的地位,如果follower指定时间内没有收到信息心跳,那就说明当前leader挂了,需要选举新的

选举时更新任期号,转换为candidate状态,然后和其他服务器节点进行通信并进行投票

 投票的时候会发送当前的任期号,id和最后一个日志号,接收到投票请求的服务器会根据任期号的大小和日志是否可靠选择是否投票

如果投票成功,那么candidate会成为新的leader,如果不成功或者投票时间过长,会进入新的投票阶段或者成为fellower

 

日志复制

leader接收到客户端指令后,会将指令作为一个新的条目加入到日志中

日志中包含状态机指令,leader任期号,日志号

日志会从leader发给follower,如果半数以上的follower复制,leader就可以执行命令并将结果返回客户端

 

安全性

 

 

特点:单一的强的leader,leader的选举,集群成员变化

共识算法主要用于复制状态机,状态机计算相同副本,因为复制状态机在有相同的顺序日志的时候得到的结果是相同的,所以这要保证各个状态机的日志一致,结果也就一致

共识算法的任务就是保证复制日志的一致性

共识算法的特性:安全性:非拜占庭条件下是可靠的

可靠性:只要有半数以上的服务器能够工作,那就是正常的

不依赖时间保证日志可靠:依靠leader的顺序和log执行时间表

raft通过半数选举得到leader,leader有全部的权限来管理复制日志,leader从客户端获取命令并复制到folllower中

因此一致性问题转化为三个子问题,leader选举,日志复制,安全性

也就是保证每个时候都只有一个leader,保证leader接收到客户端命令后传送到follower日志是可靠的,每个服务器在相同条件下执行相同的log结果相同

State:

  1. 所有服务器上的持久化状态,在回复RPC之前更新持久化存储

持久化存储是指保存到磁盘上的那些存储方式,就只有三种,currentTerm保证服务器知道当前的节点,保证集群至少有一个人知道最大的节点,从而保证任期不会重复,为了保证同一任期只有一个leader。因为某一任期中log不一定会增加,所以不能仅依靠log来得到当前任期

log是保存了所有应用程序状态,通过重复log操作能够复现当前的应用程序状态

votedFor记录了投票的信息,为了保证不会重复投票导致同投票数超过服务器数,这是为了保证这一时刻只有一个leader

  • currentTerm:服务器知道的最近任期,当服务器启动时初始化为0,单调递增
  • votedFor:当前任期中,该服务器给投过票的candidateId,如果没有则为null
  • log[]:日志条目;每一条包含了状态机指令以及该条目被leader收到时的任期号

  2. 所有服务器上的易失性状态

这两个不需要存储是leader可以根据log和follower log的差距来判断当前commit的log

    • commitIndex:已知被提交的最高日志条目索引号,一开始是0,单调递增
    • lastApplied:应用到状态机的最高日志条目索引号,一开始为0,单调递增

  3. leader上的易失性状态,在选举之后重新初始化

    • nextIndex[]:针对所有的服务器,内容是需要发送给每个服务器下一条日志条目索引号(初始化为leader的最高索引号+1)
    • matchIndex[]:针对所有的服务器,内容是已知要复制到每个服务器上的最高日志条目号,初始化为0,单调递增

leader只会发出两种rpc,一个用于同步日志,一个用于选举leader

AppendEntries RPC,同时leader发起,用来复制日志条目或者发送心跳:

  1. 参数
  • term:leader的任期号
  • leaderId:用来让follower把客户端请求定向到leader
  • prevLogIndex:紧接新条目之前的日志条目索引(当前最大的日志条目索引)
  • prevLogTerm:prevLogIndex的任期
  • entries[]:储存的日志条目(如果某条目是空的,它就是心跳;为了提高效率可能会发出不止一条日志)
  • leaderCommit:leader的commitIndex

  2. 结果

    • term:当前任期,用来让leader更新自己
    • success:如果follower包含的日志匹配参数汇总的prevLogIndex和prevLogTerm,返回true

  3. 接收者的实现

    • 如果参数term小于接收者的currentTerm,返回false
    • 如果参数的term和prevLogTerm相等的的日志中不包含prevLogIndex的条目,返回false
    • 现有条目与新条目(索引相同但任期不同)发生冲突,删除当前及以后的所有条目
    • 添加不在日志中的新条目
    • 如果leaderCommit>commitIndex,设置commitIndex=min(leaderCommit, 上一个新条目的索引)

主要作用是发送心跳提醒目前leader还活着,而如果自身的team比follower还小,说明任期过了,自更新为follower

如果同一任期下leader的log小于follower的log,说明任期过了,自更新为follower

如果发送的条目和follower的条目不符。同时不触发上面两点,那follower以leader为准

用于选举leader的rpc

RequestVote RPC,candidate发起来收集投票:

  1. 参数
  • term:candidate的任期号
  • candidateId:发起投票的candidate的ID
  • lastLogIndex:candidate的最高日志条目索引
  • lastLogTerm:candidate的最高日志条目的任期号

  2. 结果

    • term:服务器的当前任期号,让candidate更新自己
    • voteGranted:如果是true,意味着candidate收到了选票

  3. 接收者实现

    • 如果参数中的term<接收者的currentTerm,返回false
    • 如果服务器中的votedFor是null或者参数中的candidateId,而且candidate的日志至少和接收者的日志一样新(up-to-date),获得选票

 

服务器的规则:

  1. 所有的服务器
  • 如果commitIndex>lastApplied:增加lastApplied,应用log[lastApplied]到状态机
  • 如果RPC的请求或者回复中的term T>currentTerm:设置currentTerm=T,转为follower

2. Follower

    • 回复来自candidate和leader的RPC
    • 如果经过了选举超时(election timeout)还没有收到当前leader的AppendEntries或者candidate的投票请求:转为candidate

3. candidate

    • 转为candidate之后,开始选举:
      • 增加currentTerm
      • 为自己投票
      • 重设选举定时器
      • 给所有的服务器发送RequestVote RPC
    • 如果收到大多数服务器的选票:成为leader
    • 如果收到新leader的AppendEntries RPC:转为follower
    • 如果经过了选举超时(选举定时器到达了):开始一个新的选举

4. leader

    • 在选举之前:发送空的AppendEntries RPC(心跳)给所有的服务器,一段时间后重复发送来防止选举超时的发生
    • 如果收到了客户端指令:将新的条目添加到本地日志中,在应用到状态机之后返回结果
    • 如果最大的日志索引>=follower的nextIndex:发送从包含nextIndex开始的日志条目的AppendEntries RPC:
      • 如果成功:更新follower的nextIndex和matchIndex
      • 如果因为日志的不一致导致失败:leader减小nextIndex然后重试
    • 如果存在一个N,N>commitIndex,大部分的matchIndex[i]>=N。而且log[N].term==currentTerm,设置commitIndex=N

 

posted @   纸包鱼  阅读(299)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· 写一个简单的SQL生成工具
· Manus的开源复刻OpenManus初探
点击右上角即可分享
微信分享提示