21.10 Bully 与多数派选主

一句话

Bully 把选主简化成 “ID 最大者当选”,代价是网络分区时会出现两个 leader;所以工程上真正用的是 “多数派投票 + 任期”(Raft 那套),本质是给 Bully 补上 “没有多数派就不许当选” 这条闸。

Bully 算法

步 动作
① 节点发现 leader 无响应 → 向所有 ID 更大 的节点发 Election
② 收到 Election 的更大节点回 OK,并自己发起选举
③ 若一段时间内没收到任何 OK → 自己当选,广播 Coordinator
④ 收到 Coordinator 的节点承认新 leader
指标 表现
消息量 最坏 O(N²)(所有人同时发起)
收敛 快(ID 最大者天然胜出)
容错前提 假设只有崩溃故障、网络不分
分区行为 每个分区各选一个 leader(双主)

多数派选主:加一条 “人数不够不许当选”

规则 作用
候选者需要拿到 ⌈(N+1)/2⌉ 票 任意两个多数派必相交,保证同一任期只有一个 leader
任期(term)单调递增 旧 leader 的请求被拒绝
日志至少一样新 只有拥有最新已提交日志的节点才能当选(见 21.5 Raft:选主)
租约/心跳 减少频繁选举,但不能替代 quorum
维度 Bully 多数派(Raft 式)
消息量 O(N²) 最坏 O(N) 每轮
分区安全 不安全(双主) 安全(少数派选不出)
可用性 分区时两边都能 “选主” 少数派不可用(正确行为)
实现复杂度 低 中

代码:多数派选主的两个必要条件

// 条件一:拿到多数派投票才能当选
if votes < (len(members)+1)/2 { return ErrNoQuorum }

// 条件二:候选者日志至少和任何投票者一样新(Raft 的选举限制)
func (c *Candidate) canVoteFor(cand) bool {
    return cand.lastTerm > c.lastTerm ||
        (cand.lastTerm == c.lastTerm && cand.lastIndex >= c.lastIndex)
}

// 条件三(工程):上任后先提交一条空日志,宣告新任期并 fencing 旧主
if err := c.appendNoop(); err != nil { return err }

反模式

反模式 后果
用 Bully 做生产选主 网络抖动/分区时双主,数据分叉
以 “心跳超时” 作为唯一判据 慢节点被误判,频繁切换
选主成功但不做 fencing 旧 leader 恢复后继续写(见 21.11 领导者租约)
忽略时钟 租约与超时依赖时钟,漂移会导致误判(见 23.1 物理时钟与漂移)

| 选主后不做日志追赶 | 新主缺数据,立刻又切走 | | 集群偶数节点 | 无多数派优势,可用性更差 |

三条铁律

🔑 没有 quorum 的选主必然脑裂;Bully 只能用于 “网络可靠” 的封闭环境。

🔑 任期 + 日志新旧是 “谁能当选” 的第二道闸,缺一不可。

🔑 选主之后还要 fencing,否则旧主仍是威胁。

自检

Q1 Bully 在网络分区时为什么会产生双主?

▸ 答案:因为它的当选条件是 “ID 更大者都没响应”,而分区让 “没响应” 无法与 “已崩溃” 区分:两个分区各自的最大 ID 节点都认为对方挂了,于是各自当选。多数派投票要求 “拿到过半票”,少数派分区凑不够票,因此选不出 leader。

Q2 多数派选主为什么要求 “日志至少一样新”?

▸ 答案:为了不丢已提交的写:如果当选者的日志落后,它会覆盖掉已经提交但自己没收到的条目。要求 “候选者日志不落后于任何投票者”,配合 “多数派相交”,保证新 leader 一定包含所有已提交条目(见 21.7 Raft:安全性证明)。

Q3 选举频繁(抖动)怎么缓解?

▸ 答案:三个手段:随机化超时(避免同时发起)、pre-vote(先探测能否赢再真正加任期,避免被分区节点打断)、租约(leader 在租期内不必担心被挑战)。注意租约依赖时钟,漂移要留余量。

Q4 Bully 还有适用场景吗?

▸ 答案:有:节点少、网络可靠、允许短暂双主的封闭系统(教学、嵌入式、某些硬件仲裁)。它的价值是 “简单直观”,而不是 “生产可用”。

Q5 选主完成后的第一件事应该是什么?

▸ 答案:** fencing 旧 leader + 确认提交位点**:新 leader 先提交一条 “任期开始” 的空日志(Raft 的做法),并让存储侧拒绝旧任期的写(token/租约)。否则 “旧 leader 复活继续写” 会直接破坏一致性。

Q7 为什么 “日志至少一样新” 这条限制能防止丢数据?

▸ 答案:因为它保证当选者包含所有已提交的条目:已提交意味着多数派持有,而当选者也需要多数派投票,两个多数派必相交,交集中的节点不会投票给日志更旧的候选者。于是 “新 leader 一定不缺已提交数据”,这是 Raft 安全性的核心一环(见 21.7 Raft:安全性证明)。

关联