2. etcd 核心架构回顾:Raft 协议、MVCC、Watch 机制、存储引擎
在深入压测与调度瓶颈诊断之前,必须重新校准对 etcd 内部四大核心模块的理解。本章从 Raft 共识、MVCC 并发控制、Watch 事件推送、bbolt 存储引擎四个维度,梳理关键路径与性能敏感点。
2.1 Raft 协议:共识层与写放大
etcd 使用 Raft 协议保证多节点数据一致性。每个写请求必须经过 Leader 节点,并完成以下步骤:
- Propose:Client 向 Leader 提交提案。
- Append:Leader 将提案写入自己的 WAL 并广播给 Follower。
- Commit:Leader 收到多数派(quorum)确认后,提交日志并应用到状态机。
- Reply:Leader 返回成功给 Client。
性能关键点:Raft 的写延迟 = 网络 RTT(多数派) + 磁盘 fsync 延迟。在跨 AZ 部署或磁盘 IO 抖动时,P99 延迟会显著上升。
下表总结了 Raft 各阶段对性能的影响:
| 阶段 | 主要开销 | 压测关注指标 |
|---|---|---|
| Propose | 序列化 + 网络发送 | Leader 接收 QPS |
| Append | WAL fsync + 网络广播 | 磁盘 IOPS / 网络带宽 |
| Commit | 等待多数派 ACK | P99 延迟 / 心跳超时 |
| Apply | 状态机写入 bbolt | bbolt 事务吞吐 |
2.2 MVCC:并发控制与历史版本
etcd 通过 MVCC(Multi-Version Concurrency Control)实现无锁读。每个 key 的每次修改都会生成一个新版本,读请求可以指定版本号,避免读写冲突。
- revision:全局单调递增的主版本号,每次事务提交 +1。
- keyValue:存储 key、value、create_revision、mod_revision、version。
- 历史版本:旧版本不会立即删除,而是等待 compaction 回收。
压测陷阱:大量历史版本堆积会导致 bbolt 空间膨胀、读放大。如果 compaction 配置不当(如 auto-compaction-retention 过小),会频繁触发压缩,消耗 CPU 和 IO。
2.3 Watch 机制:事件流与推送模型
Watch 是 etcd 的核心订阅能力。Client 通过 gRPC 流监听 key 或前缀的变化,etcd 在事务提交后异步推送事件。
- WatchableStore:维护 watcher 列表,按 key 和 revision 索引。
- 事件合并:同一 revision 内的多次修改合并为一个事件。
- 历史事件回放:如果 Client 指定的 revision 已被 compaction,则返回 ErrCompacted。
压测关注点:Watch 数量过多(>10k)会导致内存膨胀和事件分发延迟。每个 watcher 在事件触发时都会产生一次 gRPC 消息,高吞吐场景下可能成为瓶颈。
2.4 存储引擎:bbolt 与 B+ 树
etcd 使用 bbolt(原 boltdb)作为底层存储引擎,这是一个基于 B+ 树的嵌入式 KV 数据库。
| 特性 | 说明 | 压测影响 |
|---|---|---|
| 事务模型 | 读写事务互斥,同一时刻只允许一个写事务 | 写并发受限,高 QPS 下易出现锁竞争 |
| 页管理 | B+ 树节点以页(4KB/8KB)为单位分配 | 大量随机写入导致页分裂和碎片 |
| mmap | 通过内存映射文件访问数据 | 内存占用随数据量线性增长 |
| freelist | 管理空闲页,支持事务回滚 | 频繁 compaction 导致 freelist 膨胀 |
2.5 核心路径总结
一个 etcd 写请求的完整路径如下:
Client → gRPC → Raft (Propose) → WAL (fsync) → Raft (Commit) →
State Machine → MVCC (版本分配) → bbolt (B+树写入) → Watch (事件推送) → gRPC → Client