多数派的定义

分布式系统里(比如 Etcd、ZooKeeper、Consul、Redis Sentinel、MongoDB ReplicaSet 等)一般采用多数派原则。

多数派原则(majority)

只有多数派节点(majority of voting nodes)同意,写入才算成功;只有多数派在线,集群才能选出主节点。少数派分区永远不能参与选举,也不能投票

选举(leader election)和一致性仲裁(quorum)更高效、更可靠。在最少节点的情况下达成最高的可用性。

多数派的定义是:

1
quorum = floor(N/2) + 1
  1. 多数派选举需要 >50% 的票数
  2. 奇数节点能减少浪费的资源
  3. 偶数节点不会提高容错能力
  4. 奇数节点能避免“平票”(leader 选不出来)

奇数投票节点确保一致性?

(1)避免平票,保证能选出 Primary

奇数节点让投票在任何情况下都能明确区分“多数派”和“少数派”,而不会出现 50/50 的对等分裂,从而避免平票,让 leader 一定能选出来。

主节点选举依赖多数票。示例对比:

✔ 使用 3 个投票节点(奇数)

1
2
3
4
5
6
节点:A, B, C   (3 voting)
majority = 2

假设 A 挂了:
剩下 B、C 仍有 2 票
可继续选主

系统仍然 有主节点 → 可继续读写

✘ 使用 4 个投票节点(偶数)

1
2
节点:A, B, C, D
majority = 3

即使挂掉一个:

1
剩 3 个节点 → 需要 3 票 → 刚好全部在线

任何一个节点再出现网络抖动:

1
2
只剩 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 冲突规则保证只有一个候选人存活

🚫 投票只能投给一个候选人,不能“投给自己”当候选人

  1. Raft 用的是 随机选举超时,每个节点有一个随机 electionTimeout,这个时间到达之前,不会发起选举,所以每轮只有 1 个节点最先到时间。

  2. 即使时间因为延迟等原因差不多,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. 节点故障属于网络分区的一种特殊情况

节点故障有两种典型表现:

  1. 节点宕机(进程挂了、机器断电)
  2. 节点卡死(CPU 100%,没有响应)

在这两种情况下:

  • 其他节点发 RPC 给它
  • 超时,一直收不到回复

效果 ≈ 这个节点被隔离到了一个别人都到不了的区域

这就是一个非常典型的 “一对多” 网络分区:

1
2
正常区域:A、B、C
隔离区域:D(单独一块)

从多数派算法看:

  • D 和大家无法通信
  • A、B、C 仍然构成多数派,继续运行

所以“节点故障”其实就是“单节点网络分区”

2. 网络分区不一定是节点故障

网络分区可以更复杂,比如:

情况 1:分成 2-2 的两个组(4 节点例子)

1
A B | C D
  • 两边互相看不到
  • 每一边都认为另一边“宕机”

但实际上大家都还在,只是通信断了。

情况 2:单链路损坏

1
2
3
A -- C
A X B
B -- C

A 和 B 互相到不了,但都能到 C。

这不是任何节点故障,只是链路坏了。

3. 多数派协议看的是“能否通信”,不看“为什么无法通信”

对于 Raft / Paxos / ZK 而言:

情况 协议视角 等价吗
节点宕机 收不到心跳 → 不可达 和“单节点分区”完全一样
节点高负载卡死 超时 → 不可达 等价
网络 cable 掉了 不可达 等价
路由异常导致某节点看不到其他节点 不可达 等价
整个机房掉电 大部分节点不可达 等价

所以:对分布式一致性算法来说,“原因不重要”,只看通信结果。

4. 为什么要把它们视为同一种情况?

因为共识算法的核心目标是:

保证只有多数派区域能够选主、写数据。

不管是因为:

  • 节点宕机
  • 火灾
  • 网卡坏了
  • 交换机掉线
  • 延迟过高
  • TCP 拥塞太严重
  • 机器卡死不回包
  • 某段链路丢包

最终的结果都是节点之间无法通信。所以从一致性协议角度:

节点故障”和“网络分区”都是“不可达”。
它们的后果与处理方式完全一样。

拓展问题

MongoDB 如何判断一个节点“失联”?(基于心跳、electionTimeout)

投票节点(voting node)和数据节点(non-voting node)的区别?

如果主节点没有 step down 会怎样?(真实脑裂案例)

“网络分裂情况下的时间线”描述一次真实的 MongoDB 选举过程