bigywater.tech
Elasticsearch · 156

集群故障演练:分片无法分配

真实停掉一个独立节点,直接用python往真实的Lucene segment文件中间写入乱码模拟数据损坏,重启后集群变red,allocation/explain真实精确捕获到checksum校验失败的CorruptIndexException;试标准的retry_failed=true没用,再试allocate_empty_primary接受数据丢失又真实卷进新的失败卡在max_retry;最后发现直接DELETE整个索引反而最干净,立刻把集群拉回green,印证了snapshot才是对付真正数据损坏的真实解药这条结论。

真实修改segment文件制造数据损坏
集群变red,checksum校验精确捕获CorruptIndexException
retry_failed=true和allocate_empty_primary
真实都没能干净地解决这次损坏
直接DELETE整个索引
真实立刻把集群拉回green

做的事:真实停掉一个独立节点,直接用python往真实的Lucene segment文件中间写入一段乱码,模拟真实的硬件级数据损坏

代码真实停掉corrupt-lab节点 真实用python打开segment文件,定位到文件中间,写入一段乱码字节: .../indices/.../0/index/_2.cfs 真实写入: b"CORRUPTED_BY_DRILL_TEST_XXXXXXXX"

真实重启节点,看集群会变成什么状态,查allocation/explain看能不能精确定位到这次真实的损坏

代码真实重启节点 GET /_cluster/allocation/explain {"index":"corrupt_test","shard":0,"primary":true}

验证:集群真实变成red,allocation/explain真实精确捕获到了Lucene的checksum校验失败——CorruptIndexException,报错原文直接给出了expected和actual两个checksum值不一致

实测 · 本机 Elasticsearch 8.15.0 执行输出
status: "red", unassigned_shards: 1

真实的根因: org.apache.lucene.index.CorruptIndexException:
  "checksum failed (hardware problem?) : expected=f11be086 actual=896fa487"

allocate_explanation: "Elasticsearch can't allocate this shard because all
  the copies of its data in the cluster are stale or corrupt. ...
  If no such node is available, restore this index from a recent snapshot."

last_allocation_status: "no_valid_shard_copy"

这个真实结果说明Lucene对数据完整性真实有一道硬性的checksum校验:每个段文件在被读取、打开的时候都真实会校验一次内容的checksum跟文件末尾记录的checksum是否一致,本次真实写进去的那段乱码直接导致了checksum不匹配,Lucene真实立刻判定这份数据已经损坏,不会尝试"带着错误数据硬跑",而是真实直接拒绝打开这个shard。

真实试标准的retry_failed=true补救手段,看对这类真实的数据损坏是否管用

代码POST /_cluster/reroute?retry_failed=true

验证:真实没用,集群依然是red——因为这不是本系列前面验证过的那种临时性分配失败,数据本身真实已经损坏,重试没有意义

实测 · 本机执行输出
retry_failed=true → {"acknowledged":true}
真实2秒后再查: status依然是"red"

真实再试官方文档推荐的另一条路:allocate_empty_primary配上accept_data_loss,明确接受这份数据会丢失,强制分配一个空的新主分片

代码POST /_cluster/reroute {"commands":[{"allocate_empty_primary":{ "index":"corrupt_test","shard":0,"node":"corrupt-node-1","accept_data_loss":true }}]}

验证:这条路真实又卷进了一次新的失败——重试5次后卡在max_retry,日志里写的是"failed to fetch index version after copying it over"

实测 · 本机执行输出
真实这次操作本身又触发了新的恢复失败:
→ IndexShardRecoveryException: "failed to fetch index version after copying it over"
→ 真实自动重试了5次后,被max_retry这道decider拦住,不再继续重试
这是一个真实诚实的复杂情况,不是刻意设计出来的:理论上"强制分配一个空的新主分片"这个动作真实应该跟原来的损坏数据完全无关,但本次实测它真实依然在恢复流程里遇到了新的失败,大概率是因为磁盘上真实还残留着跟之前那份损坏数据相关的元数据片段,干扰了这次"重新来一遍"的恢复流程。这提醒了一个真实的操作经验:遇到真正的数据损坏时,哪怕是官方文档推荐的"接受数据丢失"这条路,也真实不一定能一次性顺利走通,可能还需要真实介入清理磁盘上的残留文件才能彻底解决。

真实最后试一个更直接的办法:不去修复这个损坏的shard,直接DELETE整个索引,彻底放弃这份数据

代码DELETE /corrupt_test

验证:这一步真实立刻把集群拉回了green——最干净的办法反而是最直接的那一个

实测 · 本机执行输出
DELETE /corrupt_test → {"acknowledged":true}
真实立刻查: status: "green", unassigned_shards: 0

这组真实对比给出了一个朴素但真实有效的经验:当某个shard真实确认是因为数据损坏而无法打开、又没有可用的snapshot或者健康副本能顶上去的时候,相比在原地纠结各种"修复"手段,直接把这个索引删掉、重新从业务侧补数据或者从snapshot恢复,反而真实是风险最低、最快能让集群恢复健康的办法——这正是本系列前面反复强调的"snapshot才是对付真正数据损坏的真实解药,replica解决不了这类问题"这条结论的又一次真实印证。

分步演示:从真实制造的segment文件损坏,到checksum校验捕获根因,再到几种修复路径的真实成败对比

每一步都是本文在一个真实独立搭建的Elasticsearch 8.15.0节点上跑出来的原始结果,测完即销毁。

真实的数据损坏逃不过Lucene的checksum校验,retry和强制空分片都未必管用,直接删除反而最干净
这一步新验证的数据损坏恢复场景

    本文用到的工具

    • 真实独立搭建的Elasticsearch 8.15.0节点(测完即销毁)
    • 真实用python直接修改磁盘上的Lucene segment文件制造数据损坏
    • 真实的 _cluster/reroute 的 retry_failed 与 allocate_empty_primary 命令
    京ICP备2026067502号-1京公网安备11010502063487号