3. etcd 数据模型与存储结构:key-value 存储、版本号、MVCC、watch 机制

在灾备准入工程中,etcd 的数据模型直接决定了备份恢复策略的粒度与一致性边界。etcd 并非简单的键值对数据库,其底层基于 MVCC(多版本并发控制) 与 版本号 机制,实现了高效的 Watch 事件推送与历史查询。理解这些核心概念,是设计可靠备份恢复方案的前提。

3.1 逻辑数据模型:分层键空间

etcd 将所有数据组织为一个扁平的 有序键值对集合,键(Key)是字节数组,值(Value)也是字节数组。所有键按字典序排列,支持前缀扫描与范围查询。

概念 说明 灾备影响
Key 任意字节序列,通常使用可读字符串(如 /registry/pods/default/nginx-xxx) 备份时需保留完整键路径,恢复时按前缀过滤
Value 任意字节序列,Kubernetes 中通常为序列化的 protobuf 或 JSON 恢复时需保证值格式与 etcd 版本兼容
Lease(租约) 关联 TTL 的键自动过期机制 备份需考虑租约状态,恢复后可能需重建租约
Revision(版本号) 全局单调递增的整数,每次写操作递增 用于 MVCC 版本控制与 Watch 起始位置
灾备提示: 备份时建议同时记录当前集群的 revision 值,恢复后可通过 --rev 参数指定历史版本,实现时间点恢复(PITR)。

3.2 物理存储结构:BoltDB 与 MVCC

etcd v3 使用 BoltDB(一个嵌入式 Go 实现的 B+ 树数据库)作为底层存储引擎。每个 etcd 节点在本地维护一个 member/snap/db 文件,该文件即 BoltDB 实例。

MVCC 的核心思想是:写操作不覆盖旧数据,而是追加新版本。每个键的每次修改都会生成一个新的版本记录,旧版本在 compaction(压缩)之前仍然可读。

MVCC 关键数据结构:
  • keyBucket(键桶):存储当前最新版本的键值对,键格式为 key_revision。
  • metaBucket(元数据桶):存储全局 revision、集群 ID、成员信息等。
  • 历史版本:未被压缩的旧版本数据仍存在于 BoltDB 中,可通过指定 revision 读取。
etcd MVCC 存储结构示意图 BoltDB 文件 (member/snap/db) keyBucket(键桶) 最新版本:key + revision_N → value 历史版本:key + revision_N-1 → value 历史版本:key + revision_N-2 → value 已压缩版本(不可读) metaBucket(元数据桶) current_revision: 1024 cluster_id: 0xabc123 member_id: 0xdef456 compacted_revision: 1000 每个键的每次修改生成一个新版本,旧版本在 compaction 前可读,支持历史查询与 Watch 回溯

3.3 版本号(Revision)与全局单调递增

etcd 维护一个全局的 64 位整数 revision,每次事务(写操作)完成后递增。revision 是 MVCC 的核心标识,用于:

  • 版本控制:每个键值对的最新版本由 key + revision 唯一标识。
  • Watch 起始位置:客户端通过指定 --rev=1024 从该 revision 开始监听事件。
  • 事务隔离:读操作可指定 serializable 或 linearizable 模式,revision 保证线性一致性。
灾备风险点: 备份文件中的 revision 与集群当前 revision 可能不一致。恢复后,新集群的 revision 从备份时的值继续递增,但 Watch 客户端如果使用旧 revision 可能收到重复事件或丢失事件。建议在灾备切换后,通知所有 Watch 客户端重新建立连接。

3.4 Watch 机制:基于 MVCC 的事件流

Watch 是 etcd 实现 实时事件推送 的核心机制。客户端通过 gRPC 流式接口订阅一个键或键前缀,etcd 在每次写操作后,将变更事件(Put/Delete)推送给所有匹配的 Watch 客户端。

Watch 的工作流程:

  1. 创建 Watch:客户端指定 key 或 prefix,以及起始 revision。
  2. 事件缓冲:etcd 将事件写入内存中的 事件环形缓冲区(默认 1000 个事件)。
  3. 事件推送:etcd 从指定 revision 开始,逐个推送事件。如果缓冲区溢出,客户端需重新同步。
  4. 历史事件回放:如果起始 revision 小于当前 revision,etcd 从 BoltDB 中读取历史版本并生成事件。
etcd Watch 机制事件流 Watch 客户端 gRPC 流式订阅 etcd Server Watch 管理器 BoltDB 历史版本存储 Watch(key, rev=100) 事件流 (Put/Delete) 读取历史版本 事件环形缓冲区 容量:1000 个事件 事件类型:Put / Delete 每个事件携带 revision Watch 支持从指定 revision 开始回放历史事件,但缓冲区溢出会导致事件丢失,客户端需重新同步 灾备恢复后,Watch 客户端应使用最新 revision 重新建立订阅,避免重复或遗漏

3.5 压缩(Compaction)与历史版本清理

为避免 BoltDB 无限增长,etcd 定期执行 压缩(Compaction),删除指定 revision 之前的所有历史版本。压缩后,旧版本数据不可读,Watch 也无法回放到被压缩的 revision。

参数 默认值 说明 灾备建议
--auto-compaction-mode periodic 按时间周期或 revision 数量自动压缩 灾备集群建议缩短压缩周期,减少恢复后数据量
--auto-compaction-retention 1h 保留最近 1 小时的历史版本 根据 PITR 需求调整,至少保留一个备份周期
etcdctl compaction 手动 手动触发压缩到指定 revision 恢复后建议先压缩到备份时的 revision,减少存储
灾备最佳实践:
  • 备份前记录当前 revision,恢复后使用 etcdctl compact <revision> 清理无用历史版本。
  • Watch 客户端在灾备切换后,应使用 --rev=0 从最新 revision 开始监听,避免因压缩导致事件丢失。
  • 定期检查 BoltDB 文件大小,异常增长可能表示压缩未生效或 Watch 客户端积压。

3.6 数据模型对备份恢复的工程影响

理解 etcd 的数据模型后,可以推导出以下灾备准入要点:

  • 备份粒度:etcd 备份是整个 BoltDB 文件的快照,无法单独备份某个 key。因此灾备方案必须考虑全量备份 + 增量 Watch 日志的组合。
  • 恢复一致性:恢复后的集群 revision 从备份时的值开始,所有 Watch 客户端必须重新同步,否则可能出现数据不一致。
  • 租约恢复:备份文件包含租约信息,但恢复后租约的 TTL 计时会重置。对于依赖租约的服务(如 Kubernetes Node Lease),需在恢复后重新注册。
  • 压缩策略:灾备集群的压缩策略应与生产集群一致,避免因压缩导致历史版本丢失,影响 PITR 能力。
常见误区: 不要认为 etcd 备份恢复后,所有 Watch 客户端能自动无缝衔接。实际工程中,必须设计 Watch 重连与事件回放补偿机制,否则可能导致服务发现、配置变更等关键功能失效。