分布式系统最本质的功能是通过提供数据冗余存储能力,提高系统的持久度和可用性,这也是分布式NOSQL系统最复杂的一块,笔者准备从整体架构、选举、数据同步、故障处理等几个维度逐步揭示MongoDB副本集功能的原理。
上次介绍了MongoDB复制集的总体架构,今天来介绍一下复制集的选举机制。
Raft的选举机制
作为分布式数据库,MongoDB的复制集协议来源于分布式一致性协议Raft,在分析MongoDB复制集之前,我们先简单看一下Raft的选举机制:
Raft协议中包括三种角色:
领导人(Leader):向其他节点发送心跳,负责处理客户端请求,并同步数据到其他节点。若收到比自己任期更新的心跳,降级为追随者。
候选人(Candidate):给自己投票发起选举,获得大多数投票后成为领导人,收到领导人心跳转换为追随者。
追随者(Follower):响应来自候选人和领导人的请求,如果在选举超时时间内没有收到领导人心跳,转换为候选人。

Raft算法将时间划分成任意不同长度的任期(term)。任期用连续数字表示,每一个任期从一次选举(election)开始。
一个或多个候选人试图成为领导人,赢得选举的节点在该任期内担任领导人,候选人需要获得大多数选票才能当选,所以会出现选票被瓜分,没有选出领导人的情况,这时会开始新一个任期,并马上开始新的选举。Raft算法保证在一个任期内至少要有一个领导人。

MongoDB选举机制
简单分析了Raft选举机制后,我们来看看源于Raft的MongoDB复制集成员状态机:

MongoDB复制集将其成员状态映射到不同的Raft角色上:
Startup2,Rollback,Recovering,Secondary对应Follower角色,可以投票们可以成为候选人;
Secondary同是也是候选人,可以发起选举;
Primary为领导人。
我们先来看一个常规的复制集启动流程:
节点初始化为Startup状态,加载了复制集配置后启动心跳,状态切换为Startup2,成为Follower。
节点开始初始化数据同步,状态切换为Recovering,当数据同步到集群的最小一致性时间戳(minValid)后,切换到Secondary。
当Secondary/Follower的心跳线程发现在一定时间后(选举超时时间),当前复制集视图中还没有primary/Leader时,就会切换为Secondary/Candidate发起选举。
选举分为两个阶段:预选举和正式选举
预选举(dry-run election):Candidate构造replSetVoteRequest命令发送到其他节点,试探自己能否赢得选举,这个过程不增加任期,如果有primary收到replSetVoteRequest发现任期比自身的新,就会开始stepdown。
正式选举(real election):Candidate赢得dry-run election后,就会发起正式选举,首先增加任期并给自己投票,然后发起replSetVoteRequest命令发送到其他节点,获得大多数投票后成为Leader。
作为Follower节点在收到replSetVoteRequest后,会刷新自己的任期,然后判断是否给候选人投票,投票时会判断:
任期是否新
协议版本是否匹配
复制集名称是否匹配
本节点最近提交的optime是否旧于候选人的optime
在该任期内是否已经投过票
可以看出在选举过程中,有两类重要的消息交互请求:心跳消息、投票消息,我们来分析一下MongoDB对这两个请求的实现:
心跳请求:
| 参数 | 描述 |
| term | 任期 |
| setName | 复制集名称 |
| heartbeatVersion | 心跳协议版本 |
| senderHost | 发送者ip端口 |
| ... | ... |
心跳响应:
| 参数 | 描述 |
| term | 对端任期 |
| setName | 对端复制集名称 |
| state | 对端复制集成员状态 |
| primaryId | primaryId |
| appliedOpTime | 对端最后提交的oplog时间戳 |
| ... | ... |
投票请求:
| 参数 | 描述 |
| term | 任期 |
| setName | 复制集名称 |
| candidateIndex | 发起投票的节点在复制集视图中的id |
| configversion | 复制集协议版本 |
| lastDurableOpTime | 最后提交并持久化的oplog的时间戳 |
| dryRun | 是否为预选举 |
投票响应:
| 参数 | 描述 |
| term | term |
| setName | 复制集名称 |
| candidateIndex | 发起投票的节点在复制集视图中的id |
| staleTerm | 投票人是否有更新的任期,候选人的任期过期 |
| votes | 投票结果 |
| primaryVote | 当前复制集中primary的投票结果,只在预选举(dryRun)有意义 |
| lastDurableOpTime | 最后提交并持久化的oplog的时间戳 |
总体来说MongoDB复制集就是一个Raft一致性协议的具体实现,其选举在Raft基础上加入了优先级概念,让主从选举更灵活。
未完,待续。。。
.......................... END ..........................

长按二维码关注




