raft协议-选举、日志复制、网络分区、快照

raft算法是一种用于管理复制日志的一致性算法,并且易于理解。

主要从以下几个方面来介绍raft协议:

  1. leader的选举
  2. 日志的复制
  3. 网络分区的场景
  4. 日志压缩与快照
  5. linearizable语义
  6. 只读请求
  7. leader节点的转移

leader的选举:

     raft协议的工作模式是一个leader节点和多个follower节点的模式。每个节点维护了一个状态机,并且有三种状态:leader状态、follower状态、candidate状态。

以下是每个状态的节点负责的工作:

leader节点:处理客户端所有的请求和定期向follower节点发送心跳消息。

处理客户端请求:当收到客户端请求后,leader节点会在本地创建一个日志,并封装成消息,发送到follower节点,如果 半数以上的节点收到了消息,并返回给leader节点,此时这条请求会被提交并通知客户端。

定期向follower节点发送心跳消息:防止集群中的follower节点的定时器过期,触发新一轮的选举请求。

follower节点:follower节点不处理客户端的请求,而是将请求重定向给集群的leader节点。并且只接收leader节点和candidate节点的请求。

candidate节点:是follower节点向leader节点过度中的状态。

触发选举的条件时:每次收到leader节点的心跳消息(当收到leader节点的心跳消息的时候,follower节点的定时器会被重置),follower节点的定时器会被重置(这个时间是一个随机值,基本可以保证不会同时有两个follower节点同时触发选举),当长时间没有收到leader接地那的心跳消息后,不会立刻触发新一轮的选举,而是要等待一段时间后才切换成candidate状态来触发新一轮的选举。为什么要等待一段时间才切换到candidate状态触发选举呢?这是因为有可能因为网络延迟或程序瞬时的卡顿而造成的心跳消息没有收到。

当集群初始化时,所有的节点都是follower节点,由于没有leader节点,所以follower节点无法收到心跳消息,所以第一个定时器过期的follower节点会切换到candidate并触发新一轮的选举,并获得一个唯一的任期号,此任期号是全局唯一的,每次选举都会生成一个新的任期号。

candidate节点会发送选举请求到其他follower节点,此时其他follower节点的任期号仍旧是0,所以会将投给candidate节点,同时重置自己的定时器。当收到半数的follower节点的应答后,candidate节点切换为leader节点。并且follower节点记录自己将选票投给了谁。

心跳时间需要远远小于选举超时时间。

如果两个follower节点的定时器同时到期,都切换到了candidate状态,同时,每个candidate节点收到follower节点的个数相同的时候,由于两个candidate收到的投票都没有超过半数,所以此次投票作废!当下一次定时器过期时,重新触发选举,此时又获得了一个任期号。

当存在两个candidate节点时,其中一个candidate接地那会将选票投给任期号大的一方。

通过以上的步骤已经选举出了leader节点,leader节点会定时向follower节点发送心跳消息。

如果leader节点宕机了,第一个follower节点的定时器过期的节点会切换到candidate状态触发新一轮的选举。经过以上的这些步骤之后,选举出了leader节点,某个时间点,宕机的leader节点恢复正常,会收到新leader的心跳消息,宕机的leader的任期号小于新leader,所以宕机的leader会切换为follower节点。


日志的复制:

leader节点收到客户端的更新操作时,会保存到本地的log中,然后以消息的形式发送给follower节点,follower节点收到消息后会将请求记录到自己的log中,会响应leader节点,当leader节点收到半数的follower节点时,会响应客户端,最终leader节点将更新提交,并通知follower节点,消息已经提交了。同时leader节点和follower节点也就可以将该操作应用到自己的状态机中。

集群中的每个节点都有一个本地日志,用于记录更新操作。此外,每个节点还会维护commitIndex和lastAppliy两个值。

commitIndex:当前节点已知、最大的、已经提交的日志索引值。

lastAppliy:当前节点最后一条被应用到状态机中的日志索引值。

leader节点维护了nextIndex[],matchIndex[]两个数组,这两个数组中记录了所有follower节点的nextIndex和matchIndex,这样leader节点就可以针对不同的节点的状态发送不同信息。

nextIndex[]记录了需要发送给每个follower节点的下一条日志的索引值。

matchIndex[]记录了已经赋值给每个follower节点的最大的日志索引值。

如果某个follower节点,宕机后恢复,leader节点会根据这两个数组中的值,来恢复这个宕机的follower节点中缺少的日志信息。

