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 读取。
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 的工作流程:
- 创建 Watch:客户端指定
key或prefix,以及起始revision。 - 事件缓冲:etcd 将事件写入内存中的 事件环形缓冲区(默认 1000 个事件)。
- 事件推送:etcd 从指定 revision 开始,逐个推送事件。如果缓冲区溢出,客户端需重新同步。
- 历史事件回放:如果起始 revision 小于当前 revision,etcd 从 BoltDB 中读取历史版本并生成事件。
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 重连与事件回放补偿机制,否则可能导致服务发现、配置变更等关键功能失效。