ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

HDFS NameNode元数据修复:fsck诊断与edits恢复实战

HDFS NameNode元数据修复:fsck诊断与edits恢复实战

凌晨两点,群里弹出告警:NameNode 进程起来了,但集群一直停在 Safe Mode ON。新任务全部卡住,读请求半路超时,业务方在群里连发问号。我打开 NameNode 日志,顺手敲了hdfs dfsadmin -safemode get和hdfs fsck /,一屏的 MISSING、CORRUPT 字眼冒出来,那一刻我知道,这大概率不是简单的磁盘坏块,而是NameNode 的元数据出了问题。

后来跟人复盘时发现,很多运维对hadoop fsck和 NameNode 元数据修复的理解是拧的:要么觉得 fsck 会把 HDFS 修好,要么一看到坏块就想到格式化重建集群。这两件事分开看都不复杂,但放到一起就特别容易翻车。这篇我把自己实操过程中的完整思路、命令细节和踩坑记录写出来,希望能给正在熬夜救集群的你一点参考。

1. fsck先把问题摆上桌:它到底能告诉我们什么

1.1 别把 hdfs fsck 当成 Linux fsck

我第一次用hdfs fsck时也以为它跟ext4 fsck类似:扫描整个磁盘、修复文件系统结构、把损坏的 inode 链重新串起来。实际完全不是一回事。

hdfs fsck不会全量扫描 DataNode 上的每一个字节,也不会主动修改文件系统元数据结构。它做的事情更像一个“审计员”:从 NameNode 的内存命名空间里拿到目录树和文件列表,再结合 blockMap 里记录的块副本位置,去判断每个文件是否满足副本策略、有没有缺失的块、副本分布是否均匀。真正发现磁盘上数据位损坏的是 DataNode 后台的 Block Scanner,它周期性校验本地副本的 CRC,发现坏块后再通过块汇报告诉 NameNode。fsck 只是把这些结果汇总给你看。

有一个很常见的坑:业务方已经在报“读文件失败”,你立刻执行hdfs fsck /却显示 HEALTHY。原因就是 DataNode 的块汇报有周期,坏块还没被上报到 NameNode,fsck 自然看不到。所以遇到读异常但 fsck 健康时,先等一个 block report 周期再下结论,或者直接去对应 DataNode 的日志里找 CRC 校验失败的记录。

1.2 最常用的几个 fsck 参数和输出解读

我日常用得最多的命令是这样的:

hdfs fsck /user/hive/warehouse/logs.db/dt=2025-01-01 -files -blocks -locations 2>&1 | tee fsck_report.txt

几个关键参数的作用整理如下:

参数作用说明
-files打印被检查路径下的文件列表不输出具体块信息时,只确认文件存不存在
-blocks打印每个文件的块列表和数据恢复、块数量核对应配合使用
-locations打印每个块的副本所在 DataNode定位某块还有没有副本,副本落在哪些机架
-racks打印机架拓扑信息检查副本是否跨机架,对容灾很重要
-list-corrupt-file-blocks只列出存在损坏块的文件巡检脚本最常用,输出干净,方便告警
-move把损坏文件移动到/lost+found注意这个是移动文件路径,不是修块
-delete删除损坏块对应的文件路径极其危险,后面专门讲
-includeSnapshots检查时包含快照目录忘记加这个参数会导致快照里的损坏块被忽略
-blockId blk_xxx按块 ID 查询这个块的状态和位置定位副本丢失、判断恢复进度时非常好用

正常输出大概长这样:

Status: HEALTHY Number of># 1. 先看当前安全模式状态 hdfs dfsadmin -safemode get # 2. 看 NameNode 日志里最近在卡什么 tail -200 $HADOOP_LOG_DIR/hadoop-hdfs-namenode-*.log # 3. 统计全集群损坏情况 hdfs fsck / -files -blocks -locations 2>&1 | tail -50 # 4. 列出损坏文件明细 hdfs fsck -list-corrupt-file-blocks 2>&1

如果 fsck 显示Corrupt blocks为 0,但安全模式还是没退出,那就是 DataNode 上报进度不足,等数据节点心跳把 block report 传上来就好,不用慌张。如果Corrupt blocks很多,说明有真实损坏,需要结合下面两步判断是元数据的锅还是数据的锅。

3.2 用 oiv 和 oev 逆向检查镜像和操作日志

很多运维不敢手动碰 fsimage 和 edits,怕把现场搞得更乱。其实 Hadoop 自带的oiv(Offline Image Viewer)和oev(Offline Edits Viewer)都是只读工具,不会修改原文件,可以放心用。

查看 edits 内容时,我常用的命令:

hdfs oev -i /dfs/nn/current/edits_inprogress_0000000000000000012 \ -o /tmp/edits_12.xml \ -p XML

导出的 XML 里能看到每一条事务的操作类型(OP_ADD、OP_DELETE、OP_MKDIR 等)、事务 ID、路径。重点看事务 ID 是否连续,最后一条是否完整。如果 XML 在某个事务处突然断掉或者 XML 解析报错,基本可以断定这个 edits 段写坏了。

