前言
前几篇文章我们讲了 etcd 的集群架构和 Raft 协议,明白了数据是怎么在节点之间同步的。但还有一个问题:数据在磁盘上是怎么存的? 宕机了怎么恢复?
这篇文章,我们来拆解 etcd 的存储模型。
一、数据目录结构
当你安装好 etcd 并启动后,数据目录下会有这样的结构:
/var/lib/etcd/etcd-cluster/ ← 数据目录(可配置)
├── member/
│ ├── snap/ ← Snapshot(快照文件)
│ │ └── 0000000000000001-0000000000000001.snap
│ └── wal/ ← WAL(预写日志)
│ └── 0000000000000001-0000000000000001.wal
└── ...
两个核心目录
| 目录 | 文件名 | 存什么 | 类比 |
|---|---|---|---|
| snap/ | *.snap |
某一时刻的全量数据快照 | 每个月拍一张全家福 |
| wal/ | *.wal |
所有写操作的日志 | 每天写日记 |
文件名的含义
0000000000000001-0000000000000001.snap
↑ 当前任期 ↑ 当前日志索引
二、WAL(Write-Ahead Log,预写日志)
WAL 是什么
WAL 是 etcd 的操作日志,记录所有对数据的修改操作(set、delete 等)。
etcd 收到写请求:"set timeout=60"
→ 先写入 WAL(写日志,保证不丢)
→ 再更新内存中的 key-value 数据
→ 返回客户端:写入成功
为什么先写 WAL,再更新内存?
如果不先写 WAL:
etcd 更新内存 → 突然宕机 → 内存中的数据丢失
→ 数据丢了 ❌
先写 WAL,再更新内存:
etcd 写入 WAL → 更新内存 → 突然宕机
→ 重启后,从 WAL 中重放操作,恢复数据
→ 数据不丢 ✅
WAL 是追加写入的
WAL 文件是顺序追加的,不会修改已有的内容。这意味着:
第 1 次写入 → WAL 文件末尾追加一条记录
第 2 次写入 → 再追加一条
第 3 次写入 → 再追加一条
WAL 文件会越来越大...
三、Snapshot(快照)
为什么需要 Snapshot?
WAL 一直在增长,如果不做处理:
第 1 次写入 → WAL 1KB
第 1000 次写入 → WAL 1MB
第 100000 次写入 → WAL 100MB
...
宕机恢复时,要从头重放所有 WAL → 非常慢 ❌
所以需要定期生成 Snapshot(快照):
Snapshot = 当前内存中所有数据的全量备份
有了 Snapshot,就不需要从最早的 WAL 开始重放了
Snapshot 的触发条件
bash
# 从 etcd 日志中可以查看
journalctl -u etcd | grep "snapshot count"
# 输出:snapshot count = 100000
snapshot count = 100000 表示:每处理 10 万次写操作,自动生成一次 Snapshot。
Snapshot 生成过程
当 WAL 达到 100000 条时:
1. etcd 把内存中的全量数据保存为 .snap 文件
2. 删除旧的 .snap 文件(保留最新的)
3. 删除旧的 WAL 文件
4. 新建一个空的 WAL 文件,从头开始写
所以任何时候,磁盘上只有:
1 个 .snap 文件(全量数据)
1 个 .wal 文件(snapshot 之后的增量)
四、宕机恢复流程
etcd 宕机了(比如进程被 kill)
重启 etcd:
1. 加载最新的 .snap 到内存(恢复全量数据)
2. 重放 .wal 中 snapshot 之后的日志(恢复增量数据)
3. etcd 恢复到宕机前的状态 ✅
类比:
你有一本日记本(WAL),每个月拍一张照片(Snapshot)
如果你失忆了(宕机):
1. 看最近的照片(Snapshot)→ 知道上个月的状态
2. 读照片之后的日记(WAL)→ 知道之后发生了什么
3. 你就恢复了完整记忆 ✅
五、snapshot save 是什么?
这是 etcd 迁移中最重要的操作。
执行方式
bash
export ETCDCTL_API=3
etcdctl --endpoints=http://127.0.0.1:2379 \
snapshot save /tmp/snapshot-20260903.db
它做了什么?
snapshot save 不是复制磁盘上的 .snap 文件
而是从 etcd 进程的内存中,直接导出"当前所有数据"
内存中的数据 = 磁盘上的 .snap 加载后 + .wal 重放后
= 完整的、最新的数据
所以导出的 .db 文件 = 那个时刻的完整数据 ✅
为什么会叫"save"而不是"export"?
虽然叫 snapshot save,但它跟 etcd 内部自动触发的 snapshot 是两回事:
内部自动 snapshot(由 snapshot count 触发):
├── 目的是控制 WAL 大小
├── 生成 .snap 文件,用于宕机恢复
└── etcd 自己管理,用户不需要关心
手动 snapshot save(由用户触发):
├── 目的是备份或迁移数据
├── 生成 .db 文件,可以传到其他机器
└── 用于备份、迁移、恢复
六、snapshot restore 做了什么?
bash
etcdctl snapshot restore /tmp/snapshot-20260903.db \
--name=new-node-1 \
--initial-cluster="new-node-1=http://10.0.0.1:2380,new-node-2=http://10.0.0.2:2380,new-node-3=http://10.0.0.3:2380" \
--initial-cluster-token=my-cluster-token \
--data-dir=/var/lib/etcd/etcd-cluster
恢复过程
1. 从 .db 文件中提取数据
2. 生成全新的 member/snap/ 和 member/wal/ 目录
3. 用新的集群配置初始化(name、token、成员列表)
4. 启动后,etcd 加载这个 snapshot 到内存
5. 集群数据恢复 ✅
为什么 snapshot 能跨集群迁移?
snapshot 里存的是"数据本身",不包含"集群身份"
就像:
把一本书(数据)从 A 房间(旧集群)搬到 B 房间(新集群)
书的内容不变,但书的摆放位置重新设置
--name=新节点名 ← 新房间的标签
--initial-cluster-token= ← 新房间的标识(防止跟旧集群混淆)
--initial-cluster=... ← 新房间的成员列表
为什么要改 token?
--initial-cluster-token 相当于集群的"身份证号"
旧集群:身份证号 123456
新集群:身份证号 ABCDEF
如果新集群用旧身份证号:
→ 新集群以为自己是旧集群
→ 试图找旧集群的节点
→ 找不到,启动失败 ❌
如果新集群用新身份证号:
→ 新集群知道自己是一个新集群
→ 用 snapshot 恢复的数据启动
→ 启动成功 ✅
七、compaction(数据压缩)
为什么需要 compaction?
etcd 每次写操作都会保留历史版本:
/default/myapp/config/db_url/1700123456 → "旧地址"
/default/myapp/config/db_url/1700123457 → "新地址"
/default/myapp/config/db_url/1700123458 → "最新地址"
如果不压缩,历史版本越积越多,磁盘会无限增长
compaction 就是删除旧版本,只保留最近的版本
配置自动 compaction
bash
# 在 etcd 启动参数中加
--auto-compaction-retention=1
# 保留最近 1 小时的 revision,自动删除旧版本
手动 compaction
bash
export ETCDCTL_API=3
# 查看当前 revision
etcdctl --endpoints=http://127.0.0.1:2379 endpoint status --write-out=fields
# 压缩到当前 revision
etcdctl --endpoints=http://127.0.0.1:2379 compaction <revision号>
八、总结
etcd 的存储模型核心就是两样东西:
WAL(预写日志):
├── 记录所有写操作
├── 顺序追加,高性能
└── 宕机时重放恢复
Snapshot(快照):
├── 定期压缩全量数据
├── 控制 WAL 大小,加速恢复
└── 手动 snapshot save 用于备份迁移
手动 snapshot save 导出的 .db 文件:
├── 是内存中的完整数据,不是磁盘上的 .snap
├── restore 后不需要重放 WAL,本身就是完整的
└── 可以跨集群迁移(数据不绑定集群身份)
上一篇:[etcd 学习系列(三):Raft 协议 ------ 分布式一致性就是这么简单](#etcd 学习系列(三):Raft 协议 —— 分布式一致性就是这么简单)
下一篇:[etcd 学习系列(五):Watch 机制 ------ 配置热加载的核心](#etcd 学习系列(五):Watch 机制 —— 配置热加载的核心)