浅谈Raft算法

1.什么是Raft

        Raft是一种共识算法,旨在使其易于理解。它在容错和性能上与Paxos等效。不同之处在于它被分解为相对独立的子问题,并且干净地解决了实用系统所需的所有主要部分。。我们希望Raft能够使更多的受众获得共识,并且这个更广泛的受众将能够开发出比当今更高质量的基于共识的系统。

        其最为显著的特点就是强化了Leader的作用,来减少了处理一致性问题时的多状态的复杂性。

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

Raft implements consensus by first electing a distinguished leader, then giving the leader complete responsibility for managing the replicated log. The leader accepts log entries from clients, replicates them on other servers, and tells servers when it is safe to apply log entries to their state machines. A leader can fail or become disconnected from the other servers, in which case a new leader is elected.

        上面的引文对Raft协议的工作原理进行了高度的概括: Raft会先选举出leader,leader完全负责replicated log的管理。leader负责接受所有客户端更新请求,然后复制到follower节点,并在“安全”的时候执行这些请求。如果leader故障,followers会重新选举出新的leader。

2.leader election

        Raft协议中,一个节点在任意时刻处于一下三个状态之一:

  • leader
  • follower
  • candidate

         系统中只能有一个leader,如果一段时间内,发现没有leader,则大家通过选举投票选出leader,leader 会不停的给follower发送心跳消息,表示自己存活,如果leader故障,那么follower 会重新选举出新leader。

2.1.term

        term以 election(选举)开始,然后就是一段时间或长或短的稳定工作期(normal Operation)。term是递增的,这就充当了逻辑时钟的概念。另外,term 3展示了一种没有选举出leader就结束的情况,然后会发起新的选举,后面会解释这种split vote的情况。

2.2.选举过程详解:

        选举过程:

  • 增加节点本地的 current term, 切换待candidate 状态
  • 投自己一票
  • 并行给其他节点发送 RequestVote RPCs
  • 等待其他节点的回复

在选举过程中,根据来自其他节点的消息,可能出现三种情况:

  1. 收到majority(大多数)的投票(含自己的一票),则赢得选举,成为leader
  2. 被告知别人已经当选为,那么自行切换换为follower
  3. 一段时间内没有收到majority(大多数)投票,则保持candidate(候选者),重新发起选举

        第一种情况,赢得选举后,新leader 会立刻给所有节点发消息,广而告之,避免其余节点出发新的选举。在这里我们回到投票者的视角,投票者如何决定是否给一个选举者投票的的呢? 有以下约束:

  • 在任意任期内,每个节点最多只能投一票
  • 候选人知道的信息不能比自己的少
  • first-come-first-served 先来先得

        第二种情况:比如有三个节点A B C。A B同时发起选举,而A的选举消息先到达C,C给A投了一票,当B的消息到达C时,已经不能满足上面提到的第一个约束,即C不会给B投票,而A和B显然都不会给对方投票。A胜出之后,会给B,C发心跳消息,节点B发现节点A的term不低于自己的term,知道有已经有Leader了,于是转换成follower。

        第三种情况:没有任何节点获得 majority(大多数)投票--平票。 如果出现平票的情况,那么系统是不可用的(没有leader是不能处理客户端写请求的)。因此Raft 引入了 randomized election timeouts来尽量避免平票的情况,同时,leader-based共识算法中,节点的数目都是奇数个,尽量保证majority 的出现。


3.log replication

3.1.Replicated state machines (复制状态机)

        共识算法的实现一般是基于复制状态机(replication state machines)。

        复制状态机 :相同的初识状态 + 相同的输入 = 相同的结束状态。 

If two identical, deterministic processes begin in the same state and get the same inputs in the same order, they will produce the same output and end in the same state.

        引文中有一个很重要的词deterministic,就是说不同节点要以相同且确定性的函数来处理输入,而不要引入一下不确定的值,比如本地时间等。如何保证所有节点 get the same inputs in the same order,使用replicated log是一个很不错的注意,log具有持久化、保序的特点,是大多数分布式系统的基石。

  因此,可以这么说,在raft中,leader将客户端请求(command)封装到一个个log entry,将这些log entries复制(replicate)到所有follower节点,然后大家按相同顺序应用(apply)log entry中的command,则状态肯定是一致的。

