Raft详解

#分布式协调/Raft

拜占庭将军问题

Leslie Lamport 在三十多年前发表的论文《拜占庭将军问题》。
拜占庭位于如今的土耳其的伊斯坦布尔,是东罗马帝国的首都。由于当时拜占庭罗马帝国国土辽阔,为了防御目的,因此每个军队都分隔很远,将军与将军之间只能靠信差传消息。在战争的时候,拜占庭军队内所有将军必需达成 一致的共识,决定是否有赢的机会才去攻打敌人的阵营。但是,在军队内有可能存有叛徒和敌军的间谍,左右将军们的决定又扰乱整体军队的秩序,在进行共识时,结果并不代表大多数人的意见。这时候,在已知有成员不可靠的情况下,其余忠诚的将军在不受叛徒或间谍的影响下如何达成一致的协议,拜占庭问题就此形成。拜占庭假设是对现实世界的模型化,由于硬件错误、网络拥塞或断开以及遭到恶意攻击,计算机和网络可能出现不可预料的行为。

什么是Raft

Raft是一个用于管理日志一致性的分布式协议。

raft协议的工作原理的概括

  1. raft会先选举出leader,leader完全负责replicated log的管理。
  2. leader负责接受所有客户端更新请求,然后复制到follower节点,并在“安全”的时候执行这些请求。
  3. 如果leader故障,followes会重新选举出新的leader。

Leader election

raft协议中,一个节点任一时刻处于以下三个状态之一:

  • leader
  • follower
  • candidate
    [image:48A87DE6-5F2D-47E1-B279-4F7D1299A492-33698-0000C2CFE2547926/D34F9A6C-5801-446C-BDF0-C87BEFE0D172.png]

图中可见所有节点启动时都是follower状态;在一段时间内如果没有收到来自leader的心跳,从follower切换到candidate,发起选举;如果收到majority的造成票(含自己的一票)则切换到leader状态;如果发现其他节点比自己更新,则主动切换到follower。
系统中最多只有一个leader,如果在一段时间里发现没有leader,则大家通过选举-投票选出leader。leader会不停的给follower发心跳消息,表明自己的存活状态。如果leader故障,那么follower会转换成candidate,重新选出leader。

term

[image:D5585F66-94F3-400E-A3B8-F0A186F40F02-33698-0000C3C816BECEAF/4A7014AE-F089-44D0-A236-C01AB9E98FDE.png]

term(任期)以选举(election)开始,然后就是一段或长或短的稳定工作期(normal Operation)。从上图可以看到,任期是递增的,这就充当了逻辑时钟的作用;另外,term 3展示了一种情况,就是说没有选举出leader就结束了,然后会发起新的选举,这种位/split vote/的情况。

选举过程

如果follower在/election timeout/内没有收到来自leader的心跳,(也许此时还没有选出leader,大家都在等;也许leader挂了;也许只是leader与该follower之间网络故障),则会主动发起选举。步骤如下:

  1. 增加节点本地的 /current term/ ,切换到candidate状态
  2. 投自己一票
  3. 并行给其他节点发送 /RequestVote RPCs/
  4. 等待其他节点的回复, 在这个过程中,可能出现三种结果
    1. 收到majority的投票(含自己的一票),则赢得选举,成为leader
    2. 被告知别人已当选,那么自行切换到follower
    3. 一段时间内没有收到majority投票,则保持candidate状态,重新发出选举

投票规则

  • 在任一任期内,单个节点最多只能投一票
  • 候选人知道的信息不能比自己的少
  • first-come-first-served 先来先得
    ::为避免平票情况,leader-based 共识算法中,节点的数目都是奇数个,尽量保证majority的出现。::

log replication

当有了leader,系统应该进入对外工作期了。客户端的一切请求来发送到leader,leader来调度这些并发请求的顺序,并且保证leader与followers状态的一致性。raft中的做法是,leader将客户端请求(command)封装到一个个log entry,将这些log entries复制(replicate)到所有follower节点,然后大家按相同顺序应用(apply)log entry中的command
简单来说:相同的初始状态 + 相同的输入 = 相同的结束状态
[image:3443F341-AA1F-40F9-9C4A-3042174A1011-33698-0000C4A466AF5C0D/D974E179-A96D-4EE3-8D93-F3917CD34F24.png]

