raft5大特性
选举安全特性
一个任期只会有一个领导者当选
领导人附加原则
领导者不会删除或者修改日志,只会附加日志
日志匹配原则
如果一个日志在两台机器上索引相同、任期相同,那这两台机器上的日志是一样的
领导人完全特性
领导人日志包含之前提交的所有日志
状态机安全特性
如果领导者在一个索引上应用了一条信息,那么别的机器上不会应用别的信息
raft基础
一个raft集群需要有几台服务器,典型的是5个,可以容忍2台机器宕机。
raft算法里,每台机器只可能有三种状态,跟随者、选举者、领导者。领导者负责处理客户端的请求,跟随者负责冗余数据,保证数据不会因部分节点宕机导致数据丢失。这里部分节点小于集群里机器数量的一半,也就是说,raft最多容忍floor(n/2)的节点失效宕机。
raft算法的开始,集群里的成员状态都是跟随者。跟随者在一段时间内没接收到心跳,就会变身为选举者,选举者会向集群里的其它成员,包括自己,发送请求投票的请求。如果跟随者收到大多数选票,则可变身成为领导者。如果同时有多个选举者发起投票,没有一个选举者拿到过半选票,那么这轮选举没有选出一个领导者,在一段时间后,选举者会发起重试,重新获取投票,这里为了防止出现选票过于分散的情况,在第一次选举失败,选举者会在随机时间后发起选举,这样就可以避免每次重试都有多个选举者发起投票。
选举者或者领导者如果发现自己的任期落后,就会认怂变为跟随者,任期变为当前领导者的任期。跟随者也会更新自己的任期,任期为当前领导者的任期。
raft领导选举
raft用心跳机制来触发领导选举。最开始的时候,集群里的每台机器都是跟随者。如果一台跟随者机器能收到选举者或者领导者的rpc请求,那它就一直保持自己的跟随者状态。领导者周期性地发送心跳请求,如果一个跟随者一段时间内没收到请求,则认为超时,变身为选举者,发起投票。
当一个跟随者变为选举者时,会增加自己的任期,状态变为选举者,并行发送投票请求到集群里的其它机器,等待其它机器的响应。一个选举者发起投票,会有3种结果:
成为领导者
一个选举者收到多数选票,它就会变身为领导者。集群里的机器在一个任期内必须投出自己的选票,跟随者会将选票投给收到第一个投票请求对应的选举者。选举者成为领导者,会发送心跳,保证自己的地位。
收到别的领导者的请求
如果这个请求任期大于等于选举者的任期,选举者认怂,变为跟随者。如果请求任期小于选举者的任期,选举者无视这个请求,继续发起投票。
没有选举者获取多数投票
当一个集群里有多个选举者同时发起投票时,这时每个选举者都可能获取几票,但是没有人获取多数选票,这时候选举者增加自己的任期,在一个随机时间后发起新的一轮投票。
raft日志复制
每台机器上都会有日志记录,会记录执行的指令,指令对应的索引,指令的任期。
raft日志复制有两条定理:如果两台机器上的某条日志的索引和任期一致,那么这条日志是同一个命令对应的日志;如果两台机器上的某条日志的索引和任期一致,那么这个日志之前的数据也是一致的。
我么聪实现的角度来看raft日志复制是如何处理的,我个人认为这样更容易理解一些。
领导者会发送日志复制的rpc请求到跟随者,其中包括任期号(term)、领导者id(leaderId)、前一个日志的索引(prevLogIndex)、前一个日志的任期(prevLogTerm)、复制的日志(entries[])、领导者提交的索引(leaderCommit),跟随者会返回跟随者的当前的任期term、前面日志是否匹配。
如果任期小于跟随者当前任期,返回false,并将当前任期返回到结果里,领导者发现自己任期落后,就会认怂变为跟随者。
如果前一个日志的索引和前一个日志的任期对应不上,或者说,跟随者prevLogIndex对应的prevLogTerm和请求里的prevLogIndex对应的prevLogTerm对应不上,则说明不匹配,返回false。领导者收到返回的结果,如果判断是这一种情况,就会减小prevLogIndex、prevLogTerm,增加要复制的entries,继续发送请求,直到prevLogIndex、prevLogTerm减至为0。(这里可以看出raft日志两条定理是成立的)。
如果要增加日志的索引已经有了别的内容,删掉,把请求里的数据追加到跟随者的日志里。
如果leaderCommit大于跟随者的commitIndex,那么跟随者的commitIndex更新为min(leaderCommit, index of last new entry)。
raft安全
为了保证raft算法的安全性,raft有两条特别的限制。
选举限制
选举者发送投票时,自己的日志必须够新才能够获取投票。这里对新的定义是,日志索引越大表示越新,如果日志索引一样,那么任期越大表示越新。跟随者在收到投票请求时,就会判断选举者里的请求和自己日志里的数据那个是更新的,如果选举者的更新,则投票给它,如果自己的更新,则拒绝投票。
提交限制
领导者不能直接提交之前任期的日志,只能用请求里的leaderCommit间接提交,下面用官方的例子说明为什么这样做。
如图所示,最开始s1是领导者,接受客户端的请求并把日志复制到跟随者里。在b阶段,s1收到日志2,把日志复制给s2,宕机了。然后s5当选领导者,收到客户端日志3。然后s5宕机了,s1恢复并成为领导者,这时s1收到日志4,如果raft日志可以直接提交之前任期的日志,那么要提交日志2,就需要先把日志2复制到别的机器上,当s1把日志2复制到s3时,这时提交之后宕机了。s5再次当选,s5在索引2的日志是更新的,所以s5能够当选,s5会把s1、s2、s3上的日志2删除换成s3,这就有问题出现了,日志2已经被多数机器接收,不能改被改变或者删除。
raft规定不能直接提交之前任期的日志,日志2的提交只能跟着日志4的复制、提交执行,这样当日志2被提交时,日志4已经被复制到大多数机器上了,这时s5不能当选。
raft日志压缩
raft成员变更
raft采用2阶段的方式处理成员变更。
当领导者收到成员变更的请求时,会写入一条特别的日志,然后将这个日志复制给跟随者。这个特别的日志包含老的配置和新的配置,我们用co&cn表示(co表示老的配置,cn表示新的配置)。在co&cn提交之前,集群可能用co配置处理、也可能用co&cn配置处理(集群崩溃,新领导人既可能只有co,也可能有co&cn)。
co&cn提交之后,集群就只会用co&cn配置处理了(这时集群再宕机了,新领导人也肯定有co&cn日志)。co&cn提交之后,领导人再创建一条cn的日志复制给集群就好了。
不管日志有没有提交,集群里的机器都只会按收到的日志的最新的配置处理(日志复制、选举、投票等)。
有三个问题,集群添加的新机器可能还没有日志数据,这段时间内这个新机器可能是不可用的。集群的领导人可能不是新配置里的一员。移除不在cn里的服务器可能会扰乱集群。