2. etcd 架构深度解析:Raft共识算法、WAL日志、MVCC机制、存储引擎与快照
etcd 是一个高可用的分布式键值存储系统,其核心架构围绕一致性、持久性和并发控制三大目标设计。本章深入拆解 etcd 的五大核心组件:Raft 共识算法、WAL 日志、MVCC 机制、存储引擎与快照机制,帮助你在压测场景中精准定位性能瓶颈。
2.1 整体架构分层
etcd 的架构可以抽象为三层:客户端层、共识层和存储层。共识层负责保证集群数据一致性,存储层负责数据的持久化与版本管理。
2.2 Raft 共识算法
Raft 是 etcd 实现分布式一致性的核心算法,它将共识问题分解为三个子问题:Leader 选举、日志复制和安全性。etcd 使用 Raft 确保即使部分节点故障,集群仍能对外提供一致的服务。
2.2.1 节点角色与状态转换
每个 etcd 节点在任意时刻处于以下三种角色之一:
| 角色 | 职责 | 关键行为 |
|---|---|---|
| Leader(领导者) | 处理所有客户端写入请求,管理日志复制 | 定期发送心跳(Heartbeat)给 Follower;提交日志条目 |
| Follower(跟随者) | 被动接收 Leader 的日志复制和心跳 | 响应投票请求;如果超时未收到心跳,转为 Candidate |
| Candidate(候选者) | 发起 Leader 选举 | 自增 term(任期号),向其他节点请求投票 |
2.2.2 日志复制流程
当客户端向 Leader 发起写入请求时,Raft 通过以下步骤保证一致性:
- 追加日志:Leader 将请求包装为日志条目(Log Entry),追加到自己的 WAL 中。
- 并行复制:Leader 通过
AppendEntriesRPC 将日志条目并行发送给所有 Follower。 - 等待确认:Leader 等待大多数节点(quorum)确认写入成功。
- 提交应用:一旦多数节点确认,Leader 将日志标记为已提交(Committed),并应用到状态机(BoltDB)。
- 响应客户端:Leader 向客户端返回写入成功。
etcd_disk_wal_fsync_duration_seconds)。
2.3 WAL 日志(Write-Ahead Log)
WAL 是 etcd 持久化的第一道防线。所有数据修改操作在应用到状态机之前,必须先写入 WAL 文件。这保证了即使系统崩溃,也能通过重放 WAL 恢复数据。
2.3.1 WAL 文件结构
WAL 文件由一系列日志记录(Log Record)组成,每条记录包含:
- 类型:如
metadata(元数据)、entry(日志条目)、state(状态快照)等。 - 数据:序列化的 protobuf 消息。
- CRC 校验:用于检测数据损坏。
WAL 文件按段(Segment)管理,每个段默认最大 64MB。当当前段写满后,etcd 会创建新的 WAL 段文件。
2.3.2 写入流程与 fsync
WAL 写入的关键步骤:
1. 序列化日志记录为字节流
2. 写入 WAL 文件缓冲区(Page Cache)
3. 调用 fsync() 强制刷盘(保证持久化)
4. 更新内部写入位置指针
etcd_disk_wal_fsync_duration_seconds_bucket 指标。
2.4 MVCC 机制(多版本并发控制)
etcd v3 使用 MVCC 实现无锁并发读。每次写入操作不会覆盖旧数据,而是生成一个新版本。读操作可以读取指定版本的数据,从而避免读写冲突。
2.4.1 版本号与 Revision
etcd 使用全局单调递增的 revision(修订号)来标识每次修改:
| 概念 | 说明 | 示例 |
|---|---|---|
| 全局 revision | 集群级别的全局计数器,每次写入递增 | revision=1024 |
| key 的版本 | 每个 key 的修改次数,从 1 开始 | key "foo" 的 version=3 |
| create_revision | 该 key 创建时的全局 revision | create_revision=512 |
| mod_revision | 该 key 最后一次修改时的全局 revision | mod_revision=1024 |
2.4.2 数据存储结构
MVCC 在 BoltDB 中维护两个 B+ 树 bucket:
- key bucket:存储 key 到最新版本的映射。
- value bucket:存储 (revision, key) 到 value 的映射。
读操作通过 key bucket 找到最新 revision,再从 value bucket 读取数据。历史版本数据仍然保留,直到被压缩(Compaction)清理。
--auto-compaction-retention 参数(如保留 1 小时的历史版本),并监控 etcd_mvcc_db_total_size_in_bytes。
2.5 存储引擎:BoltDB
etcd v3 使用 BoltDB 作为底层持久化存储引擎。BoltDB 是一个嵌入式、事务性的键值数据库,基于 B+ 树实现。
2.5.1 BoltDB 核心特性
| 特性 | 描述 | 对性能的影响 |
|---|---|---|
| B+ 树索引 | 所有数据按 key 有序存储,支持范围查询 | 范围查询高效,但写入可能触发页分裂 |
| 事务支持 | 支持读写事务,读写事务可并发 | 写事务串行化,长时间写事务会阻塞其他写 |
| 内存映射文件(mmap) | 将数据库文件映射到进程地址空间 | 读操作无需系统调用,但占用虚拟内存 |
| 无 WAL 分离 | BoltDB 自身不维护 WAL,依赖 etcd 的 WAL | etcd 的 WAL 和 BoltDB 文件写入顺序需协调 |
2.5.2 数据库文件与页管理
BoltDB 将数据存储在单个 .db 文件中,文件被划分为固定大小的页(Page,默认 4KB)。B+ 树的每个节点对应一个或多个页。当数据量增长时,B+ 树会分裂和合并页,导致写放大。
--batch-size 或 --batch-interval 参数。
2.6 快照机制(Snapshot)
快照是 etcd 用于加速恢复和日志压缩的关键机制。当 WAL 日志增长到一定大小(默认 10000 条日志条目)时,etcd 会触发快照。
2.6.1 快照生成流程
- 触发条件:WAL 日志条目数超过
--snapshot-count阈值。 - 冻结状态机:将当前 BoltDB 状态序列化为快照数据。
- 写入快照文件:将快照数据写入独立的
.snap文件。 - 截断 WAL:删除快照点之前的所有 WAL 日志条目。
2.6.2 快照与恢复
当新节点加入集群或落后节点追赶日志时,Leader 会发送快照给 Follower。Follower 加载快照后,再应用快照之后的增量 WAL 日志,快速追上集群状态。
| 场景 | 快照的作用 | 性能影响 |
|---|---|---|
| 新节点加入 | 避免重放全部历史 WAL 日志 | 快照传输占用网络带宽 |
| 节点崩溃恢复 | 从最近的快照快速恢复状态 | 快照加载期间节点不可用 |
| WAL 日志压缩 | 删除旧日志,释放磁盘空间 | 快照生成期间 CPU 和 I/O 负载升高 |
--snapshot-count 设置过小(如 1000),会导致频繁快照,影响正常读写性能。建议根据写入吞吐量调整该参数,监控 etcd_server_snap_db_save_total_duration_seconds 指标。
2.7 组件协同与性能关键路径
在压测场景下,理解各组件如何协同工作至关重要。一次完整的写入请求经过以下关键路径:
- 网络接收:gRPC 服务端接收客户端请求。
- Raft 处理:Leader 追加日志到 WAL,并行复制给 Follower。
- WAL fsync:等待磁盘确认写入完成。
- 网络等待:等待 quorum 确认。
- 状态机应用:将日志应用到 BoltDB。
- 响应返回:序列化结果并返回客户端。
其中,WAL fsync 和 网络 RTT 是最常见的瓶颈点。压测时应使用 etcdctl check perf 命令进行基准测试,并配合 Prometheus 指标定位具体瓶颈。
etcd_server_leader_proposals_applied_total 和 etcd_server_proposal_failed_total 指标,快速定位性能瓶颈。