0. 背景
最近我们在维护一个 3 节点的 Elasticsearch 集群,主要用于存储应用的日志数据(Logstash/Beats 写入,按年月日切分 Index)。由于集群架构调整,我们需要对硬件进行迁移,计划缩容其中一个计算节点。
因为我们的存储采用的是网盘(NAS/SAN)挂载的形式,我的设想非常美好:
“只要把网盘从旧机器卸载,挂载到新机器,IP 配好,ES 启动,集群瞬间变绿,数据零拷贝恢复。”
毕竟数据都在磁盘上躺着,日志数据又不会变,对吧?
1. 现象:明明有数据,却从头开始?
现实狠狠打脸。当我完成了存储挂载并启动新节点后,观测到的现象如下:
- 新节点成功加入了集群。
- 但是!新节点上的 Shards 并没有直接变为
Started状态。 - 集群健康状态变为
Yellow。 - 通过观测监控,发现网络 IO 飙升。
- 查看
_cat/recovery,发现集群正在进行 Peer Recovery(对等恢复),也就是从现有的主分片,全量通过网络把数据复制给新节点。
这就很离谱了:**几十 TB 的日志数据明明就在本地磁盘里,ES 视而不见,非要走网络重新拉取一遍。**这导致恢复时间极长,且占用大量带宽。
2. 深度分析:ES 为什么不信任本地磁盘?
经过查阅官方文档和原理解析,发现这是 ES 核心的数据一致性机制在起作用。简单来说,ES 并不“信任”随便挂上来的磁盘,除非你能证明你的数据是“新鲜”的。
导致全量同步的几个核心原因:
2.1 节点身份(Node Identity)错乱
ES 识别节点不是靠 IP,也不是靠挂载路径,而是靠数据目录 nodes/0/_state/ 下的元数据。
如果在 elasticsearch.yml 中,新节点的 node.name 与旧节点不一致,或者数据目录层级有微小变化,集群会认为这是一个全新的空节点。既然是新节点,之前的分片数据自然归属权就不明确了。
2.2 分片分配 ID (Allocation IDs) 不匹配
这是最主要的原因。ES 维护了一个 in-sync allocation IDs 列表,用于判断哪个分片拥有最新的数据。
- 当我暴力停止旧节点 A 时,集群检测到 A 离线。
- 为了保证服务可用,集群将其他节点上的副本分片(Replica)提升为主分片(Primary)。
- 此时,集群的元数据更新了:它记录下“现在的 Primary 是最新的,A 节点那个副本已经过时了(Out of sync)”。
- 当我启动新节点 B(挂载了 A 的盘)时,虽然磁盘上有数据,但 ES 对比 Allocation ID 发现该数据不在 In-Sync 列表中。
- 为了防止数据回滚或不一致,ES 选择丢弃本地数据,从当前的 Primary 重新拉取。
2.3 缺少 Flush 操作
ES 的数据写入先在内存 Buffer,同时写入 Translog。如果不手动 Flush,磁盘上的 Lucene Segment 可能不是最新的。在新节点启动时,ES 发现 Translog “脏了”,必须进行复杂的恢复流程,如果校验失败,就会回退到全量复制。
3. 正确的操作姿势(SOP)
如果下次再做类似迁移,为了实现**“秒级恢复”**(Local Recovery),必须严格遵守以下步骤:
第一步:迁移前准备(关键!)
在停止旧节点之前,必须告诉集群:“我要暂时维护,不要把我的分片分配给别人。”
-
禁止分片分配
防止节点下线后,集群立即触发 Rebalance 或 Replica Promotion,导致原数据失效。PUT _cluster/settings { "persistent": { "cluster.routing.allocation.enable": "primaries" } }注:设置为
primaries表示只允许主分片分配,禁止副本迁移。 -
执行同步刷盘 (Synced Flush)
这一步至关重要!它会强制将数据刷入磁盘,并生成一个唯一的sync_id。下次启动时,只要 ID 对得上,ES 就知道数据完全一致,无需对比。
ES 7.6+ 版本直接用 Flush,老版本建议用 Synced FlushPOST /_all/_flush # 或者 POST /_all/_flush/synced
第二步:执行迁移
- 停止旧节点。
- 卸载存储,挂载到新节点。
- 配置检查:确保新节点的
elasticsearch.yml中cluster.name和node.name与旧节点完全一致。 - 权限检查:确保
elasticsearch用户对新挂载的目录有读写权限。
第三步:启动与恢复
- 启动新节点。
- 等待节点加入集群。
- 恢复分片分配:
PUT _cluster/settings { "persistent": { "cluster.routing.allocation.enable": null } } - 见证奇迹:此时 ES 会检测到本地数据的
Allocation ID或Sync ID与集群一致,直接执行 Peer Recovery (from local files)。这通常只需要几秒到几分钟来校验文件指纹。
4. 亡羊补牢:已经开始慢速同步了怎么办?
如果已经像我一样不幸踩坑,正在慢速同步中,可以通过调整参数加速恢复:
调大恢复带宽限制:
ES 默认的恢复带宽限制很保守(通常是 40mb),内网千兆/万兆环境下可以大胆调高。
PUT _cluster/settings
{
"transient": {
"indices.recovery.max_bytes_per_sec": "200mb"
}
}
注意:同步完成后记得调回来,或者设为 null,以免影响正常业务 IO。
5. 针对日志场景的优化建议
对于我们这种“按日期存储日志,且历史数据不修改”的场景,还有一个优化大招:设置只读。
对于历史月份的 Index,迁移前可以设置为只读:
PUT /logs-2023-*/_settings
{
"index.blocks.write": true
}
只读索引在恢复时,ES 会跳过很多繁琐的检查步骤,能进一步提高安全性及恢复速度。
总结
Elasticsearch 的“智能”有时候会成为运维的陷阱。“直接挂载磁盘”不等于“数据迁移”。
想要数据不丢、同步不慢,核心原则只有一条:在停机前,先锁集群(Disable Allocation),再刷盘(Flush),最后再动刀。