分布式一致性算法-Raft

Paxos协议的出现为分布式强一致性提供了很好的理论基础,但是Paxos协议理解起来较为困难,
实现比较复杂。

然后斯坦福大学RamCloud项目中提出了易实现,易理解的分布式一致性复制协议 Raft。

我们熟知的Redis哨兵模式和阿里开源的服务发现注册中心Nacos、K8s的使用的存储系统etcd都实现了Raft。

1.相关理论

1.1 状态机复制模型

Raft的算法实现基于状态机复制思想,一个服务由一致性模块(Consensus Module)、日志模块(Log)、状态机(State Machine)组成,集群由多个服务组成。状态机执行日志输入的指令得到输出结果,那么当多个状态机经历执行相同指令后,也会得到相同的输出结果。Raft算法的核心工作既是实现一致性模块,通过解决分布式集群常见的问题,使得集群各节点的Log日志一致,从而达到各服务状态机一致。

1.2 对称与非对称共识算法

通过共识算法是否有Leader阶段负责协调整个算法运行进行区分:

  • 对称式共识算法:集群中的所有服务身份一样,向任意一个服务进行操作,达到的结果都是一样的。
  • 非对称式共识算法:整个集群的的协调由Leader节点负责,其他节点被动的接收Leader的请求并处理。

2.Raft算法

2.1 Raft基础

2.1.1 Raft算法中的状态(角色)

在中文内容中一般会提到Raft算法有三种角色,但实际在Raft的论文中使用的是state,在我查阅过的中文论文中,有一部分会使用状态进行翻译,因此我在本文中会使用状态来代替角色一词

A Raft cluster contains several servers; five is a typical number, which allows the system to tolerate two failures. At any given time each server is in one of three states: leader, follower, or candidate

Raft定义了集群中的每个节点在启动后必定属于以下状态:

  • Leader

Leader负责集群的协调工作,接受请求,日志同步,心跳广播维持节点之间的状态,由Candidate在选举胜利中转变而来。Raft算法保证了在一个集群当中,有且仅有一个Leader。

  • Follower

服务启动的时候,所有节点的起始状态都是Follower。Follower通过接受Leader的同步日志来更新本身节点的数据,每个节点都会自带一个定时器,当定时器计时完毕后,会发起选举并转换状态到Candidate。每当接收到Leader的心跳信息,定时器会重置。

  • Candidate

Candidate是一个中间状态,由Follower转变而来,当选举结束后,要么成为Leader协调集群工作,要么变回Follower。但是

他们之间的状态转换的关系图如下:

 2.1.2 Raft 算法中的任期

Raft算法定义了任期(Term)的概念

每一个任期都是从一次Leader选举开始的,触发Leader选举由两种情况引起:1.Follower定时器计时完毕转化成Candidate并发起Leader选举。2.出现多个Candidate票数一样,导致无Leader当选因此举行新的选举。

任期在Raft算法中充当逻辑时钟的作用,用于节点之间比较信息的新旧。Raft状态改变的比较和日志复制都会用上日志。

2.1.3 RPC

实现Raft需要两个通信接口,我们可以使用RPC,这两个RPC接口分别是Candidate用于请求其他节点为它投票的RequestVote RPCs,和由Leader发起用于复制日志条目和提供心跳的AppendEntries RPCs。

2.2 Leader选举

动态演示我们可以通过查看Raft

Raft通过心跳超时机制来触发Leader选举。起始的时候,集群中的所有节点状态都为Follower,他们会随机初始化一个随机心跳超时时间,当节点超时的时候,超时节点就会转变为 Candidate ,当出现Candidate节点的时候就会任期小节中提到的选举阶段,如下图的Node C

在选举阶段会出现3种情况:

  • Candidate通过投票成为Leader

当节点成为Candidate的时候,自身term会自增一,并会给自己投赞成票,同时往其他节点发送RequestVote RPC,当该节点获得过半数赞成票的时候就会成为Leader,并通过AppendEntries RPC向其他节点发送心跳消息

  • 其他Candidate节点优先成为Leader

当Candidate节点收到其他Candidate成为Leader后的心跳消息后会重新转变为Follower

  • 在竞选结束的时候没有一个节点能成为Leader

 在极端的情况下,选票被瓜分(Node A和Node D几乎同时成为Candidate,因为Raft算法的限制,每个节点只能投一票赞成票,只能给其他Candidate投否定票,此时Node A和Node D都没能获得半数赞成票)本轮选举失败,此时Candidate节点会维持当前状态,并进入下一轮选举。

2.3 日志

2.3.1 日志结构

Raft算法中,每个节点都需要维护一份日志,具体结构如下

 2.3.2 日志复制

当竞选成功后,所有Client的请求将会有Leader来处理。因为每个Client的请求可以抽象为若干个命令,日志复制就是将客户端的日志同步到集群中的其他节点,无论Leader节点宕机还是Follower节点宕机,各节点之间的日志始终一致,正常情况下该过程分为以下的步骤(异常情况将会在2.4中谈及):

  1. Leader节点将客户端请求对应的日志项追加到自己的日志列表中。
  2. Leader节点向全部Follower节点发送AppendEntries RPC请求,等待响应结果。
  3. 收到AppendEntries RPC请求后,Follower节点根据条件在本地追加收到的日志项,向Follower响应追加结果。
  4. Leader节点统计响应结果,如果能够确定当前追加日志项在集群中的副本数量超过半数,则把Leader节点的日志状态修改为已执行,并且提交到状态机执行,然后返回给客户端处理结果。
  5. 在随后的心跳消息中,Leader节点会通知Follower节点执行已提交的日志项。