3.2.请求完整流程

        当系统(leader)收到一个来自客户端的写请求,到返回给客户端,整个过程从leader的视角来看会经历以下步骤:

  • leader append log entry
  • leader issue AppendEntries RPC in parallel
  • leader wait for majority response
  • leader apply entry to state machine
  • leader reply to client
  • leader notify follower apply log

        可以看到日志的提交过程有点类似两阶段提交(2PC),不过2PC的区别在于,leader只需要大多数(majority)节点恢复即可,这样只要超过一半节点处于工作状态则系统就是可用的。

        那么日志在每个节点上是什么样子的呢?

         可以看出,logs由顺序编号的log entry组成 ,每个log entry除了包含command,还包含产生该log entry时的leader term。从上图可以看到,五个节点的日志并不完全一致,raft算法为了保证高可用,并不是强一致性,而是最终一致性,leader会不断尝试给follower发log entries,直到所有节点的log entries都相同。

        在上面的流程中,leader只需要日志被复制到大多数节点即可向客户端返回,一旦向客户端返回成功消息,那么系统就必须保证log(其实是log所包含的command)在任何异常的情况下都不会发生回滚。这里有两个词:commit(committed),apply(applied),前者是指日志被复制到了大多数节点后日志的状态;而后者则是节点将日志应用到状态机,真正影响到节点状态。

3.3.safety

        在上面提到只要日志被复制到majority节点,就能保证不会被回滚,即使在各种异常情况下,这根leader election提到的选举约束有关。
        这一节主要讨论Raft协议在各种各样的异常情况下是如何工作的。 衡量一个分布式算法,有许多属性,如:

  • safety:nothing bad happens,
  • liveness: something good eventually happens.

        Raft协议为保证数据的一致性必须遵守的5大原则。

  • Election Safety: at most one leader can be elected in a given term. §5.2
  • Leader Append-Only: a leader never overwrites or deletes entries in its log; it only appends new entries. §5.3
  • Log Matching: if two logs contain an entry with the same index and term, then the logs are identical in all entries up through the given index. §5.3
  • Leader Completeness: if a log entry is committed in a given term, then that entry will be present in the logs of the leaders for all higher-numbered terms. §5.4
  • State Machine Safety: if a server has applied a log entry at a given index to its state machine, no other server will ever apply a different log entry for the same index. §5.4.3

3.3.1.Election safety

        选举安全性, 即对于一个给定的任期号,最多只会有一个领导人被选举出来。在一个复制集中,任何时刻只能有一个leader,系统中同时有多余一个leader, 称之为脑裂(brain spilt),这是非常严重的问题,会导致数据覆盖。 在Raft 中有两点保证了这个属性:

  • 一个节点某一任期内最多只能投一票
  • 只能获得majority 投票的节点才能成为leader。

        因此 某一任期内一定只有一个leader。

3.3.2.Leader Append-Only

        领导人的只附加原则是指所有的信息流都是从领导者流入到跟随者中,这样可以保证领导者自身的数据的一致性,保证了不会出现领导者已经应用的日志被出现更改的情况。

3.3.3.log matching

        日志匹配原则,如果两个日志在相同的索引位置的日志条目的任期号相同,那么我们就认为这个日志从头到这个索引位置之间全部完全相同。 如何做到的只要依赖以下两点:

  • 如果在不同的日志中的两个条目拥有相同的索引和任期号,那么他们存储了相同的指令。
  • 如果在不同的日志中的两个条目拥有相同的索引和任期号,那么他们之前的所有日志条目也全部相同。

        首先,leader在某一term的任意位置只会创建一个 log entry,且log entry是 append-only。

        其次,consustency check, leader 在AppendEntries只包含最新log entry 之前的一个log的 term和index,如果follower对应的term index找不到日志,那么久告诉leader 不一致。

        在没有异常的情况下,log matching是很容易满足的,但如果出现了node crash,情况就会变得复杂。比如下图:

        注意:上图的a-f不是6个follower,而是某个follower可能存在的六个状态。

  leader、follower都可能crash,那么follower维护的日志与leader相比可能出现以下情况

  • 比leader日志少,如上图中的ab
  • 比leader日志多,如上图中的cd
  • 某些位置比leader多,某些日志比leader少,如ef(多少是针对某一任期而言)

  当出现了leader与follower不一致的情况,leader强制follower复制自己的log。