查看 fsimage 内容时,用:

hdfs oiv -i /dfs/nn/current/fsimage_0000000000000000020 \ -o /tmp/fsimage_20.xml \ -p XML

这里有个经验:fsimage 导出的 XML 可能非常大,几十 GB 也是正常的,输出目录要预留足够磁盘,别把/tmp挤爆。还有新版和旧版的 image 格式不同,老集群导不出的情况时有发生,可以用oiv_legacy去试,但支持上有坑。

3.3 先分清是“元数据坏”还是“数据坏”

这是决定后续操作方向的分水岭。我见过太多人一看到 fsck 报坏块,就想去动元数据,结果把本来能自动恢复的问题搞成永久故障。

给一个简单的判断表:

现象大概率根因优先动作
NameNode 启动失败,回放 edits 报错元数据日志损坏用 oev 检查,重建或回退 edits
NameNode 正常启动,但 fsck 显示某些块 corruptDataNode 磁盘坏块、副本丢失下线坏盘,等 NameNode 自动复制
文件可列出,但读取时不存在数据块缺失无副本找冷备、快照或上游数据重放
安全模式不退出,threshold 永远达不到坏块数超过阈值处理损坏后手动 leave,谨慎 forceExit

如果元数据本身没坏,只是数据盘上有坏块,正确做法是让坏节点把副本状态上报,NameNode 把健康副本复制到新节点,然后下线坏盘。整个过程不需要碰 fsimage 和 edits。反过来,如果是元数据回放失败,你再怎么换 DataNode 都没用,必须回到 NameNode 这一层处理。

4. 元数据修复实操:从重建edits到安全恢复

4.1 动手前的第一件事:备份现场

不管后续方案是什么,我先做备份。NameNode 的元数据目录一般不大,但恢复价值极高,必须完整复制。

mkdir -p /backups/nn-current-backup-20250101 cp -a /dfs/nn/current /backups/nn-current-backup-20250101/

注意,dfs.namenode.name.dir如果配置了多个目录,每个目录都要单独备份,不能只备份其中一个。把备份放在本机之外的地方更稳妥,因为如果 NameNode 所在机器磁盘整个坏了,本机备份也保不住。

这一步不要偷懒。修复过程一旦操作失误,备份就是最后的保险。很多人觉得“我就改个文件名,不会有事”,结果把唯一可用的 fsimage 也搞没了,就只能格式化重建。

4.2 edits 损坏下的重建流程

假设 oev 已经确认edits_inprogress_0000000000000000012尾部损坏,最后一个完整事务 ID 是 150,后续内容读不出来。在非 HA 集群上,我的处理顺序如下:

第一步,停掉 NameNode,避免它继续往坏日志里写新事务:

hdfs dfsadmin -safemode enter hdfs dfsadmin -shutdownDatanode datanode1 # 视情况逐个停 # 找到 NameNode 进程,正常停止

第二步,把可疑的 edits 文件改名留档,不要直接删除:

cd /dfs/nn/current mv edits_inprogress_0000000000000000012 edits_inprogress_0000000000000000012.corrupt

第三步,重新启动 NameNode。如果一切正常,NameNode 会基于上一个 fsimage 和剩余可读的 edits 段启动,并创建一个新的edits_inprogress_0000000000000000015继续写日志。

这里要清楚代价:从损坏节点到最后成功之间的事务全丢了。假如这期间有用户新建了目录、写了文件,这些操作在元数据层面会永久缺失。但数据本身可能还在 DataNode 上,只是 NameNode 不再认为它存在。这种时候要么让上游重新写入,要么用快照恢复对应路径。

如果是 HA 集群,处理的姿势不一样。HA 模式下 edis 主要存在 JournalNode 上,本地 NameNode 只有 fsimage 和少量临时 edits。不要直接改本地 current 目录,正确的做法是:

  1. 把 active NameNode 切到 standby。
  2. 在 JournalNode 上找到最新且健康的 edits 段。
  3. 用 oev 校验该段能够完整回放。
  4. 如果 JournalNode 也坏了,可以从另一台 NameNode 的dfs.namenode.shared.edits.dir对应路径去恢复。
  5. 确认无误后再把 NameNode 拉起来。

4.3 fsimage 损坏时的恢复路线

fsimage 损坏比 edits 损坏更棘手,因为它是启动的基本盘。不过通常不会所有镜像副本一起坏,尤其是dfs.namenode.name.dir配了多目录的情况下。

如果本地多个目录里的 fsimage 全部不可用,但还有 checkpoint 备份目录,可以使用-importCheckpoint恢复。流程大致如下:

# 1. 找一个空目录作为新的 name dir mkdir -p /dfs/nn/recovery # 2. 把可用的 fsimage 放到 checkpoint 目录 # 假设 SecondaryNameNode 或 standby 的镜像在这里 ls /dfs/nn/checkpoint/current/ # 3. 启动时带 importCheckpoint hdfs namenode -importCheckpoint

