MongoDB复制集由一组保存相同数据的mongod进程组成,其中只能有一个节点接受写入,这个节点被称为主节点,其余节点通过复制主节点的oplog保持数据一致。当主节点宕机、网络不可达或管理员主动执行维护操作时,复制集需要重新选出一个主节点来恢复写能力。这个选举过程并不只是随机挑一个从节点,而是结合了Raft协议思想,通过多数派投票、节点数据新旧比较和任期递增来完成。理解选举机制,有助于合理设计节点数量、配置优先级,并在故障时快速判断恢复路径。

选举触发条件与节点角色
选举并不是时刻发生的,通常只在复制集没有主节点或主节点需要切换时触发。首次初始化复制集时,所有具备选举资格的节点都会尝试发起选举。运行过程中,主节点失去与多数投票成员的联系、主节点进程崩溃、管理员执行rs.stepDown()命令,或者修改复制集配置导致当前主节点不再满足要求,都可能触发新的选举。
理解选举首先要分清节点角色。常规数据节点分为主节点和从节点,主节点负责写入,从节点同步oplog并可在选举时投票。MongoDB还支持仲裁节点、隐藏节点、延迟节点和无投票权节点。仲裁节点只有投票权,不存储数据,也不参与数据复制,适合在偶数个数据节点场景中充当多数派判决者。隐藏节点不可见,优先用于备份或分析。延迟节点保留一段时间的延迟数据,可防止误操作。隐藏节点和延迟节点默认都有投票权,但可以设置votes:0去掉投票权。复制集最多有50个成员,但只有7个成员可以拥有投票权。
下面用一个三成员配置说明。三个节点中两个为数据节点,一个为仲裁节点,这样可以在主节点故障时仍然获得2票多数。
rs.initiate({
_id: "rs0",
members: [
{ _id: 0, host: "db1:27017", priority: 2 },
{ _id: 1, host: "db2:27017", priority: 1 },
{ _id: 2, host: "db3:27017", arbiterOnly: true }
]
})
初始化完成后,db1因为优先级最高且数据最新,通常会当选为主节点。其他成员会持续向主节点发送心跳,如果主节点在electionTimeoutMillis时间内没有响应,它们就会认为主节点不可达,从而启动新一轮选举。
选举流程与日志新鲜度比较
选举的核心是多数派投票。每个候选节点在发起选举时,会把自己的任期编号加一,然后向其他所有具备投票权的成员发送投票请求。收到请求的节点会做两类关键判断:一是当前任期是否有效,如果请求中的任期小于自己已经见过的任期,会直接拒绝;二是候选者的oplog是否足够新,也就是说候选者最后应用的oplog时间戳是否大于或等于自己的。如果候选者数据比自己旧,投票节点就会拒绝,防止已提交数据被回滚。
多数派规则要求候选者必须获得投票节点总数的一半以上选票,而不是简单地获得在线节点的一半以上。假设一个三节点的复制集,投票成员为2个数据节点和1个仲裁节点,那么必须获得2票才能当选。如果网络发生分区,主节点被隔离在一个只有1票的分区中,它无法联系到其他2个投票成员,就会主动降级为从节点,而另一个分区由于有2票,可以选出新主。这个机制保证了不可能同时出现两个主节点,避免脑裂。
同一轮选举中,如果多个节点同时成为候选者,可能出现票数分散,没有任何节点获得多数票。MongoDB采用随机超时机制让各节点的选举启动时间错开。例如一个从节点的electionTimeoutMillis到期后先发起选举,另一个节点稍后发起,先发起者若已经获得多数票成为主节点,后发起者会转为投票给新主。下面的查询可以观察节点状态和oplog时间戳。
rs.status().members.map(m => ({
name: m.name,
stateStr: m.stateStr,
optime: m.optime,
priority: m.priority,
votes: m.votes
}))
从输出中可以比较各个成员的optime和stateStr。如果某个候选者长时间停留在RECOVERING或STARTUP2,可能表示它的数据还没有追上,无法获得足够投票。这种数据新鲜度检查是选举可靠性的重要保障,它避免了一个拥有旧数据的节点在故障转移后覆盖新节点已经确认的写入。
优先级、超时参数与选举调优
复制集配置中有多个参数会影响选举结果和恢复速度。priority是节点当选主节点的权重,数值越大越容易被选为候选者或主节点。默认所有数据节点优先级为1,取值范围0到1000。优先级为0的节点永远不会成为主节点,适合放在备份机房或作为纯读取节点。仲裁节点没有优先级概念。手动调整优先级可以控制故障发生时的接替顺序。
检测故障的速度主要由心跳和选举超时决定。heartbeatIntervalMillis控制节点之间发送心跳的间隔,默认通常是2000毫秒。electionTimeoutMillis表示从节点在一个心跳周期后如果没有收到主节点响应,等待多久再发起选举。这个值不能设置得太小,否则在短暂网络抖动时会频繁触发选举,导致不必要的写中断。相反,如果设置得过大,故障恢复时间会变长。需要根据网络质量和业务对可用性的要求折中调整。
cfg = rs.conf() cfg.settings.electionTimeoutMillis = 5000 cfg.settings.heartbeatIntervalMillis = 2000 cfg.members[0].priority = 2 cfg.members[2].votes = 1 rs.reconfig(cfg)
修改复制集配置后,主节点可能会因为不再满足最高优先级而主动降级,触发新的选举。如果业务需要频繁维护某个节点,可以先把它的priority调整为0,避免它在维护窗口中被选为主。多数派计算还要注意,设置votes:0的节点不参与多数派,它们即使不可达也不会影响主节点在位。利用这一点可以把只读副本从多数派计算中剥离,降低跨机房网络链路的故障影响。
常见选举失败场景与排查
在实际运维中,复制集有时会长时间没有主节点,表现为写操作报No primary错误。最常见的原因是网络分区导致没有任何分区持有投票节点多数。例如三个节点分布在两个机房,机房之间断网后,拥有2个节点的机房可以选出主节点,另一个机房只有一个节点,无法形成多数,它会持续尝试选举但不成功。这种场景下应该优先检查网络链路,而不是反复重启服务。
另一个常见问题是oplog差距过大。候选节点因为停机时间较长,数据落后于存活节点,选举请求会被拒绝。此时需要先让该节点通过初始同步或增量的方式追平数据,或从数据最新的节点中挑选候选。优先级配置不当也会触发意外切换。比如把priority设置为较高的节点放在网络不稳定的区域,主节点可能会在恢复后重新抢占角色,造成频繁切换。排查时可以查看日志中的选举事件,典型字段包括ELECTION、term、candidateId,并结合rs.status()中每个成员的health、stateStr和lastHeartbeatMessage判断具体原因。
如果使用了仲裁节点,还需要确认仲裁节点与其他数据节点的网络连通性。仲裁节点虽然不复制数据,但在多数派投票中非常关键。一个三节点复制集中,如果仲裁节点宕机,剩下两个数据节点刚好各一票,主节点仍可继续在位,因为主节点在选举时获得过两票,但一旦主节点也故障,剩下的那个数据节点只有一票,无法单独当选,整个复制集会进入无主状态。因此仲裁节点也应当部署在网络条件稳定的位置,并纳入监控。