持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第14天,点击查看活动详情
1. 概述
Raft是一个分布式共识算法,主要解决分布式系统下多节点的数据一致性问题,也称为共识算法.
分布式系统必然存在多个节点,而多个节点之间要保证数据的一致性,否则对外提供的服务就可能出现数据不一致的问题.
例如分布式数据库(如ETCD),再比如一些分布式服务(如Nacos),为了保证高可用,必然是多节点分布式架构,但是这些服务都必须保证多个节点间数据的一致性,才能提供稳定可靠的服务.
而多节点间如何保证数据的一致性?
那就必须借助一些算法理念了.
比较著名的Pasox算法,论文-- lamport.azurewebsites.net/pubs/paxos-…
而我们本文所说的Raft算法其实是基于Pasox演化而来的一种一致性算法
其他的还有zk使用的ZAB算法
1.1 延伸
分布式:
分布式系统是若干独立计算机的集合,这些计算机对于用户来说就像单个相关系统,分布式系统(distributed system)是建立在网络之上的软件系统
共识:
共识是容错分布式系统中的一个基本问题。共识涉及多个服务器就值达成一致。一旦他们就某个问题做出决定,该决定就是最终决定
2. 算法简述
Raft算法文档: raft.github.io/
论文文档: raft.github.io/raft.pdf 中文翻译: www.cnblogs.com/linbingdong…
Raft算法中,每个节点都有自己的角色.通过领导者节点来通知跟随者节点更新日志,从而使所有节点保持数据一致.
Raft算法主要规定了选举领导者机制和日志同步机制.
- 选举领导者机制: 传统的主从复制机制,在主节点宕机后集群就会陷入瘫痪,而领导者选举机制就是为了保证一个领导者节点由于某些情况宕机后,可以由其他节点重新选举一个领导者,保证系统的正常运行
- 日志同步机制: 领导者如何将日志同步至其他跟随者.
这两个机制可以看下官方发布的动画说明,简单易懂
thesecretlivesofdata.com/raft/
2.1 角色划分
raft算法中的角色有三种
Leader: 领导者
Candidate: 候选者
Follower: 跟随者
2.2 选举机制详述
选举,顾名思义就是选出一个领导者
在分布式系统中,选举机制就是从候选者节点中选出一个领导者.
具体过程如下:
-
启动时每个节点都是follower状态
-
当领导者节点长时间没有响应时,跟随者自动转为候选者,开始进行选举
-
每个候选者节点都向其他节点发送一个投票请求,携带自己生成的一个任期号,这个投票请求里会先自己投自己一票,发送后等待其他节点回复
-
其他节点收到候选者节点的投票请求后,会给最早的候选者节点投一票(如果有多个候选者节点时,按请求的先后顺序投票)
-
当候选者节点获得大多数节点投票时,就会成为Leader节点.
例:
现在有5个节点,分别为S1-S5,我们模拟选举的过程
- 5个节点启动,每个节点都是follower状态
- S1在经过一段时间后,发现还没收到leader的心跳请求,准备开始进行选举(raft有随机选举超时时间机制,每个节点开始选举的时间都是随机的,这里以S1为例)
- S1现在本地将任期号置为2,然后向其他四个节点发送选举请求(RPC),请求内携带任期号并自己先投自己一票,然后等待其他节点回复
- S2-S5收到S1的请求后,判断请求中的任期号是否大于本地的任期号,如果大于,更新本地的任期号,并投S1一票返回请求
- S1收到S2的响应后(S2-S5的响应有先有后,假设S2先响应,S3-S5还没响应),发现获得了多数投票(包含自己的一票),就转为Leader
- S1向其他四个节点发送请求,表明自己是Leader,然后开始进行工作.
2.3 日志复制机制详述
Raft算法中提出共识算法的实现通常基于复制状态机
理解: 我把Raft算法的日志复制分为两部分:
- 复制状态机: 复制状态机用来执行日志的命令
- 日志: 领导者节点发送过来的日志,各个节点保证日志一致
在保证日志一样的情况下,通过复制状态机运行对应的日志中的命令就可以保证各个服务器上的一致性(最终一致性)
为什么说是最终一致性呢,Raft算法是一个多数派的算法,只要有多个节点接受了日志,Raft就允许应用该日志(状态机执行该日志),这样就算有一些节点有问题,但在日志一致的情况下,复制状态机能保证执行的最终结果是一致的.
2.3.1 复制状态机
状态机: 状态机是一种抽象出来的数据模型,状态机模型确定后,如果输入相同的话,输出也应该保持一致.
我个人理解状态机就相当于一个制定好的公式,入参相同的话,公式的结果应该每次都一样.
复制状态机: 多台服务器上都有相同的状态机,这样就能保证每台服务器上如果输入相同,那么输出也相同.
复制状态机的作用:
首先我们思考下如果没有复制状态机,我们如何保证各个服务器的数据保持一致?(这里的数据指的是最终数据,并非日志)
借助时间保持数据的一致性? 如果服务器时间被修改了,那么数据就可能出现问题
所以Raft算法提出应该避免使用本地时钟,如果日志依赖本地时钟的话,就可能导致消息延迟的情况下出现可用性问题
They do not depend on timing to ensure the consistency of the logs: faulty clocks and extreme message
delays can, at worst, cause availability problems.
而复制状态机就相当于摒弃其他可能影响数据不一致的因素,只要各个服务器输入一样,那么输出就肯定一样.
2.3.2 日志复制
有了复制状态机,我们就只需要考虑日志在各个服务器上如何保持一致.
从领导者节点来看整个日志复制的流程:
- 客户端发送一个命令到Leader
- Leader本地新建log Entry记录该日志
- Leader发送RPC请求给各个Follower节点,要求各Follower节点进行日志的记录
- Leader判断是否有超过一半的Follower节点返回成功的请求,如果超过一半(多数),就开始应用该日志(允许复制状态机进行日志命令的执行)
- Leader发送RPC请求,请求各个Follower节点开始应用日志
- 多数节点应用成功,Leader返回client端成功标志
注意:
- client指的是客户端,就是调用分布式服务的客户端,可能是浏览器,也可能是其他服务
- 如果有部分(少数)节点并没有给Leader返回记录日志成功的响应,那么Leader就会无限期进行请求,直到成功为止.
从Follower节点来看,就比较简单了:
- 接受Leader记录日志的请求,并传递日志,Follower开始记录日志
- 记录成功后将结果返回给Leader
- 接收Leader应用复制状态机的请求,开始将日志应用到复制状态机
- 应用成功后返回给Leader成功的响应.
日志复制还存在一些问题,属于安全性问题,比如在日志复制时少数节点还没进行日志的保存,此时Leader节点宕机,怎么办?
2.4 日志安全性
Raft算法为了解决像上面说的日志复制中存在安全问题,也做了一些安全性的限制
日志的结构里包含了两个重要信息:
- 任期号: Leader节点从被选举为Leader生成的一个标志
- 日志的索引
为了保证日志的一致性,Raft对日志有如下要求:
- 任期号+日志索引能找到唯一的一条日志
- 每次选举时,follower投票时会判断参加选举的节点的日志是否比自己落后(任期+索引),如果落后于自身,那么不会进行投票
- 当Leader和follower的日志发生冲突时,会进行强制的日志复制,将Leader的日志复制到follower
-
- 找到Leader和follower的最近一条匹配的日志(任期号+索引)
- 删除follower节点中匹配的日志后的所有日志
- 将Leader节点的日志复制到follower节点
2.5 日志压缩
随着分布式系统的运行,日志也会不断增长,为了避免日志过大对系统的影响,需要对日志进行压缩处理,也可以称为快照
快照的用途:
- 给某个follower同步日志时
- 某台新加入的节点同步日志时
- 系统重启后从快照恢复日志
快照的频率要适中,太频繁会影响系统的效率,间隔时间太长也会影响到系统重启后的恢复.
快照中的内容:
- 元数据,包括最后一条提交日志的索引和任期,在新的日志复制时,会进行检查.
- 系统状态
2.6 成员变更
成员变更: 顾名思义,集群中新增/移除节点
一般成员变更的操作都是将集群下线->然后修改集群配置->重启集群,这样的方式
Raft算法提供有在线成员变更的功能,在不停止集群运行的状态下修改集群的成员配置
但是在线修改成成员配置必须要考虑的问题就是: 宕机
当修改配置到一半的时候Leader宕机了怎么办? 新选出的Leader是没收到修改配置请求的节点怎么办?'
Raft算法对此进行了规定:
Raft将成员变更看作Old和New两个配置,成员变更就是将Old变为New的过程.
Old就是老的配置,New就是新的配置,Leader需要在配置变更后将配置信息同步至各个Follower节点.
Raft算法中成员变更借助了过度
成员变更流程如下:
- 成员发送变更请求到Leader
- Leader收到变更请求后创建一个新的LogEntry