3. etcd 键值存储模型:key-value 存储逻辑、版本号与 revision 机制、MVCC 实现原理

在 etcd 存储组件容量水位评估中,理解底层的键值存储模型是基础。etcd 并非简单的哈希表,而是一个基于 多版本并发控制(MVCC) 的、有序的键值存储引擎。本节将深入剖析其核心逻辑,包括 key-value 的物理存储结构、逻辑版本号(version)与全局修订号(revision)的区别,以及 MVCC 如何支撑高并发读写与历史数据回溯。

3.1 键值存储逻辑:从逻辑视图到物理存储

etcd 的键值存储模型在逻辑上呈现为一个扁平的、按 key 的字节序排序的字典。每个 key 对应一个 value,但底层实现远比这复杂。为了支持高效的范围查询和事务,etcd 使用 BoltDB(一个基于 B+ 树的嵌入式数据库)作为其持久化存储引擎。

在 BoltDB 中,etcd 维护了一个名为 “key” 的主 bucket。这个 bucket 中存储的 key 并非用户直接传入的 key,而是一个复合结构。其核心设计如下:

  • 物理 Key 构成:物理 key 由 用户 key + ‘\0’ + revision 组成。其中 ‘\0’ 是分隔符,revision 是全局递增的整数。
  • 排序特性:B+ 树按物理 key 的字节序排序。这意味着同一个用户 key 的所有历史版本会物理上聚集在一起,且按 revision 递增排列。
  • Value 存储:物理 value 存储的是用户传入的原始 value 以及一些元数据(如创建时的 revision、lease ID 等)。
关键理解:用户看到的“一个 key 对应一个 value”是逻辑视图。物理存储上,同一个用户 key 可能对应多条记录,每条记录对应一个历史版本。这种设计是 MVCC 和事务隔离的基础。

下图展示了用户 key “/app/config” 在 BoltDB 中的物理存储结构:

etcd 物理键值存储结构 BoltDB “key” Bucket 物理存储结构 用户 Key 逻辑视图:/app/config 物理 Key: /app/config\0 revision=100 Value: “v1” 物理 Key: /app/config\0 revision=105 Value: “v2” 物理 Key: /app/config\0 revision=110 Value: “v3” 说明:物理 key 按用户 key 前缀聚集,revision 递增。最新版本(revision=110)即为逻辑视图的当前值。

3.2 版本号(version)与全局修订号(revision)

etcd 中存在两个核心的递增计数器,它们服务于不同的目的,是理解 etcd 数据一致性和历史查询的关键。

属性 版本号(version) 全局修订号(revision)
作用域 每个 key 独立维护 全局唯一,整个集群共享
递增时机 当该 key 的 value 被更新时递增 每次事务(Put、Delete、Txn)提交时递增
起始值 key 创建时为 1 集群初始化时为 1
用途 乐观锁、检测 key 是否被修改 全局数据快照、MVCC 版本标识、Watch 事件排序
示例 key “/a” 被修改 3 次,version = 3 集群第 100 次事务提交,revision = 100
容量评估要点:revision 是单调递增的,永远不会回退。在长时间运行或高写入负载的集群中,revision 会增长到非常大的数值(如 10^12)。这会影响 BoltDB 中 B+ 树的深度和索引效率,是容量水位评估中需要监控的关键指标之一。

3.3 MVCC 实现原理

etcd 的 MVCC 实现并非像传统数据库那样通过 undo/redo 日志,而是通过 保留所有历史版本 的方式。其核心组件包括:

  • treeIndex:一个基于内存的 B 树,用于快速查找用户 key 对应的最新 revision 以及所有历史 revision 列表。它维护了从用户 key 到 revision 的映射关系。
  • backend:即 BoltDB 实例,负责持久化存储物理 key-value 对。treeIndex 中的 revision 指向 backend 中的具体物理记录。
  • 压缩(Compaction):由于历史版本会无限增长,etcd 提供了压缩机制,用于删除指定 revision 之前的所有历史版本,以回收存储空间。

一次典型的读操作流程如下:

  1. 客户端发起一个带有 revision 参数的读请求(例如 --rev=100)。
  2. etcd 在 treeIndex 中查找用户 key,获取其所有 revision 列表。
  3. 在 revision 列表中,找到小于等于请求 revision 的最大 revision(例如 revision=100)。
  4. 使用该 revision 和用户 key 拼接成物理 key,在 BoltDB 中执行一次精确查找,获取对应的 value。
  5. 返回结果。如果未找到,则返回 key 不存在。

下图展示了 MVCC 读操作的完整数据流:

etcd MVCC 读操作流程 MVCC 读操作数据流(请求 revision=100) 客户端 Get(“/app/config”, rev=100) treeIndex (内存) 查找 revision ≤ 100 找到? 是 BoltDB (磁盘) 拼接物理 Key: /app/config\0\100 返回 Value 返回 Value “v1” 否 返回 KeyNotFound MVCC 核心优势: 1. 读写不冲突:读操作读取历史快照,写操作创建新版本,无需加锁。 2. 支持时间旅行:客户端可以读取任意 revision 的历史数据,实现一致性快照。 3. 事务隔离:事务内的所有读操作都基于事务开始时的 revision,保证可重复读。 4. 存储代价:历史版本会持续占用磁盘空间,需要定期压缩(Compaction)来回收。

3.4 压缩(Compaction)与容量水位

MVCC 带来的历史版本堆积是 etcd 存储容量增长的主要来源。etcd 通过 自动压缩 和 手动压缩 两种方式来管理历史版本。

  • 自动压缩:基于时间周期(如每 5 分钟压缩一次)或 revision 间隔(如每 10000 个 revision 压缩一次)。
  • 手动压缩:运维人员通过 etcdctl compaction 命令指定一个 revision,删除该 revision 之前的所有历史版本。
容量评估关键指标:
- 历史版本数量:通过 etcdctl endpoint status 的 DB SIZE 和 HISTORY SIZE 字段监控。
- 压缩延迟:如果压缩速度跟不上写入速度,历史版本会持续堆积,导致 DB SIZE 异常增长。
- 碎片整理:压缩操作会在 BoltDB 中产生空闲页,需要定期执行 etcdctl defrag 来回收磁盘空间。

理解 MVCC 机制是评估 etcd 存储容量水位的前提。运维人员需要密切关注 revision 的增长速率、压缩策略的有效性以及 DB SIZE 的变化趋势,才能准确判断存储组件的健康状态。