聊聊分布式一致性协议---Raft协议
Posted 咸X蛋
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了聊聊分布式一致性协议---Raft协议相关的知识,希望对你有一定的参考价值。
Raft是分布式一致性协议,与Paxos相比,它更简单,更易理解。本文将对Raft协议进行简单介绍。
基本原理
Raft中每个节点存在三个状态:leader、candidate和follower。每个节点在不同状态下,需要保存如下一些参数:
每个节点需要持久存在的参数:
currentTerm:节点最后任期ID,其从0递增;
votedFor:当前任期内的候选人ID,若没有则为null;
log[]:存储日志内容;每条日志包括任期号和命令内容;
每个节点不稳定存在的参数:
commitIndex:已经提交的最大日志索引;
lastApplied:被状态机执行的最大日志索引;
日志从被提交到执行,通常有一定的时间间隔。这两个参数都是从0开始递增。
leader节点不稳定存在的参数:
nextIndex[]:保存各follower节点中,待发送的日志索引,初始化为leader最后一条日志索引+1;
matchIndex[]:保存各follower节点中,已经成功复制的日志索引,初始化为0;
这两个参数在完成选举后,进行初始化。
约束条件:
每个节点需要遵守:
1、若commitIndex>lastApplied,则,自增lastApplied,同时,将log[lastApplied]应用到状态机;
2、若请求或响应的term>currentTerm,则,设置currentTerm=term,同时将自己切换为follower状态;
follower节点需要遵守:
1、响应来自leader或candidate节点的请求;
2、若election time时间内,未收到leader的同步(心跳)信息,或者candidate的投票信息,则,将自己切换为candidate状态;
candidate节点需要遵守:
1、一旦成为candidate,则必须开始选举:
a:将currentTerm自增1;
b:选举自己为leader;
c:重置election time;
d:向其他所有节点发送选举请求;
2、若收到大多数节点的投票,则,自己变为leader;
3、若收到一个新leader发来的同步日志请求,则,自己变为follower;
4、若超过election time未决,则,发起新一轮选举;
leader节点需要遵守:
1、一旦成为leader,需要向其他节点发送心跳包,同时,在空间时间重复发送,防止发起选举;
2、若收到来自客户端的请求,需要先将日志追加到本地,待应用到状态机后,响应客户端;
3、若某follower节点的最新日志索引>=nextIndex,则,需要往该follower节点,发送nextIndex以后所有的日志:
a:若成功,则,更新nextIndex和matchIndex;
b:若因为日志不一致失败,则,将nextIndex递减,重试;
4、若有N>commintIndex,且大多数matchIndex[i]>=N,同时log[N].term==currentTerm,则,将commitIndex设定为N。
基本特性:
1、一个任期内只存在一个leader;
2、leader只会追加日志,不会覆盖和删除日志;
3、如果两条日志的索引和任期号相同,则认为这两条日志是一致的,同时,该索引位置之前所有日志都是一致的;
4、如果一条日志在当前任期被提交,那么,它将出现在,比当前任期号更大的所有任期内;
5、如果一个节点已经将日志应用到状态机;则,其他节点不会在该索引位置应用其他日志;
Raft保证这些特性在任何情况下都成立。
Leader选举
当follower节点在election time内,没有收到leader的任何信息(日志同步信息或者心跳),则发起选举。选举时,follower节点将自身当前任期号(currentTerm)自增1,并向其他节点发起选举自己为leader的请求,同时也将自己的状态转为candidate。直到满足下列三个条件:1)自己成为leader;2)其他节点成为leader;3)本轮中没有任何节点成为leader。
选举接口(RequestVote RPC)定义:
参数:
term:候选者任期号;
candidateId:请求投票的候选人id;
lastLogIndex:候选人最新日志索引;
lastLogTerm:候选人最新日志对应的任期号;
返回值:
term:当前任期号,用于候选人更新自己;
voteGranted:返回true表示同意选择该候选人为leader;
响应逻辑:
1、若term值小于currentTerm,则返回follower;
2、若votedFor为null,或者votedFor为candiatedId,并且候选者日志是最新日志(根据lastLogIndex和lastLogTerm判断),则同意选举candidateId为leader;
关键点:
1、follower节点频繁发起选举,导致不可用的问题。需要设置合理的election time,一般设置为比节点平均响应时间大一个数量级;
2、产生多个leader,且投票数均未超过半数,即选举分裂。每个节点使用一个随机的election time,一般取150-300ms之间的随机值;
3、已提交日志丢失问题。选举时,只能选举拥有全部提交日志者为leader;
日志同步
选举完成之后,leader节点需要向follower节点同步日志。同时,所有的客户端请求,都需要通过leader节点完成。
日志同步接口(AppendEntries RPC)定义(该接口也用来发送心跳包):
参数:
term:leader的任期号;
leaderId:leader节点ID,以便在客户端连接follower节点时,能将请求转到leader节点;
prevLogIndex:最新日志之前的日志索引;
prevLogTerm:最新日志之前的日志所属任期号;
entires[]:日志信息,当为心跳包时,entires为null;可以存储多条日志;
leaderCommit:leader节点的commitIndex;
返回值:
term:当前任期号;用于leader更新自己的任期号;
success:follower节点上存在,能满足prevLogIndex和prevLogTerm的日志时,返回true;
响应逻辑:
1、若term<currentTerm,返回false;
2、若节点日志中prevLogIndex与prevLogTerm不匹配,返回false;
3、若存在日志与待更新日志相冲突(索引号相同,term不相同),则删除该日志及其后续所有日志;
4、追加在节点日志中不存在的所有日志;
5、若leaderCommit>commitIndex,则,commitIndex=min(leaderCommit, 最新的日志索引);
关键点:
1、Raft算法中日志的同步是单方向的,即,日志只会从leader节点流往follower节点;
2、leader与follower的日志不一致时,日志同步需要进行一致性检查。具体操作方法是,递减follower节点的nextIndex,直到找到一个索引点,使得这个索引点上,leader和follower日志一致,然后将follower中,该索引点以后的日志,全部删除。leader向follower同步该索引点开始的所有日志。
集群变更
当集群成员发生变更时,raft采用两阶段法。集群首先将配置信息切换为共同一致(joint consensus)状态,一旦共同一致被提交后,系统再切到新配置。其中,配置信息同样是通过日志复制完成。在处于共同一致状态时,客户端的请求,需要同时得到新旧配置两个集群大多数达成一致才能进行提交。但是仍然有三个问题需要解决:
1、新加入节点,需要同步大量日志。因此,在同步日志阶段,该节点处于无投票权状态;
2、集群leader可能不是新配置成员。因此,在些期间,leader只负责同步日志,但是不会被认为自己是大多数之一。
3、被移除的节点,可能因为收不到新配置下leader发送的心跳,而频繁发起选举,并且一起递增自己的currentTerm。避免这个问题的办法是,在发起选举前,每个节点都需要确认在最小的election time内是否收到leader的心跳包,如果有,则不会响应选举。
日志压缩
随着客户端操作的增加,日志信息会越来越大,Raft采用的的方式是通过状态快照+增量日志的方式进行解决。
总结
Raft协议强化了leader的特性,使得该协议,更容易理解。同时,日志连续性,也确保了同步逻辑的简单。
参考资料
https://ramcloud.atlassian.net/wiki/download/attachments/6586375/raft.pdf
以上是关于聊聊分布式一致性协议---Raft协议的主要内容,如果未能解决你的问题,请参考以下文章