《分布式一致性协议raft简介》

Posted

tags:

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

参考技术A

如果你搭建过大型的分布式系统,那么一般你会用到zookeeper这个服务。该服务实现了ZAB算法。其通常会用在fail over选主,微服务上下游server配置中心等场景。但是ZAB和paxos有个缺点,就是理解性比较差。其论文内容十分复杂,导致真正理解的开发人员非常少。 【知乎:raft算法与paxos算法相比有什么优势,使用场景有什么差异】

【raft homepage】
【raft paper】
【live demo】

raft和zookeeper类似,一般需要3或者5个node。这样有利于判断选举的多数情况
node分为3个状态:follower,candidate,leader
状态的转换
raft有2个timeout设置
1)从follow而转换到candidate的timeout: election timeout,设置为:150ms到300ms中的随机数。一个node到达这个timeout之后会发起一个新的选举term(递增的,大的表示新的),向其他节点发起投票请求,包括投给自己的那票,如果获得了大多数选票,那么自己就转换为leader状态
2)node成为leader之后会向其他node发送Append Entries,这个时间为heartbeat timeout
如果lead在实际使用中down掉,剩下的节点会重新开启1)和2)描述的选举流程,保证了高可用性
特殊情况
如果集群中剩下偶数个node,并且在选举的过程中有2个node获得相等的选票数,那么会开启新的一轮term选举。知道有一个node获得多数选票(随机的election timeout保证可行)

client给leader发送数据修改请求
leader通过Append Entries在心跳的过程中将修改内容下发到follower nodes
在多数follower 接收了修改内容返回后,leader向client确认
leader向follower发送心跳,具体执行修改操作,此后数据在集群中保持一致
特殊情况
节点之前的网络状况十分不好,此时会有多个leader,其term也是不同的。
由于commit的修改需要多数通过,那么只有具有最多node的一个集群会commit修改成功。
当网络状况恢复,整个集群的节点会向多数节点的集群同步。这样整个集群中的数据会继续保持一致

live demo中没有提及,但是paper中说明的内容。
在实际使用中可有可能会遇到现有机器被新机器替换,或者为了提升稳定性扩容raft集群的情况。作者给出了joint consensus的解决方案。其能保证切换过程是无缝的。

聊聊分布式一致性协议---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简介》的主要内容,如果未能解决你的问题,请参考以下文章

分布式Raft算法

分布式Raft算法

聊聊分布式一致性协议---Raft协议

raft--分布式一致性协议

白话分布式一致性协议 | raft协议

分布式一致性算法--Raft