7Database Internals

共识算法

分布式共识

PaxosRaftZAB共识与复制

🎯 AI 时代的学习建议:不要只读理论。理解这一章的设计动机(为什么需要这个概念?)和边界条件(什么情况下会失效?)。当 AI 生成的代码涉及这些概念时,你能判断它是否用对了场景。AI 能帮你总结内容,但无法替代你亲手阅读、思考、建立直觉的过程。

第7章:共识算法(Consensus Algorithms)

一、导读

1.1 本章学习目标

第7章讲解分布式系统中的共识算法——如何让多个节点在可能发生故障的情况下就某个值达成一致。共识是分布式系统中最基础也最困难的问题之一,Paxos、Raft、ZAB 等算法是现代分布式数据库的基石。

本章核心学习目标:

  • 理解共识问题的本质:为什么在异步网络中达成共识如此困难?
  • 掌握 Raft 算法的完整流程:Leader 选举、日志复制、安全性保证
  • 理解 Paxos 的基本思想:提案的两阶段提交
  • 掌握 ZAB 协议:ZooKeeper 的共识算法与 Raft 的区别
  • 理解 FLP 不可能性定理:异步网络中确定性共识算法的局限

1.2 为什么本章如此重要

共识算法是强一致性分布式系统的核心。从 etcd 到 Consul,从 TiKV 到 CockroachDB,几乎所有需要强一致性的分布式系统都基于 Raft 或 Paxos。

理解共识算法,能帮助你:

  • 理解分布式数据库的选主、复制和故障恢复机制
  • 诊断分布式系统中的脑裂、数据不一致等问题
  • 设计高可用的分布式服务

1.3 核心问题引导

为什么共识问题在异步网络中如此困难?FLP 不可能性定理说了什么?

Raft 如何保证"已提交的日志条目不会丢失"?

Raft 的 Leader 选举中,为什么需要"随机超时"?

Paxos 和 Raft 的核心区别是什么?为什么 Raft 更容易理解和实现?


二、核心概念详解

2.1 共识问题的定义

共识问题要求多个进程就一个值达成一致,并满足以下性质:

  • 一致性(Agreement):所有非故障进程决定相同的值
  • 有效性(Validity):决定的值必须是某个进程提出的
  • 终止性(Termination):所有非故障进程最终都会做出决定

FLP 不可能性定理(1985):在异步网络中,即使只有一个进程可能故障,也不存在确定性的共识算法能同时满足一致性、有效性和终止性。

FLP 的含义:
- 异步网络: 消息延迟可以是任意长(但没有上限)
- 确定性算法: 给定相同输入,总是产生相同输出
- 结论: 必须放弃其中一个条件
  → 实际方案: 使用随机化算法 或 假设部分同步(有延迟上限但很大)

2.2 Raft 算法

Raft 是一种易于理解的共识算法,它将共识问题分解为三个相对独立的子问题:

2.2.1 Leader 选举

Raft 节点状态:
- Leader: 处理所有客户端请求
- Follower: 被动响应 Leader 的请求
- Candidate: 正在参与选举

选举流程:
1. 所有节点初始为 Follower
2. Follower 在超时时间内没收到 Leader 心跳 → 变为 Candidate
3. Candidate 发起投票(RequestVote RPC),term + 1
4. 获得多数票的 Candidate 成为新 Leader
5. 新 Leader 发送心跳,确立权威

随机超时:
- 每个节点的选举超时时间随机(如 150ms-300ms)
- 减少多个 Candidate 同时竞选的概率(分裂投票)
- 如果分裂投票 → 等待下一个超时 → 重试

安全性保证:
- 只有拥有最新日志的节点才能当选(投票请求中包含日志信息)
- 每个节点在一个 term 中只投一票(先收到先投)

2.2.2 日志复制

日志复制流程:
1. Client 发送写请求到 Leader
2. Leader 将条目追加到本地日志
3. Leader 并行发送 AppendEntries RPC 到所有 Follower
4. Leader 等待多数确认(包括自己)
5. Leader 提交条目,应用到状态机
6. Leader 通知 Follower 提交

日志匹配性质:
- 如果两个日志在某个 index 有相同的 term,则它们在该 index 的条目相同
- 如果两个日志在某个 index 有相同的条目,则它们在所有 index < 该 index 的条目相同

日志一致性:
- 如果 Leader 提交了某个条目,则未来的 Leader 日志中一定包含该条目
- 通过投票限制(只有日志最新的节点才能当选)保证

2.2.3 安全性保证

Raft 的安全性(核心不变量):
1. Election Safety: 一个 term 最多一个 Leader
2. Leader Append-Only: Leader 不会覆盖或删除日志,只追加
3. Log Matching: 同上 index+term → 同内容
4. Leader Completeness: 已提交的条目一定在未来 Leader 的日志中
5. State Machine Safety: 节点应用了某个条目后,不会有其他节点应用不同条目

关键机制:
- AppendEntries 一致性检查: Leader 发送 AppendEntries 时包含 prevLogIndex+prevLogTerm
  Follower 检查本地日志是否匹配 → 不匹配则拒绝
- 投票限制: RequestVote 中,Candidate 的日志必须至少和投票者一样新

2.3 Paxos 算法

Paxos 是 Leslie Lamport 提出的经典共识算法,比 Raft 更早但更难理解。

