本文首先介绍Raft协议的选举基本流程,然后记录mongodb主从选举的过程,主要参考Replication Internals,本文更相当于一个读书笔记性质,方便后面自己回顾。
说明:本文的step up表示节点成为primary,step down表示节点退出primary。
0. Raft协议中的选举流程
我觉得这里应该介绍一下Raft协议的基本选举内容,因为MongoDB的选举是根据Raft协议来的:
- 如果没有master,每个结点会主动发起选举,根据超时机制,超时时间到了才会发起。每个结点的超时时间在150ms~300ms之间随机。初始term都为0。
- 假设有个A结点先超时了,那么他将发起投票,将Term+1,对自己投票+1,然后发起选举给别的节点。
- 其他节点B, C收到消息后,发现对应的term还没有参与过投票,那么进行投票并返回给A。于此同时,B, C的选举超时时间重置。
- A如果收到了majority节点的选举投票,则成为master。
- master发送Append Entries给所有的slave,每次心跳超时才会发送。
- 每次slave收到,都会给master回复,并重置本地的选举超时时间(这样如果一直收到心跳,就一直不会发起选举)
- 假设master不发送这个消息了(Append Entries),那么表示master挂了,slave等待选举超时时间到期后,重新发起选举,重走1~6流程。当然此时,A, B, C节点只有2个了。term变成了2。majority是为了保证同一时刻至多只有一个主。
- 那如果多个节点同时到期发起选举,怎么办?比如下图的A和C同时将term+1=4,然后发起选举。
1. Step Up
1.1 节点什么情况下会发起选举:
- 选举超时(默认10s)后,没有看到primary节点
- 当前节点的优先级大于primary,会发起抢占。抢占的时间由优先级的差来区别,优先级越高,抢占的越快。(priority takeover)
- 新选举的primary正在追赶最新applied的OpTime,但只有全部追上了,这个primary才能允许写。但是如果这个追时间很久,超过了catchUpTakeoverDelayMillis(默认30s),那么将会发起新的选举。(catchup takeover)
- replSetStepUp命令发起选举,用于内部命令和测试,希望用户不要使用。
- 节点通过replSetStepDown命令step down,那么如果enableElectionHandoff启用,则这个primary会立刻选举一个secondary发起replSetStepUp以接管过primary,而不用等待一次选举超时时间(election handoff)。但如果replSetStepDown添加了force: true参数,或者enableElectionHandoff是false的,只能等待选举超时了才会重新发起选举。
1.2 候选者视角选举过程
1.2.1 Dry-run Election空投环节
一个候选节点首选会发起一轮空投选举,即看看大家的投票情况,并不是真正的选举。首先,候选节点启动VoteRequester使用ScatterGatherRunner发送replSetRequestVotes给每个集群中的节点以查看大家的投票情况。这个候选节点并不会自增term,因为primary如果看到有节点的term大于自身,将会主动step down。这个空投选举的意义是如果候选节点不能获胜,不会没有意义的增加term,primary也不会step down(防止出现候选节点比primary优先级高,但是仍然不能赢得选举的情况,比如没办法获得多数节点的投票,例如网络分区等等)。如果候选节点失败,则会继续作为secondary参与主从复制,成功,则会发起真正的选举。
1.2.1 Real Election真正的选举
如果节点是由election handoff进入step up的,将会跳过dry-run election环节,直接进入real election。
这个环节,候选节点将会首先自增term并且给自己投票,然后启动VoteRequester发送replSetRequestVotes到各个节点。各个节点根据自身情况回复"aye"(同意)或者"nay"(不同意)。候选节点收到了大多数的投票(包括自身),则赢得选举。
1.3 投票者视角选举过程
节点收到了replSetRequestVotes,首先检查自身的term是否是最新的,如果term小于收到的消息里面携带的term,则更新自身的term。然后ReplicationCoordinator询问TopologyCoordinator是否可以为这个votes投票,以下情况不会投票:
- 消息中的term小于当前节点的term。
- config版本不匹配
- 副本集的名字不匹配
- 当前节点的lastApply位点大于投票节点的lastApply为蒂娜
- 当前term已经投过票了
- 当前节点是arbiter,并且可以连接到一个健康的且优先级大于等于候选节点的primary。这个是为了防止网络分区的情况,而arbiter可以同时连接到2个分区里面的节点。
如果当前节点已经投票过了(包括投给自己),则会记录到local.replset.election
表里面,这个表用于发生重启的情况,不会在一个term里投多次。
1.4 过渡到PRIMARY节点
候选者赢得选举,节点状态准备切到PRIMARY。
首先清除当前节点的sync source,并通过心跳告知别的节点自己成为primary。然后,当前节点查看是否需要追上之前的primary。
新primary通过心跳回复知道各个节点lastApply位点。如果新primary的lastApply小于最新的lastApply位点,则标记这个位点为目标位点,并进行追赶。在追赶环境,首先设定超时追赶的超时时间,当这个时间到了,或者已经追赶上了,结束追赶环节。此时,当前节点清除sync source并停掉OplogFetcher。
追赶的sync source跟主从一样,不一定从最新的,或者老的primary上拉取,也可能是链式复制。即使追赶时间到了,当前primary没有完全追上,也不会step down。
无论是否追上,节点这个时候都会进入"drain mode",这个模式标识节点已经切成primary,但是没有apply oplog。此时节点状态已经是PRIMARY。applier不断应用oplog,当已经全部drain完毕,则告知ReplicationCoordinator结束step up环节。此时节点表示可接受写服务。
根据raft协议的要求,当前term无法提交上一个term的数据,但是新的primary会写一个noop oplog以推进上一个term的write到新的commit位点。
最后,primary删除所有临时表,并且恢复所有prepared transactions,丢弃所有in progress状态的transaction,并且记录log成为primary成功。
2. Step Down
step down分为有条件的step down(通常外部发起),和无条件的step down两个部分。
2.1 有条件的step down
节点可以发起replSetStepDown进入step down,但是需要全部满足以下条件:
- 如果设置了force,则必须等待waitUntil时间。
- lastApplied位点同步到大多数节点。
- 至少有一个secondary可以被选举。
接收到replSetStepDown后,节点将会首先抢占RSTL(Replication State Transition Lock,集群状态发生变更的时候抢占的排他锁)。为了达到目的,将会杀掉冲突的操作,抛弃所有unprepared transactions。
接着,节点轮询尝试进入step down。如果force是false,那么检查lastApplied是否被同步到大多数节点,并且至少有一个节点可以被选举。如果force是true,等待waitUntil时间后直接step down。
成功step down以后,释放prepared transaction获得的锁,因为当前节点是一个secondary。
最后,记录一条日志,节点状态变为SECONDARY。
2.2 无条件step down
以下情况会被动触发step down:
- primary节点或得到一个更大的term的消息。
- primary节点没办法连接到多数节点。不一定是直接连接,可以是间接连接即可,比如A-B不同,但是A-C通,C-B通,那么默认A-B也通。这里连通传递相当于一个最小生成树。
- 用户通过replSetReconfig发起reconfig命令。
- 通过心跳知道有新的config version。
无条件step down不需要跟有条件step down一样需要一些先决条件,而是直接step down。当然在step down也会跟有条件step down一样,节点将会首先抢占RSTL。为了达到目的,将会杀掉冲突的操作,抛弃所有unprepared transactions。
2.3 同时step down
这种情况下step down将会失败。无条件的step down将会导致有条件的step down失败。
参考
https://github.com/mongodb/mongo/blob/master/src/mongo/db/repl/README.md#elections
http://thesecretlivesofdata.com/raft/