To bring a follower’s log into consistency with its own, the leader must find the latest log entry where the two logs agree, delete any entries in the follower’s log after that point, and send the follower all of the leader’s entries after that point.

        leader会维护一个nextIndex[]数组,记录了leader可以发送每一个follower的log index,初始化为eader最后一个log index加1, 前面也提到,leader选举成功之后会立即给所有follower发送AppendEntries RPC(不包含任何log entry, 也充当心跳消息),那么流程总结为:

s1 leader 初始化nextIndex[x]为 leader最后一个log index + 1
s2 AppendEntries里prevLogTerm prevLogIndex来自 logs[nextIndex[x] - 1]
s3 如果follower判断prevLogIndex位置的log term不等于prevLogTerm,那么返回 False,否则返回True
s4 leader收到follower的回复,如果返回值是False,则nextIndex[x] -= 1, 跳转到s2. 否则
s5 同步nextIndex[x]后的所有log entries

3.3.4.leader completeness vs elcetion restriction

        leader 完整性:如果一个log entry在某个任期没被提交(committed),那么这条日志一定会在所有更高的term的leader的日志里面,这跟leader election、log replication都有关系:

  •  一个日志被复制到大多数节点才算 committed;
  • 一个节点得到大多数节点的投票才能成为leader;

        而节点A给节点B投票的一个重要的前提,B的日志不能比A的日志旧。 上面两点都提到了 majority: commit majority and vote majority。根据Quorum,这两个 majority 一定是有重合,因此被选举出来的 leader 一定包含了最新的committed 的日志。 Raft 与其他协议(Viewstamped Replication、 MongoDB)不同,Raft时钟保证leader包含最新的已经提交的日志,因此leader不会从follower catchup 日志,这大大简化了系统复杂性。

3.3.5.state Machine safety

        状态机安全特性:如果一个领导人已经在给定的索引值位置的日志条目应用到状态机中,那么其他任何的服务器在这个索引位置不会提交一个不同的日志(5.4.3 节)        

        如果节点将某一位置的log entry应用到了状态机,那么其他节点在同一位置不能应用不同的日志。简单点来说,所有节点在同一位置(index in log entries)应该应用同样的日志。但是似乎有某些情况会违背这个原则:

        上图是一个较为复杂的情况。在时刻(a), s1是leader,在term2提交的日志只赋值到了s1 s2两个节点就crash了。在时刻(b), s5成为了term 3的leader,日志只赋值到了s5,然后crash。然后在(c)时刻,s1又成为了term 4的leader,开始赋值日志,于是把term2的日志复制到了s3,此刻,可以看出term2对应的日志已经被复制到了majority,因此是committed,可以被状态机应用。不幸的是,接下来(d)时刻,s1又crash了,s5重新当选,然后将term3的日志复制到所有节点,这就出现了一种奇怪的现象:被复制到大多数节点(或者说可能已经应用)的日志被回滚。

        究其根本,是因为 term4的时候leader s1 在(c)时刻提交了之前term2任期的日志。为了杜绝这种情况:

Raft never commits log entries from previous terms by counting replicas.
Only log entries from the leader’s current term are committed by counting replicas; once an entry from the current term has been committed in this way, then all prior entries are committed indirectly because of the Log Matching Property.

        也就是说:在某个leader选举成功后,不会直接提交前任leader 时期的日志,而是通过提交当前任期的日志的时候,‘顺手’把之前的日志提交了。如果leader 选举之后没有收到客户端的请求呢?在任期来时的时候立即尝试复制、提交一条空 log。

因此 在上图中不会出现(c)的情况,即term4任期的leader s1不会复制term2 的日志到s3。而是如同(e)描述的情况,通过复制-提交term4的日志顺便提交term2的日志。如果term4的日志提交成功,那么term2 的日志也一定提交成功,此时,即使 s1 crash,s5也不会重新当选。


