2564 字
约 8 分钟
13
S13-J05 · 快照与跨集群:备份、恢复与迁移

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. 自测

  1. 有 3 副本的集群为什么仍然需要快照?
  2. fs 仓库注册成功但快照分片失败,先查什么?
  3. 第二次快照为什么几乎不传段?什么操作会让它突然变大?
  4. 恢复报"存在同名打开索引",三种处理各有什么代价?
  5. SLM 删了旧快照,仓库磁盘为什么没有立刻变小?
  6. 跨版本升级的正确顺序是什么?第一步为什么不能省?
S13-J05 · 快照与跨集群:备份、恢复与迁移
https://www.clxhxhhr.top/posts/6035/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。