Press "Enter" to skip to content

【踩坑记录】Elasticsearch 节点迁移:为什么挂载了旧磁盘,ES 还是要重新同步数据?

0. 背景

最近我们在维护一个 3 节点的 Elasticsearch 集群,主要用于存储应用的日志数据(Logstash/Beats 写入,按年月日切分 Index)。由于集群架构调整,我们需要对硬件进行迁移,计划缩容其中一个计算节点。

因为我们的存储采用的是网盘(NAS/SAN)挂载的形式,我的设想非常美好:

“只要把网盘从旧机器卸载,挂载到新机器,IP 配好,ES 启动,集群瞬间变绿,数据零拷贝恢复。”

毕竟数据都在磁盘上躺着,日志数据又不会变,对吧?

1. 现象:明明有数据,却从头开始?

现实狠狠打脸。当我完成了存储挂载并启动新节点后,观测到的现象如下:

  1. 新节点成功加入了集群。
  2. 但是!新节点上的 Shards 并没有直接变为 Started 状态。
  3. 集群健康状态变为 Yellow
  4. 通过观测监控,发现网络 IO 飙升。
  5. 查看 _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 列表,用于判断哪个分片拥有最新的数据。

  1. 当我暴力停止旧节点 A 时,集群检测到 A 离线。
  2. 为了保证服务可用,集群将其他节点上的副本分片(Replica)提升为主分片(Primary)
  3. 此时,集群的元数据更新了:它记录下“现在的 Primary 是最新的,A 节点那个副本已经过时了(Out of sync)”。
  4. 当我启动新节点 B(挂载了 A 的盘)时,虽然磁盘上有数据,但 ES 对比 Allocation ID 发现该数据不在 In-Sync 列表中。
  5. 为了防止数据回滚或不一致,ES 选择丢弃本地数据,从当前的 Primary 重新拉取。

2.3 缺少 Flush 操作

ES 的数据写入先在内存 Buffer,同时写入 Translog。如果不手动 Flush,磁盘上的 Lucene Segment 可能不是最新的。在新节点启动时,ES 发现 Translog “脏了”,必须进行复杂的恢复流程,如果校验失败,就会回退到全量复制。

3. 正确的操作姿势(SOP)

如果下次再做类似迁移,为了实现**“秒级恢复”**(Local Recovery),必须严格遵守以下步骤:

第一步:迁移前准备(关键!)

在停止旧节点之前,必须告诉集群:“我要暂时维护,不要把我的分片分配给别人。”

  1. 禁止分片分配
    防止节点下线后,集群立即触发 Rebalance 或 Replica Promotion,导致原数据失效。

    PUT _cluster/settings
    {
      "persistent": {
        "cluster.routing.allocation.enable": "primaries"
      }
    }
    

    注:设置为 primaries 表示只允许主分片分配,禁止副本迁移。

  2. 执行同步刷盘 (Synced Flush)
    这一步至关重要!它会强制将数据刷入磁盘,并生成一个唯一的 sync_id。下次启动时,只要 ID 对得上,ES 就知道数据完全一致,无需对比。
    ES 7.6+ 版本直接用 Flush,老版本建议用 Synced Flush

    POST /_all/_flush
    # 或者
    POST /_all/_flush/synced
    

第二步:执行迁移

  1. 停止旧节点。
  2. 卸载存储,挂载到新节点。
  3. 配置检查:确保新节点的 elasticsearch.ymlcluster.namenode.name 与旧节点完全一致
  4. 权限检查:确保 elasticsearch 用户对新挂载的目录有读写权限。

第三步:启动与恢复

  1. 启动新节点。
  2. 等待节点加入集群。
  3. 恢复分片分配
    PUT _cluster/settings
    {
      "persistent": {
        "cluster.routing.allocation.enable": null
      }
    }
    
  4. 见证奇迹:此时 ES 会检测到本地数据的 Allocation IDSync 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),最后再动刀。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注