第3条所说的日志追加判定如下:

当Leader节点需要复制索引5的日志给Follower节点的时候,5的日志的索引为4,任期为2,当他送发到Follower节点,如果同一索引下的日志,任期也一致,那么需要复制的日志将会被允许写入Follower节点的日志列表中

 否则将拒绝日志加入Follower节点的日志列表

2.4 一些安全性规则

博文中2.2和2.3小节中提到的Leader选举和日志复制都是理想状态下的算法运行流程,在真实的分布式场景下会遇到各种的问题。

2.4.1 Leader选举安全性

在日志的复制过程中,Raft 算法规定日志项总是从Leader 节点流向Follower节点,解决Leader选举导致的日志不一致问题的关键就是保证新任Leader必须包含历届Leader节点已经提交的全部日志项。因此在Leader选举过程中应该存在一个筛选机制,保证只有符合条件的节点能够成功竞选为Leader。

此筛选机制既是日志状态的判定问题转化为了日志完整性的对比问题,即在Leader选举期间,总是尽可能的选举日志更为完备的节点成为Leader节点,我们从Raft算法的作者论文在RequestVote RPC设计中它的字段拥有lastLogIndex (本地最新一条日志项的索引)和lastLogTerm(本地最新一条日志项的任期号)这两条信息决定了收到RequestVote RPC请求的节点的投票结果。其中下标 L代表本地节点,R代表远端节点(RequestVote RPC请求的发起者),如果本机的lastLogTerm小于请求者的lastLogIndex,投票者应该给出赞成票。又或者投票请求发起者和本机的lastLogTerm一致,但是本机的lastLogIndex小于等于请求者的lastLogIndex,此时同样也应该给出赞成票,除此之外,投票者一律投出否定票:

\tiny lastLogTerm_{L}< lastLogTerm_{R} || (lastLogTerm_{L}== lastLogTerm_{R} \&\& lastLogIndex _{L}\leq lastLogIndex _{R})

2.4.2 日志提交安全性

2.3.2中的提到的的日志提交是一种理想状态下的,但考虑节点宕机情况的话,则会出现不安全的情况

从Raft作者的论文可知

在阶段a,term为2,S1是Leader,且S1写入日志(term, index)为(2, 2),并且日志被同步写入了S2 。

在阶段b,S1宕机,触发新一轮竞选,此时S2-S5都有资格成为Leader,此时S5被选为新的Leader,按照每次竞选对term+1的规则,此时的系统term为3,且写入了日志(term, index)为(3, 2)。

在阶段c,就在S5未来得及复制日志的时候,S5宕机了,这个时候又触发了新的竞选,之前离线的S1重连到集群当中,并且在这次竞选当选Leader,此时系统term为4,在日志同步的规则下S1的日志(term, index)为(2, 2)被同步到S3当中,由于同步日志的节点数占集群节点的半数,这个时候S1的日志就会被提交。

在阶段d,S1又宕机了,在新的竞选当中S5如果再次竞选到Leader,此时S5会将自己的日志更新到Followers,那么S5将会通过Raft规则强制覆盖S1-S4之前的日志。这种情况会被视为不安全。

当出现非当前term的日志进行提交的时候,Leader节点在提交非自己任期内的日志项时,需要保证至少一条自己任期内的日志项已被提交。此时当再出现阶段C中S1宕机的时候,此时S5将不可能竞选到leader,只有在S2、S3中选举到Leader,此时无论是S2还是S3当选Leader都在逻辑上包含所有的日志,不会出现阶段d的不安全情况。

2.5 日志不一致性的修复

Raft算法限制日志项总是经由Leader节点复制到Follower节点,因此解决日志冲突的关键就是追加、覆盖、删除Follower节点中的部分日志。

具体方法就是AppendEntries RPC请求中,除了包含待复制的日志项以外,还会包含该条日志项的上一条日志项的索引和任期号。当发现日志不一致的时候,通过寻找Follower节点中index和term一致的列,然后实施追加、覆盖、删除日志,最终使Follower节点日志和Leader节点一致。

2.6 成员变更

成员变更,是Raft算法的重要组成部分,但是却是算法中比较难理解的部分。

在Raft作者的论文提到的Cnew和Cold,Cnew中的C即Configuration,从开发者角度来理解即节点当中关于集群各节点信息内容的集合。

我们重新查看Raft作者提供的图例

论文图示
论文图示
瞬时图示

 因为不同节点的Configuration更新时间不尽相同,因此会出现Server2响应Server1的投票,在Cold下占大多数,因此Server1成为Leader。又Server4、5响应Server3的投票,在Cnew占大多数,因此Server3也成为了Leader。出现了两个Leader,违背了“领导者唯一性”的原则。

目前解决成员变更的方案有两种single-server changes(单节点变更)和Joint Consensus(联合共识),基于Joint Consensus实现比较复杂,我在博文中这次介绍单节点变更。

2.6.1 单节点变更

单节点变更就是每次变更只操作一个节点。

无论是从3个节点到4个节点

 

还是从4个节点到5个节点

 

通过图示可以看到通过单节点变更,无论是Cold阵营还是Cnew阵营在单节点变更的规则下确保了在整个配置变更期间,最终只会产生1个leader节点。

 

论文:In Search of an Understandable Consensus Algorithm

CONSENSUS: BRIDGING THEORY AND PRACTICE

基于分布式一致性算法Raft构建多数据中心存储系统

参考:状态机复制

Raft算法详解 - 知乎

解决成员变更的问题

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值