启动后 NameNode 发现本地 name dir 为空,但 checkpoint 目录里有可用镜像,就会把镜像拷贝过来并加载。需要注意的是,dfs.namenode.checkpoint.dir和dfs.namenode.checkpoint.edits.dir的路径要配置正确,否则它找不到镜像来源。

用 oiv 导出 fsimage 可以辅助确认损坏范围。比如某个目录的元数据记录损坏了,导出 XML 后能看到哪个路径解析失败。但我不建议手工修改 XML 再导回,因为 fsimage 内部有版本、校验和、父子关系等大量约束,没有官方工具支持“改 XML 后直接转回 fsimage”,强行折腾只会制造新问题。

4.4 安全模式退出策略:leave 和 forceExit 的分界线

元数据修复完,NameNode 重新起来,大概率还会卡在安全模式。这时候先别急着退出,我要做一次完整 fsck:

hdfs fsck / 2>&1 | grep -E "Corrupt blocks|Missing replicas|Status"

如果损坏块为 0,直接退出安全模式:

hdfs dfsadmin -safemode leave

如果损坏块已经确认无法自动恢复,且这些文件对业务不关键,或者业务方确认可以接受部分数据缺失,才考虑forceExit。命令很简单:

hdfs dfsadmin -safemode forceExit

但forceExit的本质是告诉 NameNode:别管 threshold 满不满足,先对外服务。它不会自动修好任何东西,只会把缺失的块标记为不可用,让对应的文件读取时直接失败。执行前,必须把 fsck 报告留底,统计出哪些路径会受影响,同步给业务方。否则恢复选了这条路,后续扯皮是少不了的。

我自己的底线是:forceExit 只用于“业务可以接受损失,或者文件可以从外部重新导入”的场景;对于核心业务数据,宁可让集群在安全模式多停一阵,也要先尝试从备份和 JournalNode 恢复,而不是图快强退。

5. 我见过的最典型的翻车现场与事后复盘

5.1 案例一:edits_inprogress 半截日志导致单机集群起不来

某开发集群,机器异常掉电,重启后 NameNode 一直报:

Failed to read transaction log. Recovery failed. Please restart the server with -recover option.

现场人员的第一反应是执行hdfs fsck /,但 fsck 根本跑不起来,因为 NameNode 进程压根没成功启动。后来我用 oev 一个一个试 edits 文件,发现edits_inprogress_0000000000000000099最后几条事务不完整,NameNode 没办法从中间截断回放。

处理方式就是按照上面 4.2 的流程,把坏的edits_inprogress改名留档,基于最近一次完整 checkpoint 重新拉起,丢失了近一个小时内的元数据变更。因为这是开发测试集群,业务方重新导入了一遍数据,影响不算大。但复盘时大家感叹:真正起不来的时候,fsck 根本帮不上忙,能不能救回来取决于你平时对 edits 目录结构熟不熟。

5.2 案例二:fsck -delete 误删损坏块,把业务数据全清了

那是一个生产集群,某个 DataNode 的磁盘出现大量坏块,NameNode 已经通过块汇报把一部分副本标记为 CORRUPT。Value data team 的人看了 fsck 报告,发现有些CORRUPT blocks,顺手就执行了:

hdfs fsck / -delete

这条命令的文档写着“删除损坏文件”,它不是把 DataNode 上那个损坏的块文件删掉,而是把 NameNode 元数据里对应这个块的文件路径整个从命名空间删掉。结果就是,那些原本还有机会通过其他副本恢复的数据,直接从文件系统里消失了。业务方在查询时发现目录还在,但里面的表文件少了一大片,当时差点就炸了。

正确操作应该是:先找出这些损坏块对应哪些 DataNode,坏盘下线,让 NameNode 在健康副本之间重新复制。确认没有任何健康副本时,也要先和业务方核对文件是否可以从源头重建,再决定要不要清理路径。-delete和-move这类会改文件路径的命令,宁可不用也别乱用。

5.3 留下几条硬性习惯给日常运维

这几年跟 NameNode 元数据打交道,攒了几条非常朴素但实用的建议:

第一,dfs.namenode.name.dir一定至少配两个独立磁盘的目录,最好再加一个远端的定期同步目录。单目录等于裸奔。

第二,把 fsck 巡检脚本化,每天扫一遍-list-corrupt-file-blocks,输出超过阈值自动告警。问题在早期发现时,修复成本往往极低。

第三,每次手动干预元数据之前,先备份、先留档、先截图 fsck 报告。出了事故,这些是还原现场和横向对比的唯一依据。

第四,给测试集群做一次“断电演练”。找一个非核心的 Hadoop 节点,直接断电,看 NameNode 到底能不能自动恢复。这个坑早踩比晚踩好,很多生产事故的第一现场都源自没做过这类演练。

我自己的体会是,fsck 和 NameNode 元数据修复是一对组合拳:fsck 负责告诉你“哪里伤着了”,元数据修复负责处理“伤到的是脑子还是四肢”。先分清该修哪一边,再动手,比掌握多少冷门命令都重要。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进