3. etcd 存储模型:MVCC、Revision、Key-Value 存储、Compact 与 Defrag

etcd 作为云原生控制面的核心数据层,其存储模型直接决定了多租户场景下的隔离能力与性能边界。本章深入剖析 etcd 的底层存储机制,包括多版本并发控制(MVCC)、全局修订号(Revision)、扁平化的 Key-Value 存储结构,以及关键的运维操作 Compact 与 Defrag。理解这些概念是设计仲裁水位隔离策略的前提。

3.1 多版本并发控制(MVCC)

etcd 采用 MVCC 模型来支持高并发读写与历史查询。每次对 Key-Value 的写入(Put)或删除(Delete)操作,etcd 不会原地覆盖旧数据,而是生成一个新的版本记录。这种设计避免了读写冲突,并允许客户端通过指定版本号读取历史数据。

核心机制: 每个 Key 的每一次变更都会在底层存储(BoltDB)中追加一条记录,包含 Key、Value、以及一个全局递增的版本标识。旧版本数据在未被压缩(Compact)前始终可读。
MVCC 版本链示意图 v1 (create) v2 (update) v3 (update) v4 (delete) v5 (create) 说明: • 每次写入(Put)或删除(Delete)都会生成一个新版本。 • 删除操作(v4)会写入一个墓碑标记(tombstone),Key 仍可被查询到。 • 客户端可通过指定 Revision 读取任意历史版本(在 Compact 之前)。

3.2 全局修订号(Revision)

Revision 是 etcd 集群中一个全局单调递增的 64 位整数,每次事务提交(无论涉及多少个 Key)都会递增一次。它是 MVCC 版本链的“时间戳”,也是 etcd 实现 Watch 机制、事务隔离和仲裁水位计算的基础。

Revision 属性 说明 多租户场景意义
全局唯一 整个集群共享一个 Revision 计数器,跨所有 Key 和所有租户。 可作为全局时间轴,用于仲裁水位线的统一标记。
单调递增 每次写入操作(包括事务)都会使 Revision +1。 水位隔离策略可基于 Revision 差值计算租户的“数据活跃度”。
与 Key 绑定 每个 Key 的每个版本都记录其创建时的 Revision(create_revision)。 可用于判断租户数据的“年龄”和“冷热程度”。
Watch 触发 客户端 Watch 基于 Revision 订阅事件,从指定 Revision 开始推送。 水位过高时,Watch 事件堆积可能影响租户隔离性。
实践要点: 在仲裁水位隔离策略中,我们通常使用 current_revision - compacted_revision 来估算集群中未被压缩的历史版本总量。这个差值越大,BoltDB 的存储压力越大,越容易触发仲裁水位告警。

3.3 Key-Value 存储结构

etcd 的存储模型在逻辑上是一个扁平的 Key-Value 空间,所有数据都存储在 BoltDB 的一个 Bucket 中。Key 是字节数组,Value 是包含版本元数据的结构体。

3.3.1 逻辑存储模型

  • Key 空间: 扁平的,无目录层级。前缀查询(如 /registry/pods/)是模拟目录结构的主要方式。
  • Value 结构: 包含 data(用户数据)、create_revision、mod_revision、version(当前版本号,从 1 开始递增)。
  • 删除标记: 删除操作不会立即清除数据,而是写入一个带有 tombstone 标记的版本。

3.3.2 物理存储模型(BoltDB)

etcd 使用 BoltDB(现为 bbolt)作为底层存储引擎。BoltDB 是一个嵌入式、事务性的 KV 存储,数据直接写入内存映射文件。

BoltDB 物理存储结构 BoltDB 文件 (member/snap/db) Bucket: key (主存储桶) Leaf Page (KV 对) Branch Page (索引) Freelist Page (空闲页) Meta Page

3.4 Compact(压缩)

Compact 是 etcd 回收历史版本、释放存储空间的核心操作。它通过指定一个 Revision 阈值,删除所有小于该阈值的旧版本数据。

