Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

Vinllen Chen


但行好事,莫问前程

MongoDB中的集群选举

  本文首先介绍Raft协议的选举基本流程,然后记录mongodb主从选举的过程,主要参考Replication Internals,本文更相当于一个读书笔记性质,方便后面自己回顾。
  说明:本文的step up表示节点成为primary,step down表示节点退出primary。

0. Raft协议中的选举流程

  我觉得这里应该介绍一下Raft协议的基本选举内容,因为MongoDB的选举是根据Raft协议来的:

  1. 如果没有master,每个结点会主动发起选举,根据超时机制,超时时间到了才会发起。每个结点的超时时间在150ms~300ms之间随机。初始term都为0。
  2. 假设有个A结点先超时了,那么他将发起投票,将Term+1,对自己投票+1,然后发起选举给别的节点。
  3. 其他节点B, C收到消息后,发现对应的term还没有参与过投票,那么进行投票并返回给A。于此同时,B, C的选举超时时间重置。
  4. A如果收到了majority节点的选举投票,则成为master。
  5. master发送Append Entries给所有的slave,每次心跳超时才会发送。
  6. 每次slave收到,都会给master回复,并重置本地的选举超时时间(这样如果一直收到心跳,就一直不会发起选举)
  7. 假设master不发送这个消息了(Append Entries),那么表示master挂了,slave等待选举超时时间到期后,重新发起选举,重走1~6流程。当然此时,A, B, C节点只有2个了。term变成了2。majority是为了保证同一时刻至多只有一个主。
  8. 那如果多个节点同时到期发起选举,怎么办?比如下图的A和C同时将term+1=4,然后发起选举。

1. Step Up

1.1 节点什么情况下会发起选举:

  1. 选举超时(默认10s)后,没有看到primary节点
  2. 当前节点的优先级大于primary,会发起抢占。抢占的时间由优先级的差来区别,优先级越高,抢占的越快。(priority takeover
  3. 新选举的primary正在追赶最新applied的OpTime,但只有全部追上了,这个primary才能允许写。但是如果这个追时间很久,超过了catchUpTakeoverDelayMillis(默认30s),那么将会发起新的选举。(catchup takeover
  4. replSetStepUp命令发起选举,用于内部命令和测试,希望用户不要使用。
  5. 节点通过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投票,以下情况不会投票:

  1. 消息中的term小于当前节点的term。
  2. config版本不匹配
  3. 副本集的名字不匹配
  4. 当前节点的lastApply位点大于投票节点的lastApply为蒂娜
  5. 当前term已经投过票了
  6. 当前节点是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,但是需要全部满足以下条件:

  1. 如果设置了force,则必须等待waitUntil时间。
  2. lastApplied位点同步到大多数节点。
  3. 至少有一个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:

  1. primary节点或得到一个更大的term的消息。
  2. primary节点没办法连接到多数节点。不一定是直接连接,可以是间接连接即可,比如A-B不同,但是A-C通,C-B通,那么默认A-B也通。这里连通传递相当于一个最小生成树。
  3. 用户通过replSetReconfig发起reconfig命令。
  4. 通过心跳知道有新的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/


About the author

vinllen chen

Beijing, China

格物致知


Discussions

comments powered by Disqus