Raft协议整理

Posted OshynSong

tags:

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

复制状态机是当前分布式系统中最核心最基础的组件,提供命令在多个节点之间有序复制和执行,当多个节点初始状态一致的时候,保证节点之间状态一致。系统只要多数节点存活就可以正常处理,它允许消息的延迟、丢弃和乱序,但是不允许消息的篡改。

Raft将一致性问题分解成了如下三个子问题

  • Leader election
  • Log replication
  • Safety

Leader Election

三种角色:

  • Leader:接收Client的请求,并进行复制,任何时刻只有一个Leader
  • Follower:被动接收各种RPC请求
  • Candidate:用于选举出一个新的Leader

引入新的概念:term(任期),每个Leader都有自己的任期,任期到了就需要开始新的一轮选举,在每个任期内,可以没有leader,但是不能出现大于两个的leader。

角色转换:





转换说明

raft将整个时间轴按照任期来划分,每个任期都起始于选举,即出现有Candidate开始竞选leader的时候。

  1. 系统刚启动的时候,每个节点都是follower状态,如果节点在follower状态期间,在一个election timeout时间内没有收到来自Leader的消息,则可以假设没有leader,于是启动选举过程,新增自己本地的任期。
  2. 此时节点转换到了Candidate状态,首先当然是投票给自己,并且发送RequestVote RPCs给其他follower,让他们支持自己当leader,此时在收到投票结果后,可能会出现3种结果
    2.1. 获得了大多数的认可,赢得了投票,成为leader
    2.2 发现了别人已经成为leader了或者自己的任期落后于别人的任期,自动转换为follower
    2.3 一个选举周期过去了,也没有赢得竞选,开始新一轮竞选
  • lastLogIndex、lastLogTerm保证leader completeness等safety properties
  • response里的term:返回voter的currentTerm,可用来让candidate更新自己的term



Log Replication

采用日志的方式,究其原因还是为了提高可靠性,像2PC一样,先将要做出的改变写到本地日志,然后再将其复制到其他follower,当一切就续后,最后再执行真正的写操作,将失败的可能性降到了最低,因此一步操作总比多步出错的可能性低。

  • 只有leader会处理client请求
  • 日志只会从leader流向follower
  • 日志由logIndex索引,并记录对应的term

commit :当多数写成功后,该log变成committed log,可以被apply到状态机

follower 根据append entries request中的commit index来commit log

日志由条目组成,按顺序编号。每个条目包括创建时的term(每个格子中的号码)与状态机命令。
当条目被安全的应用到状态机就认为该条目提交
Leader以强制Follower复制其的日志的方式来处理不一致








membership management

每次只允许增删一个节点,可以保证不会出现两个独立的多数派

Safety

// 所有server的原则 Rules for Servers
// 1. 如果commitIndex > lastApplied:则递增lastApplied,应用 log[lastApplied] 到状态机之中
// 2. 如果Rpc请求或回复包括纪元T > currentTerm: 设置currentTerm = T,转换成 follower, 并且设置 votedFor=-1,表示未投票

// rules for Followers
// 回复 candidates与leaders的RPC请求
// 如果选举超时时间达到,并且没有收到来自当前leader或者要求投票的候选者的 AppendEnties RPC调 :转换角色为candidate

// rules for Candidates
// 转换成candidate时,开始一个选举:
// 1. 递增currentTerm;投票给自己;
// 2. 重置election timer;
// 3. 向所有的服务器发送 RequestVote RPC请求
// 如果获取服务器中多数投票:转换成Leader
// 如果收到从新Leader发送的AppendEnties RPC请求:转换成follower
// 如果选举超时时间达到:开始一次新的选举

// rules for Leaders
// 给每个服务器发送初始空的AppendEntires RPCs(heartbeat);指定空闲时间之 后重复该操作以防 election timeouts
// 如果收到来自客户端的命令:将条目插入到本地日志,在条目应用到状态机后回复给客户端
// 如果last log index >= nextIndex for a follower:发送包含开始于nextIndex的日志条目的AppendEnties RPC
// 如果成功:为follower更新nextIndex与matchIndex
// 如果失败是由于日志不一致:递减nextIndex然后重试
// 如果存在以个N满足 N>commitIndex,多数的matchIndex[i] >= N,并且 log[N].term == currentTerm:设置commitIndex = N

// AppendEntries RPC的实现:在回复给RPCs之前需要更新到持久化存储之上
// 有3类用途
// 1. candidate赢得选举的后,宣誓主权
// 2. 保持心跳
// 3. 让follower的日志和自己保持一致
// 接收者的处理逻辑:
// 1. 如果term < currentTerm 则返回false
// 2. 如果日志不包含一个在preLogIndex位置纪元为prevLogTerm的条目,则返回 false
// 该规则是需要保证follower已经包含了leader在PrevLogIndex之前所有的日志了
// 3. 如果一个已存在的条目与新条目冲突(同样的索引但是不同的纪元),则删除现存的该条目与其后的所有条
// 4. 将不在log中的新条目添加到日志之中
// 5. 如果leaderCommit > commitIndex,那么设置 commitIndex = min(leaderCommit,index of last new entry)

// RequestVote RPC 的实现: 由候选者发起用于收集选票
// 1. 如果term < currentTerm 则返回false
// 2. 如果本地的voteFor为空或者为candidateId,
// 并且候选者的日志至少与接受者的日志一样新,则投给其选票
// 怎么定义日志新
// 比较两份日志中最后一条日志条目的索引值和任期号定义谁的日志比较新
// 如果两份日志最后的条目的任期号不同,那么任期号大的日志更加新
// 如果两份日志最后的条目任期号相同,那么日志比较长的那个就更加新。

// 以上所有的规则保证的下面的几个点:
// 1. Election Safety 在一个特定的纪元中最多只有一个Leader会被选举出来
// 2. Leader Append-Only Leader不会在他的日志中覆盖或删除条 ,他只执行添加新的条
// 3. Log Matching:如果两个日志包含了同样index和term的条 ,那么在该index之前的所有条目都是相同的
// 4. Leader Completeness:如果在一个特定的term上提交了一个日志条目,那么该条目将显示在编号较大的纪元的Leader的日志里
// 5. State Machine Safety:如果一个服务器在一个给定的index下应用一个日志条目到他的状态机上,没有其他服务器会在相同index上应用不同的日志条目


以上是关于Raft协议整理的主要内容,如果未能解决你的问题,请参考以下文章

区块基础-RAFT

图解分布式协议-RAFT

Raft算法

raft协议

Raft协议整理

分布式理论 - 一致性协议Raft