完整流程

  • 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

可以看到leader只需要大多数(majority)节点的回复即可,这样::只要超过一半节点处于工作状态则系统就是可用的::。
日志在每个节点上样子如下:
[image:3B683D87-DB07-4D80-8C1B-C29516DDCA96-33698-0000C4FB8B703A03/78CF9595-121B-4201-BCE0-3F29D94482AD.png]

不难看到,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),前者是指日志被复制到了大多数节点后日志的状态;而后者则是节点将日志应用到状态机,真正影响到节点状态。

Safety

在任何系统模型下,都需要满足safety属性,即在任何情况下,系统都不能出现不可逆的错误,也不能向客户端返回错误的内容。比如,raft保证被复制到大多数节点的日志不会被回滚,那么就是safety属性。而raft最终会让所有节点状态一致,这属于liveness属性。

Election safety
选举安全性,即任一任期内最多一个leader被选出。这一点非常重要,在一个复制集中任何时刻只能有一个leader。系统中同时有多余一个leader,被称之为脑裂(brain split),这是非常严重的问题,会导致数据的覆盖丢失。在raft中,两点保证了这个属性:

  • 一个节点某一任期内最多只能投一票;
  • 只有获得majority投票的节点才会成为leader。
    因此,::某一任期内一定只有一个leader::。

log matching
log的匹配特性:是说如果两个节点上的某个log entry的log index相同且term相同,那么在该index之前的所有log entry应该都是相同的。
leader在某一term的任一位置只会创建一个log entry,且log entry是append-only。其次,consistency check。leader在AppendEntries中包含最新log entry之前的一个log 的term和index,如果follower在对应的term index找不到日志,那么就会告知leader不一致。
某个follower可能存在的六个状态:
[image:C2733DEC-CA95-46E4-B34D-B0E20809F2DF-33698-0000C8074CFCC06F/744B8EE5-32C2-460C-B7F1-DBEC1F84DB3E.png]

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

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

  • 一个日志被复制到majority节点才算committed
  • 一个节点得到majority的投票才能成为leader,而节点A给节点B投票的其中一个前提是,B的日志不能比A的日志旧。下面的引文指处了如何判断日志的新旧

corner case

stale leader
raft保证Election safety,即一个任期内最多只有一个leader,但在网络分割(network partition)的情况下,可能会出现两个leader,但两个leader所处的任期是不同的。
在这样的情况下,我们来考虑读写。
首先,如果客户端将请求发送到了NodeB,NodeB无法将log entry 复制到majority节点,因此不会告诉客户端写入成功,这就不会有问题。
对于读请求,stale leader可能返回stale data,比如在read-after-write的一致性要求下,客户端写入到了term2任期的leader Node E,但读请求发送到了Node B。如果要保证不返回stale data,leader需要check自己是否过时了,办法就是与大多数节点通信一次,这个可能会出现效率问题。另一种方式是使用lease,但这就会依赖物理时钟。
从raft的论文中可以看到,leader转换成follower的条件是收到来自更高term的消息,如果网络分割一直持续,那么stale leader就会一直存在。而在raft的一些实现或者raft-like协议中,leader如果收不到majority节点的消息,那么可以自己step down,自行转换到follower状态。

leader crash
follower的crash处理方式相对简单,leader只要不停的给follower发消息即可。当leader crash的时候,可以从请求的流程图中的各个情况进行分析,[[Lead节点对一致性的影响]]中可以看到在任一个环节中Leader crash都不会对一致性产生影响。

Lead节点对一致性的影响

#分布式协调/Raft

Raft 协议强依赖 Leader 节点的可用性来确保集群数据的一致性。数据的流向只能从 Leader 节点向 Follower 节点转移。当 Client 向集群 Leader 节点提交数据后,Leader 节点接收到的数据处于未提交状态(Uncommitted),接着 Leader 节点会并发向所有 Follower 节点复制数据并等待接收响应,确保至少集群中超过半数节点已接收到数据后再向 Client 确认数据已接收。一旦向 Client 发出数据接收 Ack 响应后,表明此时数据状态进入已提交(Committed),Leader 节点再向 Follower 节点发通知告知该数据状态已提交。
[image:8AF959F4-5E7F-4FC8-B58D-1033F7717324-33698-0000C7BBD702A5CF/815275-20160301175358173-526445555.png]

