去年夏天接了一个 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 异常导致遍历卡死 | 尽快计划离线检查,不要反复 Ctrl+C 重试 |
| 写了文件后 sync/umount 卡住 | 日志或元数据刷盘异常 | 等待超时,避免直接拔盘,按故障处置 |
这些表现的共同点是:它们在真正“挂载失败”之前就会出现。很多事故本来是能救的,但因为没及时看 dmesg,拖到连磁盘都认不出来再处理,就很被动了。尤其是文件系统从 rw 自动变成 ro 这种“软保护”信号,很多人第一反应是 remount 回去,结果一写入又触发新的错误,反而把损坏范围扩大。
2. xfs 元数据损坏的底层原因:日志、超级块与分配组
2.1 xfs 元数据家族:超级块、AG、inode、目录与日志
xfs 是 SGI 设计、后来移植到 Linux 的日志文件系统。它的核心设计之一是把磁盘空间划分为多个 Allocation Group(AG,分配组),每个 AG 都有自己独立的空闲空间管理、inode 分配和 B+树索引。这样做的好处是并发能力很强,不同 AG 可以并行分配和写入。
需要关注的元数据结构主要有这几类:
- 超级块(Superblock):整个文件系统的“户口本”,记录块大小、总块数、AG 数量、根 inode 位置、日志起始块等关键信息。每个 AG 的开头都保留了一份超级块副本,主超级块在 AG0;
- 分配组结构:AGI(inode 分配信息)、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_repair
3.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 bs=4M conv=noerror,sync status=progressconv=noerror,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 bs=4096 count=$logblocks seek=$logstart conv=fsync注意上面 dd 的bs=4096,所以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如果修复程序因为在日志回放阶段失败而中止,说明需要丢弃日志内容,这时候才用-L:
xfs_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 counts- Phase 1:寻找并校验超级块。如果主超级块损坏,这里会尝试扫描备用超级块;
- Phase 2:读取并回放日志。如果日志损坏,通常在这一阶段报错;
- Phase 3:检查目录连通性。它会从根 inode 出发,看看能否通过目录 B+树遍历到每个文件;
- Phase 4:检查并修复目录项的链接计数;
- Phase 5:重建 AG 的头部结构和 B+树层,包括 AGF、AGI、AGFL;
- Phase 6:逐目录、逐文件检查数据映射关系,把找不到归属的文件放入 lost+found;
- Phase 7:再次校验和修正链接计数。
修复过程中会打印进度,耗时跟文件数量正相关,不用焦虑。大于几百 GB 的分区,Phase 3 和 Phase 6 跑几十分钟都很正常。
修复完成后重新挂载并写入一个临时文件验证:
mount /dev/sdb /mnt/test touch /mnt/test/check_write.txt sync能创建文件并正常 sync,说明文件系统已经恢复读写能力。
4.4 超级块损坏时的备用超级块引导
如果损坏的不只是日志,而是主超级块,表现会不一样:mount 直接说 bad superblock,xfs_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. 修复过后的收尾:验证数据完整性,处理 lost+found
5.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 lost+found 里的文件怎么认领
xfs_repair 修复 Phase 6 之后,会把无法通过目录结构定位的文件放进根目录的lost+found。这些文件本身的 inode 还在,但对应的目录项丢了,就像一本书内容还在,但书架上的分类标签和索引卡丢了。
处理 lost+found 时不要慌,先看数量:
ls -lah /mnt/test/lost+found/ find /mnt/test/lost+found -type f | head -50根据文件大小、时间戳、内容特征来判断归属。比如一个 Java 服务目录损坏后,lost+found 里可能出现一堆几十 KB 的 class 文件或者 application.yml;数据库目录损坏后,可能看到未命名的大文件,这时候优先确认对应表空间是否可读,而不是急着改文件名。
直接删除 lost+found 是我见过最危险的操作。至少要保留一到两个完整备份周期,等确认业务数据全部恢复后再清理。
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 事故本来是可以救的,只是我们发现得太晚。不要迷信某个工具能包治百病,真正可靠的是“定期巡检 + 提前备份 + 故障时的冷静流程”。如果你准备把这篇教程当作自己的行动指南,请记住三件事:先备份再修复,诊断顺序永远从硬件到软件,修复后至少追踪一周日志。能做到这三点,你离真正的文件系统故障就会越来越远。