关键警告: 未及时执行 Compact 会导致 BoltDB 文件持续膨胀,最终触发 etcd 的存储空间配额(quota-backend-bytes)告警,导致集群进入只读模式。这是多租户场景下最常见的仲裁水位超限原因之一。

3.4.1 Compact 操作流程

  1. 触发: 通过 etcdctl 或 API 调用 PUT /v3/kv/compact,指定 revision 参数。
  2. 标记: etcd 在内部记录一个 compacted_revision,表示该 Revision 之前的所有版本已被逻辑删除。
  3. 后台清理: etcd 的后台 goroutine 会逐步扫描 BoltDB 的 B+ 树,删除被标记的旧版本数据页。
  4. 空间释放: 删除的页被标记为 Freelist,等待后续写入复用。但 BoltDB 文件大小不会立即缩小。

3.4.2 自动 Compact 策略

策略类型 配置参数 说明
周期性 --auto-compaction-retention 按时间周期(如 1h)自动执行 Compact,保留最近 N 小时的数据。
基于 Revision --auto-compaction-retention (单位: revision) 保留最近 N 个 Revision 的数据,适用于对版本数量敏感的场景。
手动 etcdctl compaction 运维人员根据监控指标手动触发,适合精细化控制。

3.5 Defrag(碎片整理)

Compact 只标记删除旧版本,但 BoltDB 文件中的物理空间并未归还给操作系统。随着 Compact 的反复执行,文件内部会产生大量碎片(空闲页分散在各处),导致文件大小持续增长。Defrag 操作通过重写 BoltDB 文件来整理碎片、回收空间。

3.5.1 Defrag 触发条件

  • 文件大小持续增长: 即使执行了 Compact,文件大小仍接近配额上限。
  • 后端存储使用率过高: 监控指标 etcd_server_is_leader 和 etcd_server_quota_backend_bytes 显示使用率 > 80%。
  • 仲裁水位告警: 多租户场景下,某个租户的“数据活跃度”导致整体存储水位异常。

3.5.2 Defrag 操作流程

  1. 停止写入: Defrag 期间需要暂停对该 etcd 成员的写入操作(通常通过 Leader 转移实现)。
  2. 创建新文件: etcd 创建一个新的 BoltDB 临时文件。
  3. 数据重写: 将当前所有有效数据(未被 Compact 的版本)按顺序写入新文件,消除碎片。
  4. 文件替换: 原子性地用新文件替换旧文件,释放磁盘空间。
  5. 恢复服务: 恢复该成员的读写能力。
最佳实践: 在多租户控制面中,建议将 Defrag 操作编排到维护窗口内,并配合 Leader 转移实现零停机。同时,通过监控每个租户的 Key 数量和 Revision 增长率,可以预判 Defrag 的触发时机,避免被动触发。

3.6 存储模型与仲裁水位的关系

etcd 的存储模型直接决定了仲裁水位的计算方式和隔离策略的可行性:

  • MVCC 版本堆积: 每个租户的频繁写入都会产生大量历史版本,这些版本共享同一个 BoltDB 文件,导致全局水位上升。
  • Revision 作为水位标尺: 仲裁水位隔离策略通常使用 current_revision - compacted_revision 作为“未压缩版本深度”指标,用于评估每个租户对存储的消耗。
  • Compact 与 Defrag 的协同: Compact 控制逻辑水位(版本数量),Defrag 控制物理水位(磁盘空间)。两者必须配合执行,才能有效控制仲裁水位。
存储维度 指标 对仲裁水位的影响
逻辑水位 未压缩版本数 (Revision 差值) 高:Watch 事件堆积、查询性能下降、触发配额告警。
物理水位 BoltDB 文件大小 / 配额 高:磁盘 I/O 压力增大、Defrag 耗时增加、集群进入只读模式。
租户隔离水位 单个租户的 Key 数 + 版本增长率 高:该租户成为“噪声邻居”,影响其他租户的写入延迟和 Watch 稳定性。