---
title: 共识在实际系统中的实践
url: https://doc.liz6.com/distributed-systems/02-consensus-protocols/03-consensus-in-practice
locale: zh
area: distributed-systems
tags:
- distributed-systems
- 共识协议
date: 2026-06-30
modified: 2026-07-11
description: etcd、Consul、ZooKeeper——三个实际系统中同一个共识问题的三种工程解法。Raft+boltdb、Raft+Gossip、ZAB,共识协议是核心但不是全部:线性一致性读、租约、watch 机制和成员管理各有一套工程取舍。
---

# 共识在实际系统中的实践

> etcd、Consul、ZooKeeper——三个实际系统中同一个共识问题的三种工程解法。Raft+boltdb、Raft+Gossip、ZAB,共识协议是核心但不是全部:线性一致性读、租约、watch 机制和成员管理各有一套工程取舍。

## etcd

etcd 是最广泛使用的 Raft 实现（Go），Kubernetes 的核心依赖——所有 K8s 对象（Pods, Services, ConfigMaps）都存在 etcd 中。

### 架构

<svg viewBox="0 0 720 300" xmlns="http://www.w3.org/2000/svg" font-family="-apple-system,'Source Han Sans CN','Microsoft YaHei',sans-serif" role="img" aria-label="etcd 架构:gRPC 请求经 Raft 层共识后写入 boltdb,MVCC 管理历史版本">
  <defs><marker id="arr-etcd" markerWidth="10" markerHeight="8" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6 Z" fill="#475569"/></marker></defs>
  <rect width="720" height="300" fill="#ffffff"/>
  <text x="360" y="28" text-anchor="middle" font-size="17" font-weight="700" fill="#1f2933">etcd 架构:gRPC 请求经 Raft 共识写入 boltdb,MVCC 管理历史版本</text>
  <rect x="40" y="100" width="150" height="48" rx="8" fill="#4f46e5"/>
  <text x="115" y="129" text-anchor="middle" font-size="13" font-weight="700" fill="#ffffff">Client gRPC</text>
  <line x1="190" y1="124" x2="226" y2="124" stroke="#475569" stroke-width="1.8" marker-end="url(#arr-etcd)"/>
  <rect x="230" y="60" width="450" height="28" rx="6" fill="#4f46e5"/>
  <text x="455" y="79" text-anchor="middle" font-size="13" font-weight="700" fill="#ffffff">etcd server</text>
  <rect x="242" y="100" width="426" height="40" rx="6" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="256" y="116" font-size="12" font-weight="700" fill="#3730a3">Raft layer</text>
  <text x="256" y="132" font-size="11" fill="#4f46e5">共识 + log replication</text>
  <rect x="242" y="146" width="426" height="40" rx="6" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="256" y="162" font-size="12" font-weight="700" fill="#3730a3">Storage</text>
  <text x="256" y="178" font-size="11" fill="#4f46e5">boltdb(B-tree,持久化)</text>
  <rect x="242" y="192" width="426" height="40" rx="6" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="256" y="208" font-size="12" font-weight="700" fill="#3730a3">MVCC</text>
  <text x="256" y="224" font-size="11" fill="#4f46e5">多版本 key-value(历史 revision)</text>
  <rect x="60" y="248" width="600" height="40" rx="8" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="76" y="272" font-size="12.5" fill="#3730a3">写路径:client → Raft propose → commit → apply to boltdb → response;MVCC 让读可按版本回溯。</text>
</svg>

每个写请求 → Raft propose → commit → apply to boltdb → response。读请求有两种模式：

- **Serializable Read**: 从本地 boltdb 直接读（不经过 Raft）→ 可能返回旧数据（stale）
- **Linearizable Read** (默认): ReadIndex 机制——1) leader 记录当前 commitIndex; 2) 发心跳给多数 follower 确认自己仍是 leader; 3) 等状态机 apply 到 ≥ 该 commitIndex; 4) 读。这保证了读不会看到已被更替的 leader 的数据。

### Watch

etcd 支持 long-poll watch: client 指定 key prefix → server 返回 change events。Kubernetes 的 controller pattern (Informer) 基于 etcd watch 实现。

## Consul

Consul 用 Raft 做服务目录（service catalog）的一致性复制，用 Gossip (SWIM) 做成员发现和健康检查传播——分离了"需要一致性的数据"和"可以最终一致的数据"。