在这个过程中,主节点可能在任意阶段挂掉,看下 Raft 协议如何针对不同阶段保障数据一致性的。
1. 数据到达 Leader 节点前
这个阶段 Leader 挂掉不影响一致性,不多说。
[image:5482EED5-F09B-4CEF-BF18-37747208D59E-33698-0000C7BBD6CD9C4E/815275-20160301175405705-1452838896.png]

2. 数据到达 Leader 节点,但未复制到 Follower 节点
这个阶段 Leader 挂掉,数据属于未提交状态,Client 不会收到 Ack 会认为超时失败可安全发起重试。Follower 节点上没有该数据,重新选主后 Client 重试重新提交可成功。原来的 Leader 节点恢复后作为 Follower 加入集群重新从当前任期的新 Leader 处同步数据,强制保持和 Leader 数据一致。
[image:194479CB-30A7-45DF-A95E-AC7654667A52-33698-0000C7BBD698F509/815275-20160301175412580-649716029.png]

3. 数据到达 Leader 节点,成功复制到 Follower 所有节点,但还未向 Leader 响应接收
这个阶段 Leader 挂掉,虽然数据在 Follower 节点处于未提交状态(Uncommitted)但保持一致,重新选出 Leader 后可完成数据提交,此时 Client 由于不知到底提交成功没有,可重试提交。针对这种情况 Raft 要求 RPC 请求实现幂等性,也就是要实现内部去重机制。
[image:B2E0C1A1-206A-490A-A299-8FF6434E3C47-33698-0000C7BBD6643954/815275-20160301175419501-326023047.png]

4. 数据到达 Leader 节点,成功复制到 Follower 部分节点,但还未向 Leader 响应接收
这个阶段 Leader 挂掉,数据在 Follower 节点处于未提交状态(Uncommitted)且不一致,Raft 协议要求投票只能投给拥有最新数据的节点。所以拥有最新数据的节点会被选为 Leader 再强制同步数据到 Follower,数据不会丢失并最终一致。
[image:981DE4F1-67F7-4CB0-A5F2-EACCF108C35D-33698-0000C7BBD62DBA67/815275-20160301175427314-1771762822.png]

5. 数据到达 Leader 节点,成功复制到 Follower 所有或多数节点,数据在 Leader 处于已提交状态,但在 Follower 处于未提交状态
这个阶段 Leader 挂掉,重新选出新 Leader 后的处理流程和阶段 3 一样。
[image:C7A4696C-0038-4332-B884-F25B1A4FBC1A-33698-0000C7BBD5F24D1C/815275-20160301175434189-317254838.png]

6. 数据到达 Leader 节点,成功复制到 Follower 所有或多数节点,数据在所有节点都处于已提交状态,但还未响应 Client
这个阶段 Leader 挂掉,Cluster 内部数据其实已经是一致的,Client 重复重试基于幂等策略对一致性无影响。
[image:A31FB799-070E-4D91-A08C-420E95EC5C2E-33698-0000C7BBD5ADF102/815275-20160301175628111-980324469.png]

7. 网络分区导致的脑裂情况,出现双 Leader
网络分区将原先的 Leader 节点和 Follower 节点分隔开,Follower 收不到 Leader 的心跳将发起选举产生新的 Leader。这时就产生了双 Leader,原先的 Leader 独自在一个区,向它提交数据不可能复制到多数节点所以永远提交不成功。向新的 Leader 提交数据可以提交成功,网络恢复后旧的 Leader 发现集群中有更新任期(Term)的新 Leader 则自动降级为 Follower 并从新 Leader 处同步数据达成集群数据一致。
[image:A1B89F6B-B991-4D87-9B04-75B6173230F9-33698-0000C7BBD561BE52/815275-20160301175637220-1693295968.png]

综上穷举分析了最小集群(3 节点)面临的所有情况,可以看出 Raft 协议都能很好的应对一致性问题,并且很容易理解。

参考:一文搞懂Raft算法
官网文档:Raft Consensus Algorithm

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值