4.Corner case

4.1.srale leader

        Raft保证Election safety,即一个任期内最多只有一个leader,但在网络分割(network partition)的情况下,可能会出现两个leader,但两个leader所处的任期是不同的。如果leader收不到majority节点消息,那么可以自己 stop down,自行转换到follower状态。

4.2.leader crash

        ollower的crash处理方式相对简单,leader只要不停的给follower发消息即可。当leader crash的时候,事情就会变得复杂。 Raft协议强依赖Leader节点的可用性来确保集群数据的一致性。数据的流向只能从leader节点向follower 节点转移。当Client向集群leader节点提交数据后,leader节点接受到数据处于未提交的状态,接着leader节点会并发向所有follower节点复制数据等待接受响应,确保至少集群中超过半数节点已接收导数据后再向client确认数据已接收。一旦向client发出数据接受ACK响应后,表明测试数据状态已经进入提交状态。leader节点再向follower 节点发通知告知该数据状态以提交。

在上述过程中,leader 节点可能在任意时刻carsh。

1.数据到达 Leader节点前:
这个阶段leader挂掉不影响数据一致性
2. 数据到达Leader节点,但是未复制到Follower 节点

这个阶段Leader挂掉,数据属于未提交状态,Client不会接受ACK认为超时失败可安全发起重试。Follower 节点上没有改数据,重新选举后Client重试重新提交可成功。 原来的Leader节点恢复后作为Follower加入集群重新从当前任期的新Leader处同步数据,强制保持和Leader 数据一致。

3. 数据到达 Leader 节点,成功复制到Follower所有节点,但是还未向 Leader 响应接收

这个阶段Leader 挂掉,虽然数据在Follower 节点处于未提交状态,但保持一致,重新选举leader 后可完成数据提交,此时Client由于不知道到底提交成功没有,可重试提交。 针对这种情况Raft请求实现幂等性,也就是要实现每部去重机制。

4.数据到达 Leader 节点,成功复制到Follower部分节点,但还未向Leader响应接收
这个阶段 Leader挂掉,数据在Follower节点处于未提交状态且不一致,Raft协议要求投票只能投给拥有最新数据的节点。 所以拥有最新数据的节点会被选为Leader再强制同数据到Follower,数据不会丢失并最终一致。

5.数据到达 Leader节点,成功复制到 Follower所有的或者多数节点,数据在 Leader 处于已提交状态,但是 Follower处于未提交状态
这个阶段 Leader挂掉,重新选出Leader后的处理流程和阶段3一样

6.数据到达Leader节点,成功复制Follower 所有节点或者多数节点,数据在所有节点都处于已提交状态,但是还未响应Client
这个阶段 Leader 挂掉,Cluster内部数据其实已经是一致的。Client重复重试基于幂等策略对一致性无影响。

7.网络分区导致的脑裂情况,出现双 Leader
网络分区将原先Leader节点和Follower节点隔开,Follower收不到Leader的心跳将发起选举产生新Leader。这是就产生了双leader,原先的Leader独自在一个区,向他提交数据不可能复制到多数节 点所以永远提交不成功,网络恢复后旧的 Leader 发现集群中有更新任期(term)的新Leader 则自动降级为Follower 并从新Leader处同步数据达成集群数据一致。

5.小结

        raft将共识问题分解成两个相对独立的问题:leader election,log replication。流程是先选举出leader,然后leader负责复制、提交log(log中包含command)

  为了在任何异常情况下系统不出错,即满足safety属性,对leader election,log replication两个子问题有诸多约束。

leader election约束:

  • 同一任期内最多只能投一票,先来先得
  • 选举人必须比自己知道的更多(比较term,log index)

log replication约束:

  • 一个log被复制到大多数节点,就是committed,保证不会回滚
  • leader一定包含最新的committed log,因此leader只会追加日志,不会删除覆盖日志
  • 不同节点,某个位置上日志相同,那么这个位置之前的所有日志一定是相同的
  • Raft never commits log entries from previous terms by counting replicas.

6.参照

https://web.stanford.edu/~ouster/cgi-bin/papers/raft-atc14

Raft Consensus Algorithm

Raft

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值