Basic Paxos (单值共识):

Phase 1 - Prepare:
1. Proposer 发送 Prepare(n) 到 Acceptor (n 为提案编号)
2. Acceptor 收到后,如果 n 大于之前见过的任何编号:
   → 回复 Promise(n, prev_accepted_value, prev_accepted_number)
   → 承诺不再接受编号 < n 的提案

Phase 2 - Accept:
3. Proposer 收到多数 Promise 后:
   → 如果有 Acceptor 返回了已接受的值 → 使用该值
   → 否则 → 使用自己提议的值
   → 发送 Accept(n, value) 到 Acceptor
4. Acceptor 收到 Accept(n, value):
   → 如果没有收到编号 > n 的 Prepare → 接受该值

Multi-Paxos (多值共识):
- 固定一个 Leader (长期不变的 Proposer)
- 跳过 Phase 1 (因为 Leader 已经获得了长期承诺)
- 直接执行 Phase 2 → 效率接近 Raft

2.4 ZAB 协议

ZAB(ZooKeeper Atomic Broadcast)是 ZooKeeper 使用的共识协议,与 Raft 类似但有一些区别:

ZAB vs Raft:

相同点:
- 都使用 Leader 模型
- 都通过两阶段提交保证原子性
- 都保证已提交的数据不丢失

不同点:
- ZAB 支持 Leader 主动让位(Raft 只能通过新选举替换)
- ZAB 使用 epoch 而非 term(epoch 只在 Leader 变更时递增)
- ZAB 的恢复协议更复杂(需要保证之前 Leader 的已提交事务被所有 Follower 接收)
- ZAB 支持客户端从 Follower 读取(Raft 默认只从 Leader 读)

三、实战案例

3.1 案例一:Raft Leader 选举模拟

python
import random
import time
from enum import Enum

class NodeState(Enum):
    FOLLOWER = 0
    CANDIDATE = 1
    LEADER = 2

class RaftNode:
    def __init__(self, node_id, peers):
        self.node_id = node_id
        self.peers = peers
        self.state = NodeState.FOLLOWER
        self.current_term = 0
        self.voted_for = None
        self.log = []
        self.election_timeout = random.uniform(0.15, 0.3)
        self.last_heartbeat = time.time()
        self.votes_received = 0

    def tick(self):
        if self.state == NodeState.LEADER:
            self.send_heartbeats()
        elif self.state in (NodeState.FOLLOWER, NodeState.CANDIDATE):
            if time.time() - self.last_heartbeat > self.election_timeout:
                self.start_election()

    def start_election(self):
        self.state = NodeState.CANDIDATE
        self.current_term += 1
        self.voted_for = self.node_id
        self.votes_received = 1  # 投给自己
        self.election_timeout = random.uniform(0.15, 0.3)
        print(f"Node {self.node_id}: Starting election for term {self.current_term}")
        self.request_votes()

    def request_votes(self):
        for peer in self.peers:
            if peer.receive_vote_request(self.current_term, self.node_id, len(self.log)):
                self.votes_received += 1
        if self.votes_received > len(self.peers) // 2:
            self.become_leader()

    def become_leader(self):
        self.state = NodeState.LEADER
        print(f"Node {self.node_id}: Became leader for term {self.current_term}")
        self.send_heartbeats()

    def receive_vote_request(self, term, candidate_id, log_length):
        if term > self.current_term:
            self.current_term = term
            self.voted_for = None
            self.state = NodeState.FOLLOWER
        if term >= self.current_term and (self.voted_for is None or self.voted_for == candidate_id):
            if log_length >= len(self.log):
                self.voted_for = candidate_id
                self.last_heartbeat = time.time()
                return True
        return False

    def send_heartbeats(self):
        for peer in self.peers:
            peer.receive_heartbeat(self.current_term)

    def receive_heartbeat(self, term):
        if term >= self.current_term:
            self.current_term = term
            self.state = NodeState.FOLLOWER
            self.last_heartbeat = time.time()

# 模拟 5 节点集群
nodes = []
for i in range(5):
    node = RaftNode(i, [])
    nodes.append(node)
for node in nodes:
    node.peers = [n for n in nodes if n != node]

# 模拟选举
for node in nodes:
    node.tick()

# 输出:
# Node X: Starting election for term 1
# Node X: Became leader for term 1

四、本章小结

FLP 不可能性定理表明异步网络中不存在确定性的共识算法

Raft 将共识分解为 Leader 选举、日志复制和安全性三个子问题,易于理解和实现

Paxos 是经典的共识算法,Multi-Paxos 通过固定 Leader 优化性能

ZAB 是 ZooKeeper 的共识协议,与 Raft 类似但支持更多特性


五、思考与练习

思考题

为什么 Raft 的 Leader 选举需要随机超时?如果所有节点使用相同的超时会怎样?

Raft 如何保证"已提交的日志不会丢失"?关键机制是什么?

在网络分区场景下,Raft 的少数派分区中的 Leader 能做什么?

编程练习

实现 Raft 的日志复制功能,模拟 Leader 接收写入并复制到 Follower 的过程。

模拟网络分区场景:将 5 个节点分为 2+3 两组,观察 Leader 选举和日志复制的行为。


7/7共识算法
0%