要搞raft, 两篇论文不能忽视,一个就是公开发布的那篇论文,另一个就某团队的写工程实践的论文。 本自己的最近又重看了下公开发表的论文,算是给自己的做的笔记,写的太简略,纯粹给自己当笔记用的。
raft协议解析
raft的特点
raft和大多数一致性协议相似,但是又一些独有的特质
stronger leader: 相对于其它一致性协议, raft又一个强主, 比如,log entries只能从master流向follower, 这样极大的简化了日志管理过程。
Leader election: raft使用随机计数器选择leader,这样能简单高效的解决选主冲突。
MemberShip change: 利用new joint consensus(其间,两个配置中的大多数是重合的)
复制状态机
一致性协议是在复制状态机的基础上发展而来的,在这种方式下, 在多个机器上运行的状态机运行相同的序列, 并且能够在一部分机器挂掉的情况下能够对外提供服务。
在分布式系统中, 复制状态机是用来解决容错问题的, 大规模系统中, 一般都有单个cluster leader
比如在GFS、 HDFS都是用日志复制状态机来进行选主、以及保存配置。
日志复制状态机一般用复制log实现,如图1所示, 每个server维护了一个包含一系列command的日志。每个机器的日志状态机顺次执行这些命令,因此, 每个日志状态机执行相同序列的command。
实际中的一致性算法一般有以下的特质:
确保安全性(从不会返回错误的结果), 在非拜占庭条件下: 网络延迟、分区、包丢失、重复、序列重排。
只要大多数节点是可用的、互通的且和client是联通的则整个系统是可用的。
不依赖时钟来保证一致性。
大多数情况下, 只需要大多数节点回应, 一个command就算提交成功。少数节点并不能影响系统的性能。
raft的三个子问题
leader election: 一旦leader 失败,需要选出新的leader。
log replication: leader需要接受client的log enties,并且把其复制到集群中,强制其它机器上的log遵从自己的顺序。
正确性保证:如果任何server已经在某个log index上提交过日志, 其它server不能再提交任何command。当然这里还需要其它一些额外的机制(选举阶段)来保证。
leader election
当candidate既没有赢得选举也没有失去选举(当多个followers同时成为candidate, 投票被平分,也就是没有candidate获得了大多数投票发生), 那么每个candidate都超时,并增加自己的term, 然后发起新一轮的投票请求。注意, 如果没有额外的机制,在极端条件下, 这个过程可能会无限循环下去, 这样就极大的影响可用性。
log up-to-date
如果两个log有最后一个entries的term不同, 那么term最新的则认为more up-to-date, 如果最后一个entrie的term一样长,则log较长的则more up-to-dat。
选举限制
日志只有由leader流向其它节点,leade从不rewrite自己的log
使用选举阶段的一些限制来限制candidate获得大多数选票,除非该candidate已经包含所有commited的entries。
Cluster membership change
其实就是集群成员发生改变,一般分为两个阶段完成。
在某些系统中, 在第一阶段禁止使用old config, 这个时候,cluster不能再处理客户端的请求。 在第二阶段激活new config。这种明显可用性比较低(第一阶段, 不能处理客户端的请求)。 raft中专门设计了一种方法。 cluster首先转到转移配置(我们称之为联合一致), 一旦这个联合一致被提交,cluster继而转化到新配置,联合一致结合了新旧配置。
log entries 被复制到各个server上。
无论使用的是old/new配置, 任何server都能被选举为leader。
决议(leader选举/entry提交)需要old/new配置中的大多数。
the joint consensus在不损失安全性的前提下,让cluster中的单个server异步进行状态转移。
步骤
- 当leader收到一个要改变集群(从old config -> new config)的命令后,首先保存一个Config(old, new)在自己的log文件中,然后把该log entries复制到cluster中其它server(一旦一个server把该Config添加到自己的log中,该server就用该Config去做决定,而不管这个Config做为log Entries是否被已经提交), 这也就是整个集群会用Config(old, new)去决定Config对应的log entries是否该被提交。 如果leader在这个时候crash, leader会在Config(old)或者Config(old, new)下被选举出来,当然这取决于candidate是否已经收到Conf(old, new), 在任何情况下, Config(new)都不能单方面作出决定,一旦Conf(old, new)被提交, 不能凭借Conf()
如果在新的配置中并没有包涵serverA,serverA在一段时间内没有接受到hb, 然后超时,并发起新的选举, 这会导致新的leader转化为follower。最终一个新的leader被选举出来,但是serverA又一次超时,又发起选举, 这样会极大的极大的影响系统的可用性。
为了解决这个问题,如果server B 在他的最小的selection timeout时间内, 收到了RequestVote请求,他并不会更新自己的item/支持这个选举。
日志压缩
各个server自己压缩-快照。(其实违背了raft的整体设计思想)
如果follower是新加入的/或者follower的日志延迟太多, 则leader需要发送快照到follower上。
影响压缩的两个关键点:
1 压缩的时机:
如果快照过于频繁, 则会浪费磁盘带宽, 如果快照过于少, 则会直接浪费磁盘空间,并且在恢复的时候, 需要大量的时间来恢复。 比较合适的方式是,设置一个固定值, 如果数据大于这个值, 则进行快照。
这个时候磁盘带宽占用比较少。
- 写入快照
一般来说, 写入快照比较耗时, 我们并不想因为快照影响正常的操作。 在工程中,我们可以使用copy-on-write 技术来应对:一个新的更新可以在不影响正在打快照到的数据,
客户端的交互问题
raft client的交互,包括如何找到leader、如何保证线性一致性。
assign unique serial number, 防止重复提交。
read-only query. 不用写日志, 但是为了防止读到stale数据。有两个措施。
2.1 leader需要知道哪行是最新提交的数据,因此, 在成为leader之后, leader首先会提交一个empty entry。
2.2 leader需要和集群里的大多数节点进行心跳测试,这样,才能保证自己当前确实是leader。
2.3 其实也可以用心跳机制提供某种租约, 但是这个在一定程度上依赖于时钟。
题外话:
从election 的方式上就可以看到可理解性是如何指导我们涉及这套系统的, 设计之初, 我们打算用rank systeming来进行election。
设计方法:
给每一个server设置一个rank值。在以后的选举中,当冲突发生的时候, 权值高的选举成功,也就是当一个candidate发现有一个权值更高的candidate,自己就返回成follower状态, 这样,权值高的candidate更容易在下一轮选举中获得大多数投票。
问题
这样会有降低系统的可用性,当高权值的candidate失败后, 权值低的follower超时后, 又发起新的一轮选举, 如果这个发生的比较频繁,往往会重置正在选举过程。
结构体
单个server持久化的state
struct State {
currentTerm,
voteDfor,
logs[]
}
单个server易变的state
struct State {
commitIndex,
astApplied
}
Leader上易变的state(每次选举之后重新初始化)
struct State {
//commitIndex,
//astApplied
nextIndex[],
matchIndex[]
}
两个重要的RPC结构
- AppendEntries RPC(也是用来做hb的结构)
**Argument**
struct
term,
leaderId,
prevLogIndex,
prevLogTerm,
entries[],
leaderCommit
**Results**
term, currentTerm, for leader to update itself
success, true if follower contained entry matching prevLogIndex and prevLogTerm
**Receiver implementation**:
1. Reply false if term < currentTerm (§5.1)
2. Reply false if log doesn’t contain an entry at prevLogIndex
whose term matches prevLogTerm (§5.3)
3. If an existing entry conflicts with a new one (same index
but different terms), delete the existing entry and all that
follow it (§5.3)
4. Append any new entries not already in the log
5. If leaderCommit > commitIndex, set commitIndex =
min(leaderCommit, index of last new entry)
- RequestVote RPC
**Arguments**:
term
candidateId
lastLogIndex
lastLogTerm
**Results**:
term currentTerm, for candidate to update itself
voteGranted true means candidate received vote
**Receiver implementation**:
1. Reply false if term < currentTerm (§5.1)
2. If votedFor is null or candidateId, and candidate’s log is at
least as up-to-date as receiver’s log, grant vote (§5.2, §5.4)
特质
Election Safety at most one leader can be elected in a given term. §5.2
Leader Append-Only a leader never overwrites or deletes entries in its log; it only appends new entries. §5.3
Log Matching if two logs contain an entry with the same index and term, then the logs are identical in all entries up through the given index. §5.3
Leader Completeness: if a log entry is committed in a given term, then that entry will be present in the logs of the leaders for all higher-numbered terms. §5.4
State Machine Safety if a server has applied a log entry at a given index to its state machine, no other server will ever apply a different log entry for the same index. §5.4.3