<svg viewBox="0 0 720 320" xmlns="http://www.w3.org/2000/svg" font-family="-apple-system,'Source Han Sans CN','Microsoft YaHei',sans-serif" role="img" aria-label="Consul 架构:Raft 复制强一致的 service catalog,Gossip 传播最终一致的成员状态">
  <defs><marker id="arr-consul" markerWidth="10" markerHeight="8" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6 Z" fill="#475569"/></marker></defs>
  <rect width="720" height="320" fill="#ffffff"/>
  <text x="360" y="28" text-anchor="middle" font-size="17" font-weight="700" fill="#1f2933">Consul:Raft 管强一致数据,Gossip 管最终一致数据</text>
  <rect x="40" y="100" width="150" height="90" rx="8" fill="#4f46e5"/>
  <text x="115" y="150" text-anchor="middle" font-size="13" font-weight="700" fill="#ffffff">Consul Agent</text>
  <line x1="190" y1="128" x2="314" y2="92" stroke="#475569" stroke-width="1.8" marker-end="url(#arr-consul)"/>
  <text x="252" y="102" text-anchor="middle" font-size="11" font-weight="700" fill="#3730a3">Raft</text>
  <rect x="320" y="60" width="360" height="64" rx="8" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="336" y="84" font-size="13" font-weight="700" fill="#3730a3">Consul Server</text>
  <text x="336" y="102" font-size="11" fill="#4f46e5">3-5 个,Raft 复制 service catalog</text>
  <text x="336" y="117" font-size="11" fill="#4f46e5">强一致(谁在提供什么服务)</text>
  <line x1="190" y1="162" x2="314" y2="202" stroke="#475569" stroke-width="1.8" marker-end="url(#arr-consul)"/>
  <text x="252" y="218" text-anchor="middle" font-size="11" font-weight="700" fill="#0f766e">Gossip</text>
  <rect x="320" y="170" width="360" height="64" rx="8" fill="#f0fdfa" stroke="#99f6e4"/>
  <text x="336" y="194" font-size="13" font-weight="700" fill="#115e59">所有 Consul Agents</text>
  <text x="336" y="212" font-size="11" fill="#0f766e">成员发现,健康状态传播</text>
  <text x="336" y="227" font-size="11" fill="#0f766e">最终一致(谁还活着)</text>
  <rect x="60" y="254" width="600" height="50" rx="8" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="76" y="275" font-size="12.5" fill="#3730a3">设计原则:服务注册需要强一致 → Raft;成员存活检测可用最终一致 → Gossip——</text>
  <text x="76" y="293" font-size="12.5" fill="#3730a3">分离"需要一致性的数据"和"可以最终一致的数据"。</text>
</svg>

设计原则：服务注册（谁在提供什么服务）需要强一致 → Raft。成员存活检测（谁还活着）可用最终一致 → Gossip。

## ZooKeeper (ZAB 协议)

ZooKeeper 的 ZAB (ZooKeeper Atomic Broadcast) 协议比 Raft 早 8 年，设计目标不同：

- Raft: 所有 server 的 log **完全相同**
- ZAB: leader 的 log 是权威的，followers 可能 leader 多一些 entries（但可以被 overwrite）

ZAB 的"顺序保证"不是全局 linearizability order 像 Raft，而是 **FIFO client order**——client 的多个请求保持发送顺序，但不同 client 之间没有顺序保证。

### 为什么 ZooKeeper 还活着

ZooKeeper 的先发优势和 Java 生态导致大量遗留系统依赖它（Kafka 旧版本、Hadoop、HBase），但新系统越来越倾向 etcd（更简、Go-native、K8s 依赖）。

## 共识的应用场景

共识不是万能的——它解决的是"多个节点对单个值达成一致"：

```
该用共识:                   不该用共识:
  - Leader election          - 大容量数据复制 (用 gossip)
  - 分布式锁                 - 非 leader-based 的复制
  - 配置中心                 - 健康检查
  - 元数据管理               - 日志聚合
```

大多数系统不需要共识——它们需要的是最终一致性的复制（Gossip, async replication）或者单 master + WAL（大多数 SQL DB 的主从复制）。

## 参考

- **etcd**: github.com/etcd-io/etcd, raft.github.io
- **Consul**: consul.io/docs/architecture
- **ZooKeeper**: "ZooKeeper: Wait-free coordination for Internet-scale systems" (2010)

*Keywords: etcd, Raft, boltdb, Consul, Gossip, ZooKeeper, ZAB, linearizable read, ReadIndex*
