一 SMR
- 副本状态机:每个服务器节点作为状态机,接收相同顺序的操作指令,产生相同的状态变更,构成一组强一致的副本节点。
- Consensus algorithm:共识算法用于保证SMR的节点replicated log一致性,相同commands相同顺序。
-
SMR的应用:大型系统中的容错性问题,例如GFS/HDFS中利用单独的SMR,Chubby/Zookeeper来管理leader选举或者存储配置信息;
二 Paxos
- Paxos概念
Basic Paxos:对于单个决策达成一致,例如 a single replicated log entry;
Multi-Paxos:组合多个Basic Paxos的示例达成一系列的决策,例如 a log; - Paxos问题
1)出了名的难以理解
2)难以用来直接构建实际系统,Lamport描述主要基于basic paxos,对于multi-paxos缺乏细节。 - Paxos实现
Chubby,Google 2007
三 Raft
3.0 基础概念
- Raft 节点状态:leader,follower,candidate
leader:只有一个,响应clients请求,复制给followers;
follower:单纯响应leader和candidate的请求,自己不会发请求;(client contacts,follower重定向到leader)
candidate:选举新leader; -
状态转换
- follower一段时间内未收到leader或candidate的有效心跳包,转变为candidate发起选举;
- candidate在选举时间内收到大部分votes,转变为leader。
candidate在选举时间内发现leader或新term的信息,转变为follower。
candidate选举时间超时,开启新一轮选举过程; - leader向其他节点发送心跳包,使之处于follower状态。
leader发现更高term心跳包,转变为follower;
-
逻辑时间:term
Raft 把时间分割成任意长度的任期,任期用连续的整数标记。
Raft 中的term充当了逻辑时钟的作用,节点term小时会更新至最新term,对于old term的信息拒绝请求;
3.1 Leader election
- follower timeout: follower在一段时间内没有收到leader或candidate的有效RPC,即转为candidate状态,term+1,发起选举。
- 投票规则:每个节点在一个指定的term中,以先到先得的方式最多投票给一个节点。candidate先投给自己,再去请求其它节点的投票。(投票还会比较log信息)
- 3种结果:
(1)Win:candidate得到大多数票,成为leader。随即向其他节点发送RPC,维持leader状态;
(2)Lose:收到leader的RPC,如果term>=自己,转为follower。如果term<自己,拒绝RPC,维持candidate状态;
(3)candidate timeout:启动新一轮投票,回到1;
3.2 Log replication
- Leader accept 客户端请求, append日志项,利用AppendEntries RPCs,将新的log entry replicate到其他follower节点,一旦大多数节点安全复制,该log entry变为committed,每个节点会将committed的log entry apply到状态机中。
-
Safety Replication
Leader维护
nextIndex[]
for ench server, index of next log entry to send to that server(initalized to leader last log entry index +1)
matchIndex[]
for ench server, index of highest log entry known to be replicated on server, initalized 0
AppendEntries RPCs
prevLogIndex
: index of log entry immediately preceding new ones
prevLogTerm
: term of prevLogIndex entry
Leader给follower的AppendEntries RPCs会通过prevLogIndex
和prevLogTerm
来使得follower匹配自己的日志.
Follower发现不匹配后返回false,随后Leader减少nextIndex[i],下一次RPC减小prevLogIndex
和prevLogTerm
,这样最终会找到leader和follower匹配的log entry返回true。
Leader会覆盖follower不同的日志项,将自己的日志项发过来,从而实现状态一致; -
log commit
-
matchIndex[]
,一旦发现多余一半的节点已经匹配到某个index,该index即可commit。 - leader只能commit自己当前term内的log entry,对于先前term的log不能去commit。 (安全性)
leader一旦commit某index,也会造成先前的log entry 都被commit,包含了old leader 复制的log entry。 - leader永远不会修改或删除自己的log entry,选举成功后append-only;
-
3.3 Safety
特性 | 解释 |
---|---|
Election Safety | 对于一个给定的任期号,最多只会有一个领导人被选举出来 |
Leader append-only | 领导人绝对不会删除或者覆盖自己的日志,只会增加 |
Log Matching | 如果两个日志在相同的索引位置的日志条目的任期号相同,那么我们就认为这个日志从头到这个索引位置之间全部完全相同 |
Leader Completeness | 如果某个日志条目在某个任期号中已经被提交,那么这个条目必然出现在更大任期号的所有领导人中 |
状态机 Safety | 如果一个领导人已经在给定的索引值位置的日志条目应用到状态机中,那么其他任何的服务器在这个索引位置不会提交一个不同的日志 |
选举限制
避免场景:落后的follower选举成功为leader,于是覆盖掉原本leader已经commit的内容(此时old leader 为follower)。
Raft通过选举过程中比较日志新不新的方式来使得拥有全部已commit日志项的candidate才有机会成为leader。
Candidate请求投票时会联系大多数节点,对于已经commit的log entry,一定会被比较。而candidate获取到大多数的vote,说明至少和大多数节点一样新,所有committed的log entry肯定已经包含在自己的日志中了。
关于日志谁更新的定义:
如果两份日志的最后条目任期号不同,任期号大的日志更新。
如果两份日志的最后条目任期号相同,那么日志长的日志更新。-
提交之前任期内的log entry
如图的时间序列展示了为什么领导人无法决定对老任期号的日志条目进行提交。leader提交先前term日志会导致错误)
(a) 中,S1 是领导者,部分的复制了索引位置 2 的日志条目。
(b) 中,S1 崩溃了,然后 S5 在任期 3 里通过 S3、S4 和自己的选票赢得选举,然后从客户端接收了一条不一样的日志条目放在了索引 2 处。(选举时S2会拒绝,因为日志不够新)
(c),S5 又崩溃了;S1 重新启动,选举成功,开始复制日志。在这时,来自任期 2 的那条日志已经被复制到了集群中的大多数机器上,但是还没有被提交。
如果 S1 在 (d) 中又崩溃了,S5 可以重新被选举成功(通过来自 S2,S3 和 S4 的选票),然后覆盖了他们在索引 2 处的日志。反之,如果在崩溃之前,S1 把自己主导的新任期里产生的日志条目复制到了大多数机器上,就如 (e) 中那样,那么在后面任期里面这些新的日志条目就会被提交(因为S5 就不可能选举成功)。 这样在同一时刻就同时保证了,之前的所有老的日志条目就会被提交。上图说明的问题:
- leader只可以提交自己term内的日志项,如果尝试提交先前term日志可能会有问题。
(c)中,term为4的leader成功将index2的log entry复制到了大多数节点,如果选择提交term为2的log entry,接下来在(d) 中,commit的log entry依然被覆盖了,违反了 Leader Completeness;
(d) 中S5可以赢得选举是因为S2,S3,S4都没有自己的日志新,log index=2的term为2,而S5为3,但是S1会拒绝,并没有用。 - leader提交自己term内的log entry时,先前term的log entry也会间接的被提交。如(e);
- 一个log entry被复制到大多数节点也有可能会被覆盖。只要C不提交old term内容,C->D是实际可以存在的,并没有问题。问题在于提不提交先前任期的日志项。
- leader只可以提交自己term内的日志项,如果尝试提交先前term日志可能会有问题。
3.4 成员变更(待研究)
-
单步成员变更
单步成员变更之所以安全,是因为如果一次只变更一个成员,新旧配置的 quorum 一定是有交集的,有交集也就代表着不可能出现脑裂的情况
- 联合共识
Joint Consensus成员变更让集群先从旧成员配置Cold切换到一个过渡成员配置,称为联合一致成员配置(Joint Consensus),联合一致成员配置是旧成员配置Cold和新成员配置Cnew 的组合Cold,new,一旦联合一致成员配置Cold,new提交,再切换到新成员配置Cnew 。
TiDB 在 Raft 成员变更上踩的坑
3.5 脑裂问题
- 问题描述
当出现网络分区时,在 Node 5 的 raft lease 任期还没结束的一段时间内,Node 5 仍然认为自己是当前 term 的 leader,但是此时,另外一边分区已经在新的 term 中选出了新的 leader。
如果此时,客户端在新的 leader 上更新了某个值 x,此时是可以更新成功的(因为还是可以复制到多数派)。但是在分区的另一端,此时一个客户端去读取 x 的值,Node 5 还会返回老的值,这样就发生了 stale read。 - 方案
通过 raft 的 leader lease 来解决集群脑裂时的 stale read 问题
@梦工厂 2018.3.19