
去年夏天接了一个 Linux 服务器故障工单业务同学反复执行 yum update 都失败报错信息很直接Error: 为仓库 update 下载元数据失败 : cannot download repomd.xml。我按常规思路把镜像源、DNS、防火墙全排了个遍什么问题都没有。最后是 dmesg 里的两行日志暴露了真凶XFS (vda1): metadata I/O error后面跟着 superblock 异常的描述。根文件系统的 xfs 元数据已经处于半损坏状态dnf 在读写缓存目录时拿到 I/O error就把“仓库元数据下载失败”这个锅甩给了网络。那次故障之后我把 xfs 元数据故障的恢复流程完整梳理了一遍并动手复现了关键环节。这篇文章就是那次总结的完整版适合 Linux 运维、系统管理员以及所有用 xfs 当数据盘底层的开发者参考。1. 故障先兆看似软件问题实则是文件系统在求救1.1 从一条 yum 报错说起仓库元数据失败背后的 I/O 错误实际环境中dnf/yum 的“下载元数据失败”绝大多数确实是网络、仓库镜像、TLS 证书导致的真正由文件系统引起的比例不高。但也正因为比例低它特别容易被忽略。问题在于dnf/yum 在解析和下载仓库元数据时需要把 repomd.xml 和元数据包放到/var/cache/dnf下的缓存目录运行事务时还要检查/var/lib/rpm下的 RPM 数据库。这些操作本质都是普通文件读写。如果底层 xfs 有某个目录块或者 inode 损坏读操作会返回 EIO。dnf 只知道自己“拿不到元数据”根本不会告诉你“是磁盘坏了”。所以碰到这类报错我自己的排查顺序很固定先换源、curl 测试、清 DNS 缓存排除常规网络问题立刻看 dmesg 和journalctl -k有没有 XFS 或块设备 I/O error用 smartctl 快速检查磁盘健康状态确认/var/cache/dnf所在分区的文件系统是否还能正常读写。如果前两步没有任何异常基本可以放心追网络如果 dmesg 里有 xfs 关键字那就不要再折腾 yum 了先解决文件系统。1.2 需要立即警觉的异常表现清单下面这些现象是我在处理和排查 xfs 故障时总结出来的“报警清单”按出现频率排序。现象可能的元数据问题初步动作mount 提示 Structure needs cleaning文件系统被标记为不一致元数据操作未完成立即卸载准备 xfs_repair -n访问某个文件报 Input/output error但文件明明存在inode 或目录 B树节点损坏记录出错路径检查 dmesg计划离线修复dmesg 出现 XFS (sdX): metadata I/O error元数据读写失败可能伴随磁盘坏道先查 smartctl再做只读挂载与备份文件系统自动从 rw 变成 ro内核检测到 I/O 错误后自动降级保护不要强行 remount,rw先备份再修复ls 目录长期卡住或命令执行无响应目录结构或 AGI 异常导致遍历卡死尽快计划离线检查不要反复 CtrlC 重试写了文件后 sync/umount 卡住日志或元数据刷盘异常等待超时避免直接拔盘按故障处置这些表现的共同点是它们在真正“挂载失败”之前就会出现。很多事故本来是能救的但因为没及时看 dmesg拖到连磁盘都认不出来再处理就很被动了。尤其是文件系统从 rw 自动变成 ro 这种“软保护”信号很多人第一反应是 remount 回去结果一写入又触发新的错误反而把损坏范围扩大。2. xfs 元数据损坏的底层原因日志、超级块与分配组2.1 xfs 元数据家族超级块、AG、inode、目录与日志xfs 是 SGI 设计、后来移植到 Linux 的日志文件系统。它的核心设计之一是把磁盘空间划分为多个 Allocation GroupAG分配组每个 AG 都有自己独立的空闲空间管理、inode 分配和 B树索引。这样做的好处是并发能力很强不同 AG 可以并行分配和写入。需要关注的元数据结构主要有这几类超级块Superblock整个文件系统的“户口本”记录块大小、总块数、AG 数量、根 inode 位置、日志起始块等关键信息。每个 AG 的开头都保留了一份超级块副本主超级块在 AG0分配组结构AGIinode 分配信息、AGF空闲空间信息、AGFL空闲列表它们管理每个 AG 内部的资源inode每个文件/目录的核心描述结构存属主、权限、时间戳和文件数据块的位置映射目录结构xfs 目录用 B树组织目录项与 inode 对应日志log记录元数据修改事务的环形缓冲区默认在文件系统内部叫 internal log。如果把这些结构类比成一家线下图书馆超级块是图书馆的总索引台账AG 是各个阅览室inode 是每本书的借阅卡目录就是书架上的分类标签日志则是管理员每次整理书架后的流水记录。断电瞬间管理员可能只把一本书的新位置写到了流水账里还没来得及更新分类标签和借阅卡这就造成了元数据不一致。2.2 为什么断电和异常重启特别容易踩中元数据xfs 的日志机制已经比较成熟元数据变更会先按事务写入日志再更新实际元数据位置。日志回放时要么完整重放要么丢弃理论上不会让文件系统停留在“写了一半”的状态。但现实中有几个窗口会让这个机制失效突然断电且没有 UPS即使有日志如果日志本身所在的盘区在写一半时掉电或者 RAID 卡写缓存策略设置不当、电池失效写序就得不到保证内核 panic 或强制 reboot -f有些人贪图快不等 sync 就重启虽然大多数时候没事但 IO 栈里积压的写请求可能被截断磁盘控制器或 SSD 固件 bug某些固件在异常掉电后把有问题的写请求“回放”到错误位置日志和元数据位置都会遭殃内存不稳定或损坏元数据在内存中被改坏再写回磁盘问题从源头产生。这也是为什么生产环境服务器标准配置都建议 ECC 内存。一个反直觉的结论是xfs 的日志能保证“已提交事务”的一致性但它无法防止硬件本身撒谎。如果磁盘控制器返回了假的写完成信号日志和数据块都可能处于任意状态。这也是为什么“先确认硬件再修文件系统”是必须的流程。2.3 磁盘硬件故障如何在元数据层面体现磁盘坏道如果恰好落在元数据区域表现就非常直接读取 inode 或目录块时返回 EIO。坏道很小的时候其他区域的数据都正常文件系统损坏范围也有限但如果 SSD 的 FTL 映射表损坏或者是 RAID 卡逻辑卷上的阵列降级可能出现大量 IO 错误xfs 根本来不及把错误隔离。所以判断 xfs 故障时永远要把“文件系统软件问题”和“底层存储硬件问题”分开。前者能通过 xfs_repair 修后者修完文件系统还会继续坏甚至越修越糟。在我经手的多数案例里文件系统报错只是表面现象真正的病根在硬盘或控制器。2.4 一个常见的误判df 显示有空间却写不进文件这块要单独拎出来讲是因为我见过不少人把“写不进文件”直接当成“文件系统坏”。df -h显示还有空间但写入时报No space left on device先别急。检查两件事df -h /data df -i /data xfs_info /datadf -i看 inode 是否耗尽xfs_info看文件系统的 AG 数量和空闲块分布。xfs 的动态 inode 机制让它不太容易出现 inode 耗尽但如果你创建文件系统时设置了过小的 inode 比例或者某个 AG 被写满而其他 AG 还有大量空闲都有可能出问题。这种情况下要做的是重新规划文件系统布局或删除无用文件而不是跑修复工具。3. 动手前的诊断与数据保全该不该马上跑 xfs_repair3.1 诊断四步dmesg、journalctl、mount、smartctl拿到一台 xfs 异常机器我的标准流程是四步。第一步看内核日志。这一步最重要dmesg -T | grep -iE xfs|I/O error|scsi|nvme | tail -100如果是 systemd 系统也可以看journalctl -k -b。重点找三类关键字XFS (sda1): metadata I/O error、block device ... I/O error、Buffer I/O error。第二步检查当前挂载状态。还挂载着的话立刻用只读方式重新挂载阻止继续写入mount -o remount,ro /data如果 remount 失败说明系统可能已经强制把文件系统切到只读注意不要在任何提示下强行remount,rw否则可能扩大损坏。第三步看磁盘健康。HDD 和 SSD 的 SMART 信息都要看smartctl -x /dev/sda重点关注Reallocated_Sector_Ct、Current_Pending_Sector、Offline_Uncorrectable几个计数。如果坏块计数在增长说明盘已经在物理层面出问题应该先考虑换盘和数据整体迁移。第四步检查最近是否有掉电、重启、硬件 reset 记录last -x | head -30 journalctl -k -b -1 | grep -iE shutdown|power|panic这一步能帮你还原故障场景是意外断电引发的日志问题还是磁盘控制器连续 reset 导致的处理方向完全不同。3.2 数据保全优先只读挂载、块级镜像与 LVM 快照很多人拿到报错第一个念头是“赶紧跑 xfs_repair”。我的建议相反能先保住数据绝不直接修复。如果文件系统还能只读挂载先把最关键的数据抄出来mkdir -p /backup/recovery mount -o ro /dev/sdb1 /mnt/recover rsync -a /mnt/recover/ /backup/recovery/如果要更彻底的保全做块级镜像。这是应对“修复程序把数据修得更糟”的最强保险dd if/dev/sdb of/safe/disk_sdb.img bs4M convnoerror,sync statusprogressconvnoerror,sync让它遇到坏块也能继续坏块位置会填充零。做完镜像后所有后续修复操作都可以先在镜像上演练一遍确认没问题再对原盘动手。如果原始数据在 LVM 上先打快照更高效lvcreate -L 20G -s -n snap_data /dev/vg0/lvdata然后对快照跑 xfs_repair观察修复程序的行为和输出确定风险可控后再处理原卷。3.3 xfs_repair -n 的“只检查不修复”边界xfs_repair 提供-n选项表示 no modify只做检查、不写任何改动。这是风险最低的探测手段。在文件系统已卸载的前提下运行xfs_repair -n /dev/sdb1输出会像正式修复一样进入各个阶段但不会真正改动磁盘。看到类似would have reset bad sb、would have corrected之类的字样就说明确实存在需要修复的问题。有一点必须提醒xfs_repair -n同样不能对已挂载的文件系统运行否则会直接退出并提示文件系统正在使用中。如果是修复根分区需要从 Live CD 或救援模式启动确认目标分区未被占用后再操作。4. xfs_repair 实战日志损坏场景的完整修复链路4.1 构造一个可控的故障实验与其纸上谈兵不如自己制造一次计时可控的故障。我建议你在虚拟机里操作整个流程跟生产环境完全一致但可以大胆试错。下面用/dev/sdb作为测试盘。mkfs.xfs -f /dev/sdb mkdir -p /mnt/test mount /dev/sdb /mnt/test for i in $(seq 1 200); do cp /etc/hostname /mnt/test/file${i}.txt done sync umount /mnt/test接下来找到日志区的位置。xfs_db 是 xfsprogs 自带的调试工具-r表示只读xfs_db -r -c sb 0 -c p logstart -c p logblocks /dev/sdb输出里logstart是日志起始的文件系统块号logblocks是日志占用的块数。我们用一个变量把两块信息保存下来然后用 urandom 覆盖这整段区域logstart$(xfs_db -r -c sb 0 -c p logstart /dev/sdb | awk {print $3}) logblocks$(xfs_db -r -c sb 0 -c p logblocks /dev/sdb | awk {print $3}) dd if/dev/urandom of/dev/sdb bs4096 count$logblocks seek$logstart convfsync注意上面 dd 的bs4096所以seek$logstart的单位是 4096 字节的块而不是 512 字节扇区。如果搞错单位可能覆盖到其他区域实验就会失真。这里用 urandom 而不是/dev/zero也是有讲究的。日志区全零时内核可能把日志识别为空闲状态挂载反而会成功而随机数据会让内核在解析日志记录时直接失败更贴近真实损坏场景。这时候再挂载mount /dev/sdb /mnt/test正常情况下会失败现象可能是Structure needs cleaning也可能直接报 I/O error。dmesg 里能看到类似Invalid xfs log record之类的信息。这个实验的目的就是模拟“日志区域损坏但超级块和其他结构尚完好”的场景这是日常运维中非常典型的一类 xfs 元数据故障。4.2 关键决策点什么时候直接上 -L遇到日志损坏最常见的修复流程是xfs_repair -n /dev/sdb如果日志区域完全无法解析-n阶段很可能在读取日志时就报错。接下来运行正式修复xfs_repair /dev/sdb如果修复程序因为在日志回放阶段失败而中止说明需要丢弃日志内容这时候才用-Lxfs_repair -L /dev/sdb-L的含义是强制清零日志force log zeroing。它做两件事把日志标记为干净、丢弃其中所有未提交的元数据操作。对已确认 fsync 落盘的文件数据没有影响真正丢失的是系统在崩溃前还没来得及提交的那部分元数据变更。比如你可能刚创建了一个文件但 fsync 没执行这个文件的目录项和 inode 信息还留在日志里没来得及落到正式位置。我的经验是日志损坏时该用 -L 就果断用不要反复尝试不带 -L 的修复。每次尝试都会让日志回放失败而且可能在那个不稳定的日志区域反复读写。先确认日志坏了就直接清。提示-L只应针对日志损坏场景使用。日志正常时加-L会把本来可用于回放的有用信息也丢掉属于过度修复可能加重文件系统的不一致。但 -L 不是万能钥匙。如果是普通元数据错乱而日志本身正常-L 反而会丢掉可能有用的回放信息所以不要一上来就加。4.3 修复过程的七个阶段与输出解读xfs_repair 的输出分为七个阶段我拆开讲Phase 1 - find and verify superblock... Phase 2 - using internal log Phase 3 - check and repair directory connectivity Phase 4 - check and repair links counts Phase 5 - rebuild AG headers and level of the B-tree Phase 6 - check and repair the directory and file data Phase 7 - verify and correct link countsPhase 1寻找并校验超级块。如果主超级块损坏这里会尝试扫描备用超级块Phase 2读取并回放日志。如果日志损坏通常在这一阶段报错Phase 3检查目录连通性。它会从根 inode 出发看看能否通过目录 B树遍历到每个文件Phase 4检查并修复目录项的链接计数Phase 5重建 AG 的头部结构和 B树层包括 AGF、AGI、AGFLPhase 6逐目录、逐文件检查数据映射关系把找不到归属的文件放入 lostfoundPhase 7再次校验和修正链接计数。修复过程中会打印进度耗时跟文件数量正相关不用焦虑。大于几百 GB 的分区Phase 3 和 Phase 6 跑几十分钟都很正常。修复完成后重新挂载并写入一个临时文件验证mount /dev/sdb /mnt/test touch /mnt/test/check_write.txt sync能创建文件并正常 sync说明文件系统已经恢复读写能力。4.4 超级块损坏时的备用超级块引导如果损坏的不只是日志而是主超级块表现会不一样mount 直接说 bad superblockxfs_repair 可能提示找不到有效超级块。xfs 在设计上把每个 AG 的第一个块都作为超级块副本。AG0 是主超级块AG1、AG2……都有备份。实际经验是xfs_repair 启动时会自动扫描这些备份。如果主超级块损坏它有可能直接找到并提示当前备用超级块的位置按提示继续就行。如果扫描失败可以用 xfs_db 查看能读到的 AG 超级块或者根据 mkfs 时期的布局手动计算 AG1 起始块号AG1 起始块号 AG0 的 agblocks然后用-b参数指定xfs_repair -b AG1起始块号 /dev/sdb修复完成后主超级块会被重建正常 mount 即可。这个场景下我强烈建议先把原盘完整 dd 镜像出来再操作因为超级块损坏往往伴随更大面积的元数据问题修复程序的选择可能导致文件系统布局和原结构不一致有镜像才有一切重来的余地。5. 修复过后的收尾验证数据完整性处理 lostfound5.1 挂载检查不能只看 mount 成功mount 成功不代表所有文件都完好。xfs_repair 修复的是元数据一致性不等于恢复全部数据。我修复后的验证习惯是mount /dev/sdb /mnt/test find /mnt/test -xdev -type f | wc -l对比故障前的文件数量。如果之前有备份再用rsync --dry-run或diff -r抽查关键目录确认重要文件内容没变化。像配置文件这类小文本直接 md5sum 对比即可。在允许的前提下我还会用 xfs_scrub 做一次在线体检。xfs_scrub 是 xfsprogs 5.0 以后提供的在线检查工具运行在已挂载文件系统上能发现很多离线修复发现不了的问题xfs_scrub /mnt/test它对业务影响比 xfs_repair 小很多适合修复完之后的第二天夜间执行一轮。还有一个细节修复后第一周每天看一遍 dmesg 里的 xfs 相关日志。如果又出现 metadata I/O error说明底层硬件问题没解决文件系统只是暂时的安全区。5.2 lostfound 里的文件怎么认领xfs_repair 修复 Phase 6 之后会把无法通过目录结构定位的文件放进根目录的lostfound。这些文件本身的 inode 还在但对应的目录项丢了就像一本书内容还在但书架上的分类标签和索引卡丢了。处理 lostfound 时不要慌先看数量ls -lah /mnt/test/lostfound/ find /mnt/test/lostfound -type f | head -50根据文件大小、时间戳、内容特征来判断归属。比如一个 Java 服务目录损坏后lostfound 里可能出现一堆几十 KB 的 class 文件或者 application.yml数据库目录损坏后可能看到未命名的大文件这时候优先确认对应表空间是否可读而不是急着改文件名。直接删除 lostfound 是我见过最危险的操作。至少要保留一到两个完整备份周期等确认业务数据全部恢复后再清理。5.3 修复失败或损坏过重时的兜底思路有一种情况比较尴尬xfs_repair 跑了很多轮文件系统还是挂不上。这时候不要继续在原盘上反复尝试。我的兜底顺序是用xfs_metadump把元数据导出来交给更有经验的同事或社区分析xfs_metadump /dev/sdb /tmp/sdb.metadump注意 metadump 是拷贝元数据不修改原盘适合做远程诊断。升级 xfsprogs 到更新版本再试。xfs 文件系统格式在演进高版本 xfs_repair 对 v5 超级块、更大文件系统、更复杂目录结构的兼容和修复能力明显更强。如果确认是目录块或 inode 块被坏道物理破坏先把整个盘/分区做成镜像在镜像上用 strings、photorec 等工具做文件级抢救。最终兜底永远是备份回滚。这就是为什么我一直强调修复前先有备份/镜像比修复工具本身更能决定数据结局。6. 避免再次翻车从硬件监测到运维习惯6.1 底层硬件的五个检查点xfs 元数据故障里相当大一部分是“硬件背锅”。我从五件事入手能挡掉一多半的重复故障RAID 卡/SSD 固件保持在厂商推荐的稳定版本。不要在关键存储设备上追新固件也不要不升级长期不维护的固件定期跑 smartctl。写到 cron 里每周自动记录 SMART 数据数值突变时及时告警重要机器配 UPS。断电对没有掉电保护的 SSD 是明确风险对带写缓存的 RAID 卡风险更大有条件上 ECC 内存。内存比特翻转导致文件系统元数据写坏的现象虽然概率低一旦发生就是大面积损坏SSD 采购优先企业级产品。消费级 SSD 在掉电保护、FTL 健壮性上和真正的企业级产品差距很大。6.2 运维层面的挂载、卸载与备份规范操作规范上有几条是我踩过坑之后才真正执行的不要贪图快直接reboot -f。正常 shutdown 会让系统把日志、缓存干净地收尾关机前养成sync umount的习惯尤其是外置盘和数据盘对重要数据启用 LVM 快照 定时备份。一次快照的代价远低于一次数据丢失文件系统出现异常时第一时间mount -o remount,ro然后走诊断流程而不是继续在写负载下硬撑。6.3 针对 xfs 的监控项与最小告警集最后给一份运维上可落地的监控清单监控项命令/方法告警条件xfs 内核错误日志采集 dmesg/journald出现 metadata I/O error、XFS 关键字文件系统降级为只读监控 /proc/mounts 的 rw/ro期望 rw 却变成 ro磁盘 SMART 状态smartctl -H /dev/sdX健康状态非 PASS或坏块计数增长文件系统一致性巡检xfs_repair -n低峰期提前卸载或只读输出含 would have 等修改提示日志回放耗时重启完成时间/最近 mount 时间明显偏离基线这套告警集不需要额外采购商业监控脚本加定时任务加已有监控平台就能覆盖。xfs 是成熟可靠的文件系统绝大多数严重事故都不是一天发生的只要把上述信号的告警做起来就能把故障掐在萌芽阶段。处理了这些年 xfs 故障我最有感触的一点是大多数 xfs 事故本来是可以救的只是我们发现得太晚。不要迷信某个工具能包治百病真正可靠的是“定期巡检 提前备份 故障时的冷静流程”。如果你准备把这篇教程当作自己的行动指南请记住三件事先备份再修复诊断顺序永远从硬件到软件修复后至少追踪一周日志。能做到这三点你离真正的文件系统故障就会越来越远。