文章目录
1. 引言
一致性算法的目的是让集群工作的像一个整体,即使部分宕机依然对外提供一致的结果。
在Raft出现之前,Paxos算法是主流的一致性算法,常见的一致性框架,要么是基于Paxos,要么深受Paxos影响,比如Zookeeper。不过Paxos理解起来复杂,Paxos理论和真正的实现还有一定的空白,导致实现更为困难。
Raft一开始的目标就是提供一个比Paxos更容易理解,更方便实现的一致性算法。为此Raft将算法分解为3部分: 领导选举(leader selection)、 日志复制(log replication)、 安全性(safety), 并且减少内部状态。
Raft算法和Paxos很相似,但额外提供了几个特性:
- 强领导者(strong leader),日志同步只能从领导者发往到其他节点
- 领导选举(leader selection),使用随机定时器来发起选举领导者,减少冲突的可能。
- 成员变化(membership change)
本文主要涵盖如下内容:
- 复制状态机(replicated state machine)
- Paxos的缺点
- 提升理解性的方法
- Raft一致性方法
- 评价Raft算法
- 相关工作
1. 复制状态机(Replicated State Machine)
复制状态机用于在一组服务器提供一致的状态副本,即使部分服务器崩溃,这组服务器仍然能对外提供一致的状态。
复制状态机在分布式系统在多用于解决有关容错的问题。许多系统都需要单一的领导者,如HDFS、 GFS等,使用单独的复制状态机,来完成领导者选举、存储配置信息来应对领导者崩溃。最常见的复制状态机实现有Chubby、 Zookeeper等。
复制状态机使用类使用MySQL的主从同步机制,通过操作日志实现数据复制, 每一条操作日志里包含一定的动作,状态机在副本节点按顺序执行日志, 相同的初始状态,经过了相同的操作,最终一定会得到相同的结果。
如何保证日志复制过程的一致性就是一致性算法的工作。
在工程实践中使用的一致性算法,一般有以下特性:
- 确保安全性(不会返回一个错误结果), 非拜占庭问题,要考虑网络延迟、分区、丢包、冗余、乱序
- 高可用,集群中大部分机器可用,集群就可用
- 不依赖于时钟来保证时序
- 数据写入耗时,取决于集群中写入较快的大部分(N/2+1台),而不是最慢的那一台
2. Paxos的不足
Paxos首先定义了一个能够达成单一决策的一致性协议,我们把它叫做单一决策Paxos(single-decree Paxos), 之后通过组合多个单一决策Paxos来完成一系列的决策,如一个日志。
Paxos有两个致命的缺点:
- 不好理解,单一决策的解释比较晦涩,多决策通过单一决策实现,又额外添加了复杂性
- 难以实现,Paxos论文里详细介绍了单决策部分的逻辑,而多决策仅仅介绍了可能的方法,具体实现和算法的论文之间有巨大的鸿沟
3. 易于理解的设计
Raft的设计目标:
- 完整的理论基础,供实际系统实现
- 所有情况下保证安全可用
- 常规操作必须高效
- 易于理解,让大部分开发者都能理解
通过两种方式提高可理解性:
- 将问题分解, Raft将问题分解为: 领导选举、日志复制、安全、成员变化
- 减少需要考虑的状态数量
4. Raft一致性算法
Raft实现中将节点分为3种角色: Leader、Follower、Candidate
4.1 服务器存储的状态
所有服务器持久存储:
名称 | 描述 |
---|---|
currentTerm | 服务器最后知道的任期号(从 0 开始递增) |
votedFor | 在当前任期内收到选票的候选人 id(如果没有就为 null) |
log[] | 日志条目;每个条目包含状态机的要执行命令和从领导人处收到时的任期号 |
所有服务器不稳定存储:
名称 | 描述 |
---|---|
commitIndex | 已知的被提交的最大日志条目的索引值(从 0 开始递增) |
lastApplied | 被状态机执行的最大日志条目的索引值(从 0 开始递增) |
leader不稳定存储
名称 | 描述 |
---|---|
nextIndex[] | 对于每一个服务器,记录需要发给它的下一个日志条目的索引(初始化为领导人上一条日志的索引值 +1) |
matchIndex[] | 对于每一个服务器,记录已经复制到该服务器的日志的最高索引值(从 0 开始递增) |
4.2 日志分发RPC (Append Entries RPC)
由leader发起,负责分发日志到所有的Follower服务器, 这个RPC同时也用做心跳包。
参数 | 描述 |
---|---|
term | 领导人的任期号 |
leaderId | 领导人的 id,为了其他服务器能重定向到客户端 |
prevLogIndex | 最新日志之前的日志的索引值 |
prevLogTerm | 最新日志之前的日志的领导人任期号 |
entries[] | 将要存储的日志条目(表示 heartbeat 时为空,有时会为了效率发送超过一条) |
leaderCommit | 领导人提交的日志条目索引值 |
返回值 | 描述 |
---|---|
term | 当前的任期号,用于领导人更新自己的任期号 |
success | 如果其它服务器包含能够匹配上 prevLogIndex 和 prevLogTerm 的日志时为真 |
Follower实现:
- 如果term < currentTerm, 返回success=false
- 如果本机log数组下,log[prevLogIndex]不存在或者log[prevLogIndex]的任期号和prevLogTerm不匹配,返回success=false
- 如果log里存在一条日志,在prevLogIndex之后,且和entries对应位置的日志,任期号不匹配,则删除log在该位置之后的所有日志
- 添加任何在entris里,然后log数组相应位置不存在的日志
- 如果leaderCommit > commitIndex ,将commintIndex设置为leaderCommit并执行对应日志
4.3 投票请求 (RequestVote RPC)
候选人发起投票请求
参数 | 描述 |
---|---|
term | 候选人的任期号 |
candidateId | 请求投票的候选人 id |
lastLogIndex | 候选人最新日志条目的索引值 |
lastLogTerm | 候选人最新日志条目对应的任期号 |
返回值 | 描述 |
---|---|
term | 目前的任期号,用于候选人更新自己 |
voteGranted | 如果候选人收到选票为 true |
接受者实现:
- 如果term < currentTerm, 返回false
- 如果候选人日志没有自己新,返回false
- 如果votedFor为空或者candidateId和votedFor相同,则投票给该候选人
- 如果term > currentTerm,将votedFor置为candidateId,投票给该候选人
4.4 服务器遵循的规则
所有服务器
- 如果commitedIndex > lastApplied, lastApplied自增, 将log[lastApplied]应用到状态机
- 如果RPC请求或响应的term > currentTerm, 将currentTerm设置为term, 把自己切换为Follower
Follower
- 响应AppendEntries RPC和RequesteVote RPC
- 如果超时没有收到AppendEntries RPC和RequestVote RPC,将自己转换为候选人
候选人
- 转变为候选人后发起选举
- currentTerm自增
- 给自己投票
- 重置选举计时器 (用于统计选举超时)
- 向其他服务器发送RequestVote RPC (也就是每台机器都需要知道其他所有的服务,引擎状态机集群是不能动态添加的)
- 如果收到多数节点的投票,成为领导人
- 如果收到新leader的AppendEntries RPC,将自己转换为Follower
- 如果选举超时,开始新一轮选举
leader
- 一旦成为leader,向所有服务器发送AppendEntries请求,即使没有日志也定期发送做为心跳
- 收到客户端请求,向本地log数据写入日志,在日志被commit之后,应用到状态机,然后响应客户端
- 向Follower(假设机器index是i)发送leader的log下nextIndex[i]到log.length间的所有日志
- 如果发送成功,nextIndex[i]和matchIndex[i]都设置为log.length
- 如果发送失败
- 如果响应的term大于currentTerm, 将自己转换为Follower
- 说明同步失败是因为log的prevLogIndex和prevLogTerm不匹配,将nextIndex[i]递减
- 如果存在N
- currentTerm == log[N].term,保证这条日志是当前leader写入的日志
- 且matchIndx里多数节点的值 > N,大多数节点已经接收该日志
- 且N > commitIndex, 这个时候将commitIndex设置为N
4.5 Raft算法总结
性质 | 描述 |
---|---|
选举安全原则 Election Safety | 一个任期(term)内最多允许有一个领导人被选上(5.2 节) |
领导人只增加原则 Leader Append-Only | 领导人永远不会覆盖或者删除自己的日志,它只会增加条目 |
日志匹配原则 Log Matching | 如果两个日志在相同的索引位置上的日志条目的任期号相同, 那么我们就认为这个日志从头到这个索引位置之间的条目完全相同(5.3 节) |
领导人完全原则 Leader Completeness | 如果一个日志条目在一个给定任期内被提交, 那么这个条目一定会出现在所有任期号更大的领导人中 |
状态机安全原则 State Machine Safety | 如果一个服务器已经将给定索引位置的日志条目应用到状态机中, 则所有其他服务器不会在该索引位置应用不同的条目(5.4.3 节) |