S13-J05 · 快照与跨集群:备份、恢复与迁移
副本让集群扛得住节点掉线,但扛不住 DELETE /orders 手滑、扛不住 mapping 改错、更扛不住整个机房消失。备份最容易被"我们有副本"合理化掉,直到真要回滚那天,才发现仓库从没配过,或配了却从没验证过恢复。
按"为什么 → 存在哪 → 怎么取 → 怎么增量 → 怎么恢复 → 怎么迁移 → 怎么演练"拆。
1. 快照与副本:防的不是同一类事故
| 维度 | 副本 | 快照 |
|---|---|---|
| 防什么 | 单节点 / 单盘故障 | 误删、误改、灾难、跨集群迁移 |
| 存哪 | 同集群其他节点 | 独立仓库(fs/s3/hdfs) |
| 数据形态 | 实时同步的分片副本 | 某时间点的段文件引用 |
| 恢复粒度 | 分片级自动重建 | 索引级、可改名、可跨集群 |
| 成本 | 常驻磁盘 + 写入放大 | 增量存储,第一次最贵 |
副本是可用性,快照是可恢复性。只配副本的集群,本质上是没有备份。
2. 仓库类型与前置条件
| 类型 | 关键配置 | 适用 | 主要坑 |
|---|---|---|---|
fs |
location 须所有节点可读写 |
自建机房 | 部分节点不可写 → 分片失败 |
s3 |
bucket、base_path、client |
云上、跨地域 | 无 List 权限 → 恢复失败 |
hdfs |
uri、path、security.principal |
已有 Hadoop | Kerberos 配置 |
gcs/azure |
插件 + 凭据 | 多云 | 插件版本须匹配 |
source |
"type": "source" |
从另一集群恢复 | 只读,不能写入新快照 |
PUT _snapshot/backup_fs
{ "type": "fs",
"settings": { "location": "/data/es-backup", "compress": true,
"chunk_size": "1gb", "max_snapshot_bytes_per_sec": "100mb",
"max_restore_bytes_per_sec": "100mb" } }
fs 路径必须在白名单里,否则注册报 repository path is not in the list of allowed locations:
# elasticsearch.yml,全部节点一致,改完需滚动重启
path:
repo: ["/data/es-backup", "/data/es-backup2"]
compress: true 只压元数据,段本就是 Lucene 压缩过的二进制,别指望省空间。max_snapshot_bytes_per_sec 是节点级限速,防止备份抢生产 IO。
3. _snapshot 的完整流程
# 1 注册并验证
curl -XPUT 'localhost:9200/_snapshot/backup_fs' -H 'Content-Type: application/json' -d @repo.json
curl -XPOST 'localhost:9200/_snapshot/backup_fs/_verify?pretty'
# 2 创建快照(异步)
curl -XPUT 'localhost:9200/_snapshot/backup_fs/snap-2026-11-09?wait_for_completion=false' \
-H 'Content-Type: application/json' -d '{"indices":"orders,users-*",
"ignore_unavailable":true,"include_global_state":true}'
# 3 看状态
curl 'localhost:9200/_snapshot/backup_fs/snap-2026-11-09/_status?pretty'
# 4 恢复(可改名)
curl -XPOST 'localhost:9200/_snapshot/backup_fs/snap-2026-11-09/_restore?wait_for_completion=false' \
-H 'Content-Type: application/json' -d '{"indices":"orders",
"rename_pattern":"(.+)","rename_replacement":"restored-$1",
"include_global_state":false, "include_aliases":false}'
| 阶段 | 阻塞点 | 观察 |
|---|---|---|
| 注册 | 路径白名单、凭据 | _verify 的节点列表 |
| 创建 | 段读取与仓库写入带宽 | stats.total.file_count |
| 完成 | 元数据写入 | state、shards.failed |
| 恢复 | 同名索引、副本重建 | _recovery 的 type |
wait_for_completion=false 应作为默认:几十 GB 快照要跑几分钟到几十分钟(典型量级,按数据量与带宽核对),同步等只会让调用方超时误判。
4. 增量快照:段级复用,不是全量重传
快照记录的是哪些 Lucene 段构成了某个时间点的索引,段文件按内容寻址:只要某段已存在于之前的快照中,新快照只写一条引用。
| 快照 | 实际传输 | 原因 |
|---|---|---|
| 第一次 | 全部段 + 元数据 | 仓库为空 |
| 第二次(无写入) | 几乎只有元数据 | 段集合相同 |
| 第三次(有新增) | 新增小段 | 旧段引用复用 |
| 段合并后 | 合并产生的新段 | 复用链断了 |
两条结论:提高频率不会线性增加空间,但会积累很多小元数据;forcemerge 后做快照会突然变大,因为段被重写成全新文件。
partial: false(默认)表示任一分片失败则整个快照失败;partial: true 得到 PARTIAL 状态——可用,但要知道它缺了什么。
5. 恢复的参数与那条硬约束
| 参数 | 作用 | 常见误用 |
|---|---|---|
indices |
只恢复指定索引 | 不写 → 恢复全部 |
rename_pattern/rename_replacement |
改名,支持 $1 |
忘改名 → 与线上冲突 |
include_global_state |
带模板、ILM、持久设置 | 生产恢复到测试时带生产模板 |
include_aliases |
恢复别名 | 别名是写入口时恢复即抢流量 |
ignore_unavailable |
缺索引不报错 | 掩盖真正缺失 |
目标集群不能存在同名打开索引,否则直接报 restore_exception ... an open index with same uuid。三条可行路径:rename_replacement 改成 restored-orders;先 POST /orders/_close 再恢复同名;确认快照可用后删掉原索引。
副本数也是坑:快照记的是当时的 number_of_replicas,从 3 节点恢复到单节点会长期停在 yellow。
PUT /restored-orders/_settings
{ "index": { "number_of_replicas": 0, "refresh_interval": "30s" } }
恢复期副本归零、refresh_interval 调大可明显提速(避免边恢复边合并与分配副本),完事再改回。
6. 保留策略:SLM
PUT _slm/policy/daily-snapshots
{ "schedule": "0 30 2 * * ?", "name": "<daily-{now/d}>",
"repository": "backup_fs",
"config": { "indices": "*", "include_global_state": true, "ignore_unavailable": true },
"retention": { "expire_after": "14d", "min_count": 7, "max_count": 30 } }
curl -XPOST 'localhost:9200/_slm/policy/daily-snapshots/_execute' # 立即试跑
curl 'localhost:9200/_slm/policy?pretty' # 下次执行时间
curl 'localhost:9200/_slm/stats?pretty'
| 字段 | 含义 | 建议 |
|---|---|---|
expire_after |
超多久可删 | 按 RPO/RTO 与容量定 |
min_count |
至少保留几个 | ≥2,留上一个 |
max_count |
上限 | 防高频策略塞满仓库 |
schedule |
cron(6 段含秒) | 避开高峰与 forcemerge |
SLM 只删引用,不删仍被其他快照引用的段文件——"删了快照磁盘没变小"是正常的,要等仓库清理。
7. 跨集群迁移的三条路
| 路径 | 机制 | 适用 | 限制 |
|---|---|---|---|
| 快照恢复 | 注册源仓库后 _restore |
大批量、可停机、跨版本 | 需共享仓库 |
reindex from remote |
源 _search + _bulk |
在线、无共享存储 | 需白名单 |
| 双写过渡 | 应用写双集群后切读 | 不能停机 | 要改造与对账 |
POST _reindex?wait_for_completion=false
{ "source": { "remote": { "host": "http://old-es:9200",
"socket_timeout": "60s", "connect_timeout": "30s" },
"index": "orders", "size": 5000 },
"dest": { "index": "orders-v2", "op_type": "create" },
"conflicts": "proceed" }
# 目标集群 elasticsearch.yml
reindex.remote.whitelist: ["old-es:9200", "10.0.0.*:9200"]
op_type: create 让重复文档变成版本冲突而非覆盖,迁移时更安全。
8. reindex 的限速与中断续跑
| 参数 | 作用 | 典型取值 |
|---|---|---|
requests_per_second |
每秒接受的批次数(令牌桶) | -1 起,生产中 500–2000 观察 |
slices |
并行子请求数 | auto 或主分片数 |
size |
每批文档数 | 1000–5000 |
max_docs |
本次上限 | 分段推进 |
wait_for_completion |
同步等待 | 大任务必须 false |
curl 'localhost:9200/_tasks?actions=*reindex&detailed=true&pretty'
curl -XPOST 'localhost:9200/_reindex/<task_id>/_rethrottle?requests_per_second=200'
curl -XPOST 'localhost:9200/_tasks/<task_id>/_cancel'
任务 id 不等于断点:中断后一般用时间范围或 _id 范围重新发起,靠 op_type: create + conflicts: proceed 保证重跑不产生重复。
9. 跨版本升级的路径约束
| 场景 | 允许 | 不允许 |
|---|---|---|
| 快照恢复 | 旧快照 → 新版本 | 新快照 → 旧版本 |
| 滚动升级 | 一次一个大版本 | 跨多个大版本跳升 |
| 索引格式 | 升级后按需恢复 | 长期带旧格式运行 |
顺序恒定:先完整快照并验证可恢复 → 滚动升级 → 验证文档数与关键查询。跳过第一步就没有回退点;fs 路径不在新集群 path.repo 里,升级后连仓库都注册不上。
10. 演练要点:备份不验证等于没有
| 演练项 | 做法 | 通过标准 |
|---|---|---|
| 恢复计时 | 记录到 green 耗时 |
落在 RTO 内 |
| 文档数 | _count 与源索引对比 |
完全一致 |
| 抽样内容 | 抽 5–10 个 _id 比对 |
值、mapping 都对 |
| 别名与模板 | 确认指向与生效 | 应用无感切换 |
| 单节点环境 | 副本归零后 green |
能 |
| 最小权限 | 最小权限账号跑一遍 | 不依赖管理员账号 |
11. 排错表
| 现象 | 原因 | 处理 |
|---|---|---|
repository_verification_exception |
部分节点路径不可写 | 查挂载与 path.repo |
快照卡 IN_PROGRESS |
仓库带宽满 / 段被 merge 锁 | 查 IO,降限速反而更稳 |
SnapshotMissingException |
名字写错或已被 SLM 删 | _snapshot/repo/_all 列实际项 |
open index with same uuid |
目标集群有同名打开索引 | 改名 / 关闭 / 删除 |
恢复后长期 yellow |
副本数大于可用节点 | 临时 number_of_replicas: 0 |
| 恢复后查询变慢 | refresh_interval 未还原 |
恢复设置、按需 forcemerge |
| reindex 文档不全 | 任务被 cancel 或客户端断开 | _tasks 观察,按范围重跑 |
| 拒绝 remote reindex | 未配 reindex.remote.whitelist |
加白名单并重启目标 |
12. 边界:什么时候别用快照解决
| 需求 | 更合适的工具 |
|---|---|
| 单文档误删想找回内容 | 上游数据库 / binlog |
| 索引结构频繁试验 | 模板 + 别名切流 |
| 热数据实时容灾 | 跨集群复制(CCR) |
| 只迁一小批文档 | _reindex 带 query |
| 长期归档冷数据 | 快照 + 可搜索快照 |
13. 自测
- 有 3 副本的集群为什么仍然需要快照?
fs仓库注册成功但快照分片失败,先查什么?- 第二次快照为什么几乎不传段?什么操作会让它突然变大?
- 恢复报"存在同名打开索引",三种处理各有什么代价?
- SLM 删了旧快照,仓库磁盘为什么没有立刻变小?
- 跨版本升级的正确顺序是什么?第一步为什么不能省?