3. etcd 架构总览:gRPC API层、Raft共识模块、WAL日志、MVCC存储引擎、Watch机制
etcd 是一个高度可靠的分布式键值存储系统,其内部架构围绕“强一致性”与“高性能”两大目标进行设计。本章将自上而下剖析 etcd 的核心模块:gRPC API 层、Raft 共识模块、WAL 日志、MVCC 存储引擎以及Watch 机制。理解这五大模块的职责与协作关系,是掌握 etcd 压测与调优的基础。
架构视角:etcd 的架构可以抽象为“共识层 + 存储层 + 监听层”。Raft 模块保证日志的全局有序复制,WAL 提供持久化保障,MVCC 实现并发控制与历史版本查询,Watch 机制则支撑了分布式系统中的事件驱动模式。
3.1 整体架构分层
etcd 的代码模块遵循清晰的层级划分,从用户请求到数据落盘,依次经过以下核心组件:
3.2 gRPC API 层:统一的通信入口
etcd 的所有客户端交互均通过 gRPC 协议完成。gRPC 基于 HTTP/2,支持双向流、多路复用和高效的 Protobuf 序列化。API 层定义了以下核心服务:
| 服务名称 | 主要 RPC 方法 | 功能描述 |
|---|---|---|
| KV | Range / Put / Delete / Txn / Compact | 键值数据的读写、事务与压缩 |
| Watch | CreateWatcher / Watch (流式) | 监听键或前缀的变化事件 |
| Lease | Grant / Revoke / KeepAlive | 租约管理,用于 TTL 自动过期 |
| Cluster | MemberAdd / MemberRemove / MemberList | 集群成员变更与状态查询 |
| Maintenance | Alarm / Defragment / Snapshot / Status | 节点维护、碎片整理与快照 |
| Auth | Authenticate / RoleAdd / UserAdd | 身份认证与权限控制 |
压测关注点:gRPC 的流式 Watch 和 KeepAlive 会占用大量长连接资源。压测时需关注连接数、请求吞吐量以及 Protobuf 序列化/反序列化的 CPU 开销。
3.3 Raft 共识模块:一致性的核心
Raft 模块是 etcd 实现分布式一致性的基石。它将客户端请求转化为日志条目,并通过以下机制确保集群中所有节点达成一致:
- Leader 选举:当集群启动或 Leader 失联时,Follower 节点通过超时机制发起选举,获得多数票的节点成为新 Leader。
- 日志复制:Leader 接收客户端写请求,将日志条目追加到本地,并并行发送
AppendEntriesRPC 给所有 Follower。当条目被多数节点持久化后,即视为已提交。 - 安全性:Raft 保证只有拥有最新日志的节点才能成为 Leader,且已提交的条目不会被覆盖。
压测陷阱:Raft 的日志复制是串行提交的。当写入压力过大时,Leader 的
AppendEntries 广播会成为瓶颈。压测中应关注 etcd_server_proposals_failed_total 和 etcd_server_leader_changes_seen_total 指标,以判断集群是否因过载而发生 Leader 频繁切换。
3.4 WAL 日志:持久化的基石
WAL (Write-Ahead Log) 是 etcd 保证数据不丢失的关键机制。所有 Raft 日志条目在应用到状态机之前,必须先持久化到 WAL 文件中。WAL 的设计遵循以下原则:
- 顺序追加:WAL 文件只支持追加写入,避免随机 I/O,最大化磁盘吞吐量。
- 日志分段:WAL 文件按大小分段(默认 64MB),旧段在快照完成后可被清理。
- 崩溃恢复:节点重启时,通过回放 WAL 中的日志条目重建内存状态机,确保数据不丢失。
- 校验和:每个日志条目都包含 CRC 校验,用于检测数据损坏。
| WAL 文件属性 | 说明 | 压测影响 |
|---|---|---|
| 写入模式 | 顺序追加,fsync 频率由 --sync-interval 控制 |
高频 fsync 降低吞吐,但提高数据安全性 |
| 文件分段 | 默认 64MB 一个文件,编号递增 | 大量小文件可能影响文件系统性能 |
| 快照关联 | 快照生成后,之前的 WAL 文件可被删除 | 快照频率影响磁盘空间回收与恢复时间 |
3.5 MVCC 存储引擎:多版本并发控制
etcd 的存储引擎基于 boltdb(一个嵌入式 Go 数据库),并在此基础上实现了 MVCC (Multi-Version Concurrency Control)。MVCC 的核心思想是:每次写操作不直接覆盖旧值,而是生成一个新版本。每个版本由 revision(全局递增的主版本号)唯一标识。
- 索引树:etcd 在内存中维护一棵 B+tree(
treeIndex),用于快速根据 key 查找对应的 revision 列表。 - 后端存储:实际的 key-value 数据以
(revision, key) -> value的形式存储在 boltdb 中。 - 压缩 (Compaction):定期删除历史版本,回收存储空间。压缩操作会删除小于指定 revision 的所有旧版本数据。
压测关注点:MVCC 的版本链长度直接影响读性能。如果未及时执行压缩,历史版本堆积会导致 B+tree 查询变慢,并增加磁盘空间占用。压测时应监控
etcd_mvcc_db_total_size_in_bytes 和 etcd_mvcc_hash_revision。
3.6 Watch 机制:事件驱动的桥梁
Watch 机制是 etcd 实现服务发现、配置变更通知的核心。客户端可以通过 Watch 监听一个 key 或一个前缀,当被监听的 key 发生变化时,etcd 会实时推送事件。
- 流式推送:Watch 基于 gRPC 双向流,服务端持续推送事件,客户端无需轮询。
- 事件类型:
PUT(创建/更新)和DELETE(删除)。 - 历史回放:客户端可以指定一个起始 revision,etcd 会从该 revision 开始回放历史事件,确保客户端不会错过任何变更。
- Watch 存储:etcd 在内存中维护一个 Watch 树,用于高效匹配 key 与监听者。
压测陷阱:大量 Watch 连接会消耗 etcd 的内存和 CPU。每个 Watch 流都需要在服务端维护一个 goroutine 和缓冲区。如果客户端消费速度跟不上事件产生速度,可能导致缓冲区溢出或内存压力。压测时应监控
etcd_watch_events_total 和 etcd_watch_dropped_total。
3.7 模块协作流程总结
一个完整的 etcd 写请求(例如 Put(key, value))在内部经过以下流水线:
- gRPC API 层 接收客户端请求,解析 Protobuf,调用 KV 服务。
- Raft 共识模块 将请求封装为日志条目,通过 Leader 广播给 Follower。
- WAL 日志 将日志条目顺序追加到磁盘文件,并执行
fsync确保持久化。 - 当日志条目被多数节点确认后,Raft 模块将其标记为已提交。
- MVCC 存储引擎 将已提交的日志应用到 boltdb,生成新版本数据。
- Watch 机制 检测到 key 变更,将事件推送给所有监听该 key 的客户端。
压测启示:上述流程中,WAL 的 fsync 和 Raft 的多数派确认是主要的延迟瓶颈。压测时可通过调整
--snapshot-count、--sync-interval 以及磁盘类型(SSD vs HDD)来观察对吞吐和延迟的影响。