多数派的定义
分布式系统里(比如 Etcd、ZooKeeper、Consul、Redis Sentinel、MongoDB ReplicaSet 等)一般采用多数派原则。
多数派原则(majority):
只有多数派节点(majority of voting nodes)同意,写入才算成功;只有多数派在线,集群才能选出主节点。少数派分区永远不能参与选举,也不能投票
选举(leader election)和一致性仲裁(quorum)更高效、更可靠。在最少节点的情况下达成最高的可用性。
多数派的定义是:
1 | quorum = floor(N/2) + 1 |
- 多数派选举需要 >50% 的票数
- 奇数节点能减少浪费的资源
- 偶数节点不会提高容错能力
- 奇数节点能避免“平票”(leader 选不出来)
奇数投票节点确保一致性?
(1)避免平票,保证能选出 Primary
奇数节点让投票在任何情况下都能明确区分“多数派”和“少数派”,而不会出现 50/50 的对等分裂,从而避免平票,让 leader 一定能选出来。
主节点选举依赖多数票。示例对比:
✔ 使用 3 个投票节点(奇数)!
1 | 节点:A, B, C (3 voting) |
系统仍然 有主节点 → 可继续读写。
✘ 使用 4 个投票节点(偶数)
1 | 节点:A, B, C, D |
即使挂掉一个:
1 | 剩 3 个节点 → 需要 3 票 → 刚好全部在线 |
任何一个节点再出现网络抖动:
1 | 只剩 2 票 < majority(3) |
➡ 多花了一个节点却不能提升容错能力。
(2)写入确保不会产生“脑裂(split-brain)”
写操作要求被多数投票节点确认才能成功:
举例:3 voting nodes → majority = 2
1 | primary 成功写入两个节点 → 数据安全 |
这样即使 primary 发生故障,另一个节点晋升为 primary 时:
- 它一定已经拥有最新多数写入的数据
- 旧主不会带着未提交的数据乱写
➡ 奇数投票节点保证:只有一个分区能达到 majority,另一个分区不能产生脏写。
(3)最优容错/资源比
建议投票节点必须为奇数(3、5、7)
是因为偶数节点没有增加容错能力,纯浪费资源。
| 节点数 | 多数派 | 容忍故障数 | 说明 |
|---|---|---|---|
| 3 | 2 | 1 | 常用最小集群 |
| 4 | 3 | 1 | 和 3 节点一样,但多浪费一个节点 |
| 5 | 3 | 2 | 容错能力提高了 |
➡ 所以为了不浪费资源,同样想增加容错能力时,会直接跳到 5 节点,而不是 4 节点。
- primary 必须获得多数投票 → 避免选举平票
- 写操作必须多数节点确认 → 避免多主和脑裂
- 奇数节点最大化可用性,最小化资源浪费
因此 MongoDB 通过 majority 写入 + 奇数投票节点 + 单主架构
确保副本集中只有一个合法主节点,并且数据是强一致的。
主节点失联
注意:网络分裂是“触发选举”的原因
副本集的节点之间依赖心跳(heartbeat)互相确认是否“能连通”。如果 Primary 无法与多数投票节点通信,它必须自动放弃主节点身份。自动降级(step down)变成 Secondary、不参与选举、不参与投票。
否则:它写的数据无法同步给其他节点,其他节点可能已经选出新的 Primary,两个 “主” 会产生冲突写,破坏一致性。
- 能连通 = 存活节点
- 不能连通 = 故障节点
故障节点 自动被投票系统忽略。只有与大多数节点相互可达的节点才是合法的投票者。选举只发生在多数派分区中。
当主节点恢复,发现已经有新的 Primary,不会争抢主,会自动作为 Secondary 追赶 oplog(同步数据),完全不会参与正在进行的选举。
选举新主节点
Phase 1:请求成为候选人(申请 term) Phase 2:投票
在任何一个 term(选举轮次)里只能有一个候选人。
🚫 他们无法同时成为候选人(协议禁止)
🚫 electionTimeout 随机化让“同时发起”几乎不可能,即使一轮失败,也不会卡住,而是发起下一轮选举投票
🚫 即使同时尝试,term 冲突规则保证只有一个候选人存活
🚫 投票只能投给一个候选人,不能“投给自己”当候选人
Raft 用的是 随机选举超时,每个节点有一个随机 electionTimeout,这个时间到达之前,不会发起选举,所以每轮只有 1 个节点最先到时间。
即使时间因为延迟等原因差不多,Raft 规定节点只能给一个候选人投票。
- B 向 C 请求投票
- C 向 B 请求投票
两者收到对方的 term 后,会比较 term 大小:谁的 term 更大,另一个就自动放弃本次竞选(step down),票投给 term 更大的那一方。所以最终仍然会选出一个 Primary。这叫 term 冲突决议。
什么情况下会卡住?
多数派本身断裂:A /断网/ B C,集群仍然正常运行(在 B、C 那一边)
如果分裂成:A B | C,AB都在选自己,C又断网→无法投票,本轮失败 → 下一轮继续随机超时 → 必然选出一个。
有多数派 → 一定能选出来
没多数派 → 协议自动重试,直到选出(除非网络把多数派都断开,否则不会选不出来。)
节点故障和网络分区
本质一样,但表现方式不同。
节点故障 = 无法与该节点通信
网络分区 = 一部分节点无法与另一部分通信
→ 从 Raft / Paxos / Zookeeper 的选主协议视角来看:两者都是“通信不可达”,本质等价。
1. 节点故障属于网络分区的一种特殊情况
节点故障有两种典型表现:
- 节点宕机(进程挂了、机器断电)
- 节点卡死(CPU 100%,没有响应)
在这两种情况下:
- 其他节点发 RPC 给它
- 超时,一直收不到回复
效果 ≈ 这个节点被隔离到了一个别人都到不了的区域
这就是一个非常典型的 “一对多” 网络分区:
1 | 正常区域:A、B、C |
从多数派算法看:
- D 和大家无法通信
- A、B、C 仍然构成多数派,继续运行
所以“节点故障”其实就是“单节点网络分区”。
2. 网络分区不一定是节点故障
网络分区可以更复杂,比如:
情况 1:分成 2-2 的两个组(4 节点例子)
1 | A B | C D |
- 两边互相看不到
- 每一边都认为另一边“宕机”
但实际上大家都还在,只是通信断了。
情况 2:单链路损坏
1 | A -- C |
A 和 B 互相到不了,但都能到 C。
这不是任何节点故障,只是链路坏了。
3. 多数派协议看的是“能否通信”,不看“为什么无法通信”
对于 Raft / Paxos / ZK 而言:
| 情况 | 协议视角 | 等价吗 |
|---|---|---|
| 节点宕机 | 收不到心跳 → 不可达 | 和“单节点分区”完全一样 |
| 节点高负载卡死 | 超时 → 不可达 | 等价 |
| 网络 cable 掉了 | 不可达 | 等价 |
| 路由异常导致某节点看不到其他节点 | 不可达 | 等价 |
| 整个机房掉电 | 大部分节点不可达 | 等价 |
所以:对分布式一致性算法来说,“原因不重要”,只看通信结果。
4. 为什么要把它们视为同一种情况?
因为共识算法的核心目标是:
保证只有多数派区域能够选主、写数据。
不管是因为:
- 节点宕机
- 火灾
- 网卡坏了
- 交换机掉线
- 延迟过高
- TCP 拥塞太严重
- 机器卡死不回包
- 某段链路丢包
最终的结果都是节点之间无法通信。所以从一致性协议角度:
节点故障”和“网络分区”都是“不可达”。
它们的后果与处理方式完全一样。
拓展问题
MongoDB 如何判断一个节点“失联”?(基于心跳、electionTimeout)
投票节点(voting node)和数据节点(non-voting node)的区别?
如果主节点没有 step down 会怎样?(真实脑裂案例)
“网络分裂情况下的时间线”描述一次真实的 MongoDB 选举过程