1. etcd 存储架构概述

在深入容量水位评估之前,必须先理解 etcd 的存储架构。etcd 是一个强一致性的分布式键值存储系统,其存储组件并非单一模块,而是由 Raft 共识层、状态机层 和 持久化层 三层协同构成。每一层对容量和性能的影响各不相同,水位评估必须分层进行。

1.1 etcd 整体架构

etcd 的架构可以抽象为三个核心层次:

层次 核心组件 主要职责 容量关注点
网络层 gRPC Server / Client 处理客户端请求、节点间 Raft 消息 连接数、请求 QPS、网络带宽
共识层 Raft 模块 日志复制、Leader 选举、成员变更 Raft 日志条目数、未提交日志量
存储层 状态机 (MVCC) + 后端 (Backend) 键值读写、事务处理、持久化 数据总大小、历史版本数、boltdb 文件大小
关键认知: 容量水位评估不能只看 boltdb 文件大小。Raft 日志的堆积、MVCC 历史版本的膨胀,往往比数据本身更快耗尽磁盘空间或导致性能下降。

1.2 Raft 协议与存储的关系

Raft 协议是 etcd 一致性的基石,它与存储组件的交互遵循以下流程:

  1. 客户端写入:请求到达 Leader 节点。
  2. 日志追加:Leader 将操作封装为 Raft 日志条目,追加到本地日志存储(WAL)。
  3. 日志复制:Leader 并行向 Follower 发送日志条目,等待多数派确认。
  4. 日志提交:收到多数派确认后,Leader 将日志标记为已提交(Committed)。
  5. 状态机应用:Leader 将已提交的日志应用到内存状态机(treeIndex)和持久化存储(boltdb)。
容量启示: Raft 日志在未提交前会持续占用 WAL 磁盘空间。如果 Follower 节点响应慢或网络分区,Leader 的 WAL 会快速膨胀,这是水位评估中容易被忽略的“隐形容量消耗”。

1.3 存储组件分层

etcd 的存储组件从逻辑到物理分为三层,每一层都有独立的容量特征:

  • 内存层 (treeIndex):B-tree 结构,维护所有 key 到版本号的映射。容量受 key 数量影响,通常占用内存较小,但 key 数量超过百万级时内存压力显著。
  • 逻辑层 (MVCC):多版本并发控制,每个 key 的每次修改都会生成一个新版本。历史版本不主动清理时,会无限膨胀。
  • 物理层 (Backend / boltdb):基于 BoltDB 的 B+tree 持久化存储。单个 boltdb 文件包含所有 key-value 数据及索引,文件大小直接对应磁盘容量。
etcd 存储组件分层架构 客户端 (gRPC) Raft 共识层 日志复制 / Leader 选举 / 成员变更 状态机层 (MVCC) 多版本并发控制 / treeIndex (B-tree) / 版本管理 持久化层 (Backend) BoltDB (B+tree) / WAL (预写日志) / Snapshot 磁盘文件:member/snap/db + member/wal/ 容量水位评估分层关注点 Raft 层:WAL 日志堆积量、未提交条目数 MVCC 层:历史版本数、treeIndex 内存占用 BoltDB 层:db 文件大小、page 分配效率、空闲页碎片

1.4 各层容量特征对比

为了更清晰地理解容量评估的切入点,下表对比了三层存储组件的关键容量指标:

存储层 容量上限 主要消耗源 水位告警阈值 扩容/治理手段
Raft WAL 默认 5GB (可配置) 未提交日志、慢 Follower 导致的日志堆积 WAL 目录使用率 > 80% 调整 --max-wal-files;排查 Follower 延迟
MVCC treeIndex 受内存限制 (建议 < 8GB) Key 总数 (每个 key 约 200-300 bytes) 内存占用 > 2GB 或 key 数 > 100 万 压缩历史版本 (compact);删除无用 key
BoltDB 官方推荐 < 8GB,极限 < 100GB 数据总量 + 历史版本 + 索引 db 文件 > 4GB 或 > 磁盘 70% 碎片整理 (defrag);调整 --auto-compaction-retention
常见误区: 很多运维人员只监控 boltdb 文件大小,忽略了 WAL 日志堆积。当集群出现网络抖动时,WAL 可能在几分钟内写满磁盘,导致 etcd 进程 OOM 或节点被踢出集群。水位评估必须覆盖 WAL 和 BoltDB 两个维度。

1.5 存储组件协作流程

一次完整的写入请求在存储组件中的流转路径如下:

  1. 客户端 → Leader:Put 请求到达 Leader 的 gRPC 端点。
  2. Leader → WAL:将操作序列化为 Raft 日志条目,写入本地 WAL 文件。
  3. Leader → Follower:通过 Raft 协议将日志条目复制到 Follower 节点。
  4. Follower → WAL:Follower 收到日志后也写入本地 WAL。
  5. 多数派确认 → 提交:Leader 收到多数派确认后,将日志标记为已提交。
  6. 提交 → 状态机:Leader 将已提交日志应用到 treeIndex 和 boltdb。
  7. 返回客户端:写入成功响应返回给客户端。

这个流程中,步骤 2 和步骤 6 是容量消耗的关键点:WAL 写入是顺序 I/O,速度快但会持续占用磁盘;boltdb 写入是随机 I/O,且每次写入都会产生新的 MVCC 版本,导致 boltdb 文件持续增长。

容量评估实践: 在生产环境中,建议使用 etcdctl endpoint status 和 etcdctl endpoint db-size 命令定期采集各节点的 db 大小和 WAL 目录大小,并结合 --auto-compaction-retention 参数设置合理的历史版本保留策略,从源头控制容量膨胀。