2. etcd 架构速览:Raft 共识算法、WAL 日志、MVCC、boltdb 存储引擎、gRPC API 接口
在深入压测与瓶颈定位之前,必须建立对 etcd 核心架构的清晰认知。etcd 是一个高度一致的分布式键值存储系统,其架构可抽象为五个关键层次:共识层(Raft)、持久化层(WAL)、并发控制层(MVCC)、存储引擎层(boltdb)以及通信层(gRPC)。每一层都是潜在的性能瓶颈点,也是 Prometheus 指标监控的核心对象。
2.1 整体架构与数据流
下图展示了 etcd 的核心组件及其交互流程。一个写入请求从客户端发出到最终持久化,需要依次经过 gRPC 接口、Raft 共识模块、WAL 日志、MVCC 层,最后写入 boltdb 存储引擎。
2.2 Raft 共识算法
Raft 是 etcd 实现强一致性的核心。它将分布式共识问题分解为三个子问题:Leader 选举、日志复制和安全性。etcd 集群通常由 3 或 5 个节点组成,每个节点处于 Leader、Follower 或 Candidate 三种状态之一。
- Leader 选举: 当 Follower 在选举超时内未收到 Leader 心跳,则转为 Candidate 并发起选举。获得多数票的节点成为新 Leader。
- 日志复制: 所有写请求必须由 Leader 处理。Leader 将日志条目(Entry)并行复制到所有 Follower,并在收到多数确认后提交。
- 安全性: Raft 保证已提交的日志条目不会被覆盖,且只有日志最新的节点才能当选 Leader。
| 角色 | 职责 | 压测关注点 |
|---|---|---|
| Leader | 处理所有客户端写请求;管理日志复制;发送心跳 | CPU 负载、网络带宽、RTT 延迟 |
| Follower | 响应 Leader 的日志复制请求;转发读请求(如需线性一致性) | 磁盘 I/O(WAL 写入)、网络接收 |
| Candidate | 选举超时后发起投票;仅在选举期间存在 | 选举延迟、集群可用性 |
etcd_server_leader_changes_seen_total 指标频繁增长,说明集群 Leader 不稳定,通常由网络分区或磁盘 I/O 抖动引起。此时写吞吐量会急剧下降。
2.3 WAL 日志(Write-Ahead Logging)
WAL 是 etcd 持久化的第一道防线。所有 Raft 日志条目在应用到状态机之前,必须先顺序写入 WAL 文件。WAL 的设计目标是崩溃恢复:即使节点宕机,重启后也能通过回放 WAL 重建内存状态。
- 顺序写入: WAL 采用追加写模式,避免随机 I/O,最大化磁盘吞吐量。
- 文件分段: 单个 WAL 文件达到阈值(默认 64MB)后,会创建新文件。旧文件在数据被快照覆盖后可被清理。
- 同步策略: 通过
--wal-sync参数控制每次写入是否调用fsync。同步写入保证数据安全,但会显著增加延迟。
# WAL 相关关键 Prometheus 指标
etcd_disk_wal_fsync_duration_seconds_count # fsync 调用次数
etcd_disk_wal_fsync_duration_seconds_sum # fsync 总耗时
etcd_server_proposals_failed_total # 提案失败次数(可能由 WAL 写入慢导致)
etcd_disk_wal_fsync_duration_seconds 的 P99 值。
2.4 MVCC 并发控制
etcd 使用多版本并发控制(MVCC)来支持并发读写而不加锁。每个键的每次修改都会生成一个新的版本号(revision),旧版本数据保留一段时间供历史查询或 Watch 使用。
- 版本管理: 全局单调递增的
revision主版本号,以及每个键的mod_revision和create_revision。 - 事务隔离: 基于
revision的快照读,保证读写不冲突。写事务通过乐观锁(比较-交换模式)检测冲突。 - 历史版本压缩: 通过
--auto-compaction-retention定期压缩旧版本,防止存储空间无限增长。
| 概念 | 说明 | 压测影响 |
|---|---|---|
| revision | 全局版本号,每次写操作递增 | 高并发写入时 revision 增长快,影响 boltdb 索引大小 |
| 历史版本 | 旧版本数据保留在 boltdb 中 | 未及时压缩会导致存储膨胀和读放大 |
| Watch | 基于 revision 的事件流 | 大量 Watch 客户端会增加 MVCC 层的事件分发开销 |
2.5 boltdb 存储引擎
boltdb 是 etcd 的底层持久化存储引擎,基于 B+ 树实现,支持事务和内存映射文件(mmap)。etcd 将 MVCC 的键值对序列化后写入 boltdb 的 bucket 中。
- B+ 树结构: 所有数据按 key 排序存储在 B+ 树中,支持高效的范围查询和顺序遍历。
- 事务模型: 支持读写事务,但同一时刻只允许一个读写事务(写串行化),读事务可以并发。
- 内存映射: boltdb 将数据库文件映射到进程地址空间,读写操作直接操作内存,由操作系统负责刷盘。
# boltdb 相关关键 Prometheus 指标
etcd_mvcc_db_total_size_in_bytes # boltdb 文件大小
etcd_mvcc_db_open_read_transactions # 当前打开的读事务数
etcd_server_backend_commit_duration_seconds # boltdb 事务提交耗时
etcd_server_backend_commit_duration_seconds 的 P99 超过 1 秒时,说明 boltdb 事务提交成为瓶颈。常见原因包括:磁盘 I/O 饱和、B+ 树分裂频繁、或历史版本过多导致索引膨胀。
2.6 gRPC API 接口
etcd 对外暴露 gRPC API,支持 KV 服务(Put/Get/Delete/Txn)、Watch 服务、Lease 服务和 Cluster/Maintenance 服务。gRPC 基于 HTTP/2 协议,支持双向流、多路复用和头部压缩。
- 请求模型: 客户端通过 Protobuf 序列化请求,gRPC 将请求路由到对应的 etcd 服务处理函数。
- 流式 Watch: Watch 客户端通过 gRPC 流持续接收事件,避免了轮询开销。
- 拦截器: etcd 内置了 Prometheus 指标拦截器,自动记录每个 gRPC 方法的调用次数、延迟和错误码。
| 服务 | 主要 RPC | 关键 Prometheus 指标 |
|---|---|---|
| KV | Put / Get / Delete / Txn | etcd_grpc_requests_total、etcd_grpc_server_handling_seconds |
| Watch | Create / Watch | etcd_grpc_requests_total{method="Watch"}、etcd_server_watch_events_total |
| Lease | Grant / Revoke / KeepAlive | etcd_server_lease_expired_total |
| Cluster | MemberAdd / MemberRemove / MemberList | etcd_server_health_failures |
grpcurl 或 etcd 自带的 benchmark 工具直接发送 gRPC 请求,可以绕过 etcdctl 的序列化开销,更准确地测量 etcd 服务端性能。监控 etcd_grpc_server_handling_seconds 的延迟分布,是定位 gRPC 层瓶颈的直接手段。
2.7 架构层次总结
下表汇总了各层的核心职责、性能敏感点以及对应的 Prometheus 监控指标,方便在压测时快速对照排查。
| 架构层 | 核心职责 | 性能敏感点 | 关键 Prometheus 指标 |
|---|---|---|---|
| gRPC API | 协议解析、请求路由、流管理 | 连接数、请求大小、序列化开销 | etcd_grpc_server_handling_seconds |
| Raft | 共识达成、日志复制、Leader 选举 | 网络延迟、心跳间隔、选举超时 | etcd_server_leader_changes_seen_total |
| WAL | 顺序日志写入、崩溃恢复 | 磁盘 fsync 延迟、写入带宽 | etcd_disk_wal_fsync_duration_seconds |
| MVCC | 多版本管理、事务隔离、Watch 事件 | 版本数、压缩频率、Watch 客户端数 | etcd_mvcc_put_total |
| boltdb | B+ 树存储、事务提交、内存映射 | 磁盘 I/O、B+ 树分裂、事务冲突 | etcd_server_backend_commit_duration_seconds |