如果leader节点发生了宕机,新的leader并不知道宕机的leader中的nextIndex[],matchIndex[],所以新leader节点会重置nextIndex[],matchIndex[]。新leader会向follower节点发送消息,如果follower节点已经有了新leader的全部日志记录,会返回追加成功。如果follower节点没有要追加的信息,则会返回日志追加失败,在收到失败消息后,leader节点会将nextIndex前移。然后leader节点再次尝试发送消息,循环往复,不断减少nextIndex的值,直至follower节点追加成功。之后就进入了正常的追加消息的流程。

candidate触发选举后,follower节点会比较日志,如果自己的日志比candidate的日志新,则拒绝投票。在比较两个节点的日志时,会比较两个节点的索引值和任期号,从而决定哪个日志值新的。

网络分区的场景:

在一个集群中,如果有一部分节点的网络发生故障,与其他节点断开了连接,就会出现分区。分区后没有leader节点的一方,会因为长时间没有收到心跳消息,而触发选举,如果没有leader节点的一方的个数超过了集群中节点的半数,则这一方会有一个新的leader。当网络恢复时,就会有两个leader节点。老leader发送的心跳消息中的term号小于新leader的term号,所以新leader的follower节点会忽略老leader的心跳。由于没有半数的应答,所以老leader会切换为follower节点。

如果产生分区时,没有leader节点的一方节点数比较少,则不能生成新的leader节点。这样这个分区就会不断的触发选举,term号也会不断的增加。raft针对这种情况作了优化,在选举之前需要进入prevote状态,他会连接集群中的其他节点,如果能够连接到半数以上的节点才能发起新一轮的选举。

老leader会回滚没有提交的日志,并复制新leader节点的日志。这样网络分区恢复后,整个集群的日志又恢复一致了。

客户端连接leader的步骤:

1、客户端初次连接到集群时,会随机挑选一个服务器节点进行通信

2、如果连接到了follower节点,会将leader节点信息返回,

3、客户端连接到leader之后,就可以发送信息进行交互了

4、如果leader节点宕机,那么客户端的请求会超时,客户端会从1开始重新连接

在raft论文中还给出了另一种方案:如果连接到follower节点,那么follower节点会将请求转发给leader,leader响应后,由follower节点应答客户端。

日志的压缩与快照:

在节点重启时会重放日志,如果日志记录过多,则需要花费较长的时间完成重放操作。这就需要压缩和清除机制来减少日志量。

定期生成快照是最常见也是最简单的压缩方法。在创建快照时,会将整个节点的状态序列化,然后写入稳定的持久化存储中,这样在该快照之前的日志记录就可以全部丢弃了。在快照中除了接地那当前的数据状态,还包含了其最后一条日志记录的任期号和索引号。一般情况下,集群中的每个节点都会自己独立、定时地创建快照,在其状态恢复时,都会使用自己本地的最新快照来恢复。

如果follower节点宕机了很久,无法使用自己的快照来恢复,则leader节点会把快照发给follower节点,使用leader节点的快照来恢复。

linearizable(线性化)语义:在客户端每次向集群发送一次请求时,该请求只会被执行一次。

常见的解决方案就是客户端对每个请求都产生一个唯一的序列号,然后由服务端为每个客户端维护一个session,并对每个请求去重。当服务端收到一个请求时,会检测其序列号,如果该请求已经被执行了,那么立即返回结果,而不重新执行。


只读请求:

请求对应的日志记录会写入leader节点的本地log中,并发送到集群中半数以上的节点,之后才会真正提交并应用到状态机中。为了提高只读的性能,可以直接处理而不记录对应的日志记录。如果出现网络分区并产生新的leader节点,此时读请求就有可能读到脏数据。

处理只读请求的逻辑如下:

1、leader节点必读有关于已提交日志的最新消息

2、leader节点会记录该只读请求对应的编号作为readindex,当leader节点的提交位置达到或者超过该位置后,即可响应该只读请求。

3、leader节点在处理只读请求之前必须检查集群中是否有新的leader节点,自己是否已经作废。在处理只读请求前先和集群中的半数以上的节点交换一次心跳消息,来确认自己是否是有效的leader

4、随着日志的不断提交,leader节点的提交位置最终会超过上述readindex,此时leader就可以响应客户端的只读请求了。


leader节点的转移(手动):

首先暂停接收客户端请求,让一个指定的follower节点的本地日志与当前leader节点完全同步,在完成同步之后,该特定follower节点立刻发起新一轮的选举。其term值较大,原leader就被替换了。

 

注:以上内容是对《etcd技术内幕》raft协议的整理。

 

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值