前提条件
Raft不考虑拜庭将军问题,即消息会延迟、丢失但不会错误。
Raft的特性
- Strong leader:在 Raft 中,日志条目(log entries)只从 leader 流向其他服务器。 这简化了复制日志的管理,使得 raft 更容易理解。
- Leader 选举:Raft 使用随机计时器进行 leader 选举。 这只需在任何一致性算法都需要的心跳(heartbeats)上增加少量机制,同时能够简单快速地解决冲突。
- 成员变更:Raft 使用了一种新的联合一致性方法,其中两个不同配置的大多数在过渡期间重叠。 这允许集群在配置更改期间继续正常运行。
通过选举一个 leader 的方式,Raft 将一致性问题分解成了三个相对独立的子问题:
- Leader 选举:当前的 leader 宕机时,一个新的 leader 必须被选举出来。(5.2 节)
- 日志复制:Leader 必须从客户端接收日志条目然后复制到集群中的其他节点,并且强制要求其他节点的日志和自己的保持一致。
- 安全性:Raft 中安全性的关键是图 3 中状态机的安全性:如果有任何的服务器节点已经应用了一个特定的日志条目到它的状态机中,那么其他服务器节点不能在同一个日志索引位置应用一条不同的指令。章节 5.4 阐述了 Raft 算法是如何保证这个特性的;该解决方案在选举机制(5.2 节)上增加了额外的限制。
Raft算法的RPC和设置
Raft 算法中服务器节点之间通信使用远程过程调用(RPCs),并且基本的一致性算法只需要两种类型的 RPCs。
RPC有三种:
- RequestVote RPC:候选人在选举期间发起
- AppendEntries RPC:领导人发起的一种心跳机制,复制日志也在该命令中完成
- InstallSnapshot RPC: 领导者使用该RPC来发送快照给太落后的追随者。
超时设置:
- BroadcastTime : 领导者的心跳超时时间
- Election Timeout: 追随者设置的候选超时时间
- MTBT :指的是单个服务器发生故障的间隔时间的平均数
BroadcastTime << ElectionTimeout << MTBF
两个原则:
- BroadcastTime应该比ElectionTimeout小一个数量级,为的是使领导人能够持续发送心跳信息(heartbeat)来阻止追随者们开始选举;
- ElectionTimeout也要比MTBF小几个数量级,为的是使得系统稳定运行。
一般BroadcastTime大约为0.5毫秒到20毫秒,ElectionTimeout一般在10ms到500ms之间。大多数服务器的MTBF都在几个月甚至更长。
Leader选举
一个 Raft 集群包含若干个服务器节点;通常是 5 个,这样的系统可以容忍 2 个节点的失效。在任何时刻,每一个服务器节点都处于这三个状态之一:leader、follower 或者 candidate 。在正常情况下,集群中只有一个 leader 并且其他的节点全部都是 follower 。
- Follower 都是被动的:他们不会发送任何请求,只是简单的响应来自 leader 和 candidate 的请求。
- Leader: 处理所有的客户端请求(如果一个客户端和 follower 通信,follower 会将请求重定向给 leader)。
- candidate: 用来选举一个新的 leader
Raft 把时间分割成任意长度的任期(term),如图 5 所示。任期用连续的整数标记。每一段任期从一次选举开始,一个或者多个 candidate 尝试成为 leader 。
如果一个服务器的当前任期号比其他的小,该服务器会将自己的任期号更新为较大的那个值。如果一个 candidate 或者 leader 发现自己的任期号过期了,它会立即回到 follower 状态。如果一个节点接收到一个包含过期的任期号的请求,它会直接拒绝这个请求。
Leader选举过程
Raft 使用一种心跳机制(每个节点都维护一个随机时钟)来触发 leader 选举。当服务器程序启动时,他们都是 follower 。一个服务器节点只要能从 leader 或 candidate 处接收到有效的 RPC 就一直保持 follower 状态。Leader 周期性地向所有 follower 发送心跳(不包含日志条目的 AppendEntries RPC)来维持自己的地位。
触发条件:
- 一般情况下,追随者接到领导者的心跳时,把ElectionTimeout清零,不会触发;
- 领导者故障,追随者的ElectionTimeout超时发生时,会变成候选者,触发领导人选取;
候选操作过程:
追随者自增当前任期,转换为Candidate,对自己投票,并发起RequestVote RPC,等待下面三种情形发生;
- 获得超过半数服务器的投票,赢得选举,成为领导者;
- 另一台服务器赢得选举,并接收到对应的心跳,成为追随者;
- 选举超时,没有任何一台服务器赢得选举,自增当前任期,重新发起选举;
注意事项:
- 服务器在一个任期内,最多能给一个候选人投票,采用先到先服务原则;
- 候选者等待投票时,可能会接收到来自其它声明为领导人的的AppendEntries RPC。如果该领导人的任期(RPC中有)比当前候选人的当前任期要大,则候选人认为该领导人合法,并转换成追随者;如果RPC中的任期小于候选人的当前任期,则候选人拒绝此次RPC,继续保持候选人状态;
- 候选人既没有赢得选举也没有输掉选举:如果许多追随者在同一时刻都成为了候选人,选票会被分散,可能没有候选人能获得大多数的选票。当这种情形发生时,每一个候选人都会超时,并且通过自增任期号和发起另一轮 RequestVote RPC 来开始新的选举。然而,如果没有其它手段来分配选票的话,这种情形可能会无限的重复下去。所以Raft使用的随机的选举超时时间(150~300ms之间),来避免这种情况发生。
日志复制
只要过半的服务器能正常运行,Raft 就能够接受,复制并应用新的日志条目;在正常情况下,新的日志条目可以在一个 RPC 来回中被复制给集群中的过半机器;并且单个运行慢的 follower 不会影响整体的性能。
Leader接受命令的过程:
- 领导者接受客户端请求;
- 领导者把指令追加到日志;
- 发送AppendEntries RPC到追随者;
- 领导者收到半数以上追随者的确认后,领导者Commit该日志,把日志在状态机中回放,并返回结果给客户端;
提交过程:
- 在下一个心跳阶段,领导者再次发送AppendEntries RPC给追随者,日志已经commited;
- 追随者收到Commited日志后,将日志在状态机中回放。
安全性(保证日志顺序)
到目前为止描述的机制并不能充分的保证每一个状态机会按照相同的顺序执行相同的指令。
为了保证安全性增加以下限制:
1. 领导者追加日志(Append-Only)
领导者永远不会覆盖已经存在的日志条目;
日志永远只有一个流向:从领导者到追随者;
2. 选举限制:投票阻止没有全部日志条目的服务器赢得选举
如果投票者的日志比候选人的新,拒绝投票请求;
这意味着要赢得选举,候选者的日志至少和大多数服务器的日志一样新,那么它一定包含全部的已经提交的日志条目。
3. 永远不提交任期之前的日志条目(只提交任期内的日志条目)
在Raft算法中,当一个日志被安全的复制到绝大多数的机器上面,即AppendEntries RPC在绝大多数服务器正确返回了,那么这个日志就是被提交了,然后领导者会更新commit index。
总结
Raft明确了集群的功能+集群的实现细节。所有角色都可以接收用户请求,leader对所有的读写请求负责,也就是说,只有leader对最终的读写有解释权。通过这可以了解到,raft承担的是强一致性场景,其读和写其实是差不多的过程,不会存在读比写快。那么follower如果接收到请求会会怎么办呢?会进行请求转发,这样会多增加一些rpc的请求。
Raft集群将工作状态分成两种,一种是日志复制,另一种是领导选举。其中领导选举是一种不稳定的状态,在这种不稳定的状态中,会出现数据安全、成员状态变更等问题。另外一种日志复制是一种稳定状态,在leader确定的情况下,leader指导所有机器按规定来完成数据的读写要求,其中规定包括如下:
- 所有请求需要经过leader才能完成,包括读写
- 半数集群成员同意某个数据的写入后,才能完成数据的写入。
- 使用2PC模式进行数据写入。
参考
- Raft论文翻译
- Raft一致性算法笔记
- Raft演示动画
- Raft算法总结