一、raft概念
在分布式系统中,一致性是比较常见的概念,所谓一致性指的是集群中的多个节点在状态上达成一致。
在程序和操作系统不会崩溃、硬件不会损坏、服务器不会掉电、网络绝对可靠且没有延迟的理想情况下,我们可以将急群众的多个节点看作一个整体,此时要保证它们的一致性并不困难。
但是在现实场景中,很难保证上述极端的条件全部满足,节点之间的一致性也就很难保证,这样就需要Paxos、Raft等一致性协议。
一致性协议可以保证集群中大部分节点可用的情况下,集群依然可以工作并给出一个正确的结果,从而保证依赖于该集群的其它服务不受影响。
raft算法是保证多个服务器节点数据一致性的共识算法,它是受到Paxos的影响而产生的。相对于Paxos而言,Raft共识算法更加简单易懂。
在Raft共识算法中,所有的数据变化都是以日志的形式记录在服务节点中,日志包含索引值、任期值等信息。
其中,索引值表示日志的序号,任期值表示生成该索引值时,raft leader对应的任期值。服务节点会不断的读取日志记录,并将日志记录更新到服务节点的数据中。
其中Follower和Leader是Raft共识算法中常见的两种角色状态:
Follower:状态为跟随者,负责被动接收数据;
Leader状态为领导者,负责处理所有客户端的交互,日志复制等
在raft协议中,每个节点都维护一个状态机,该状态机有3种状态,分别是Leader状态、Candidate状态和Follower状态。在任意时刻集群中的任意一个节点都处于这3个状态之一。在大多数情况下,集群中只有一个Leader,其它节点都处于follower状态。
Leader节点:
第一主要负责处理所有客户端的请求,当接收到客户端的写入请求时,Leader节点会对写入的请求追加一条日志,然后将其封装成消息,发送到集群中的Follower节点,当Follower节点收到该消息时,会对其进行响应,如果集群中有大多数节点收到该日志记录时,那么Leader节点认为这条日志记录已经提交,可以向客户端返回响应。
第二就是向集群中其它节点发送心跳消息,这主要是防止集群中其它节点选举计时器超时触发新一轮选举。
Follower节点:
Follower节点不会发送任何请求,它们只是简单的响应来自Leader或者Candidate的请求,Follower节点不会处理客户端的请求,而是将请求重定向给集群中的Leader节点进行处理。
Candidate节点:
是由Follower节点转化而来,Follower节点长时间没收到Leader节点发送的心跳消息时,该节点的选举计时器就会过期,然后将自身状态转化成Candidate,发起新一轮选举。
在上图所示图例中,Raft集群初始化后,所有节点均为Folower节点,当某个Follower节点超时后便会触发选举,并切换成Candidate,Candidate会向其它节点发送投票消息,若能收到大多数节点的选票,则切换成Leader。若发现已有Leader或没有成为新任期的Leader,则切换成为Follower。若在选举计时器选举范围内没收到大多数的选票,那么就会触发新一轮选举。
场景一、
场景二、
三种情况进行leader选举
1、在平时,leader发送heartbeat给所有follower,表示自己还健康,如果其中某个follower没有收到heatbeat,它就会认为我的leader已经宕掉了,那么这个时候follower角色就会转变为Candidate,那么candidate就会进行一轮leader的竞选。
2、在raft里面把时间分割成不同的小块,每一块以term来表示,在每个term里面,我们的leader都是不变的,但是在我们不同的term之间我们会做leader竞选,假设其它leader竞选的条件没有达到,但是我们term结束了,型的term产生了,我们就会自动进行一个leader竞选。
3、情况比较复杂,我们leader是和客户端交互的东西发送给follower,那么leader会有它自己的logEntry,follower也会有它自己的logEntry,所以呢,我们在发送logEntry的时候,其实就是一个同步的过程,就是leader把leader logentry,然后以appendEntry这个方法发送给follower,在这种情况下,假设,follower发现自己的logEntry和Leader发送的 不一样,并且我的是比较新的,那么这个follower就会变成candidat参与leader的竞选。
下面讲下Leader竞选的过程
candidate会向所有的follower,发送一个requestVote,我要竞选了,你们来投票。投票过程中有一些规定,比如说一个服务器只能投一票,多数票胜出(非常重要的一个概念,分布式概念下一般都是多数获胜的方式去决定的)
开始是4号candidate发起投票,假如这个时候6号candidate也发起了投票
假设4拿到了2和3的投票,6拿到了1和5的投票,这个时候它们的投票是平等的,这个时候不会有一个leader产生,等待下一轮竞选。
在leader竞选完之后,leader会发送一个appendEntry的请求,其实就是说我成为了leader那么这个时候我要把我的logEntry给follower更一下,因为有可能follower的logEntry跟leader的不一样,有可能比leader老旧,也有可能是新的多余的。
还有一点就是,假设4在竞选期间收到了其它节点的appendEntry的请求,这个时候,这个appendEntry有可能是来自其它节点,比如说,4作为一个candidate正在竞选,它竞选过程当中呢,服务器6已经成为了leader,那么服务器6就会发送appendEntry,那么收到这个appendEntry,它首先要做的呢,并不是已经有leader了,我就转化为follower,它会比较自己的term,也就是比较自己logEntry里面的term和服务器发送来的term,如果服务器4认为自己还是比较新的,那么服务器4仍然会作为candidate继续竞选,如果服务器4发现自己的term比leader的旧一点,这个时候4就会放弃竞选转变为follower。
同理选举结束之后呢,leader也会发送appendEntry请求,append请求也是会比较logEntry的term的。
下面介绍下怎么去比较这个logEntry的?
每个小方块代表一个term。在复制日志的时候可能掉线了或者宕机了,重启后又成为leader,然后继续复制等等,反正很多情况,导致日志不一样的情况。
比如上图服务器1只有log1234,服务器5之后又123等等。
leader服务器2首先会比较logEntry最新一个term,比如说:
根据服务器1,发现服务器1没有term5,所以leader这里就会往回走一个term,走到term4,然后继续比较,发现最新的都是term4,这样就会把新的logEntry5发送给服务器1;
针对服务器5呢,它会发现logEntry4也没有,它会一直回溯,回溯到和要同步的follower相同的一个term为止,上图就是term3,那么leader就是从3个开始将4和5复制给服务器5。
遇到服务器4这种情况,发现服务器4 呢,它实际上是多了,多了一个term6,那么leader就会把多余的擦除,擦除之后再比较,发现term匹配。
在完成日志复制之后,这个时候客户端发送了一个新的写请求,会产生一个新的logEntry,假如服务器2还是leader,它就会发送一个appendEntry的请求,给所有的follower,如果多数follower都接收到这个logEntry,然后成功复制,那么leader就会把它提交,上图就是被大部分服务器3、4、5接收了,这个就算多数,这个时候就算已提交, 已提交之后,它(term6)就会被写入到我们leader的logEntry。
但是如果,只是被其中一个接收(如上图),那么这个logEntry6不会被作为提交写入到我们的leader logEntry中。这种情况就跟上上上个图里面的服务器4一样多了一个term6。