4. 文件系统异常排查:磁盘挂载失败、文件系统损坏、inode耗尽问题

文件系统出问题,说实话是运维日常里最让人头疼的几类故障之一。你想想看,应用跑着跑着突然写不了日志了,数据库报磁盘空间不足但 df -h 一看还有几十 G 空闲……这种时候,十有八九是文件系统层面出了幺蛾子。我这些年处理过的类似 case 少说也有几十个,今天就把最常见的三类问题——挂载失败、文件系统损坏、inode 耗尽——掰开揉碎讲清楚。

核心思路:遇到文件系统异常,先别急着重启。按「挂载状态 → 文件系统完整性 → 空间与 inode 使用率」的顺序排查,能省下大量试错时间。

文件系统异常排查知识结构 文件系统异常 磁盘挂载失败 文件系统损坏 inode 耗尽 fstab 配置错误 UUID 不匹配 设备未就绪 超级块损坏 日志回放失败 fsck 修复 小文件过多 删除后未释放 find + xargs 清理

4.1 磁盘挂载失败

挂载失败这个问题,我遇到过最离谱的一次是凌晨三点被电话叫醒——客户说所有业务都挂了,远程一看,根分区还在,但数据盘全没挂上。后来发现是机房断电重启后,磁盘设备名变了,/etc/fstab 里写的 /dev/sdb1 对不上号了。

排查挂载失败,我个人的习惯是三步走:

  1. 先看 dmesg 和 journalctl——内核会告诉你为什么挂不上。比如设备不存在、文件系统类型不对、超级块损坏等。
  2. 检查 fstab 配置——重点看设备标识、挂载点、文件系统类型、挂载选项。
  3. 手动尝试挂载——用 mount -t ext4 /dev/sdb1 /data 看具体报错。

我的习惯:生产环境的 fstab 里,设备标识一律用 UUID 而不是 /dev/sdX。因为设备名在重启后可能变,UUID 是唯一的。用 blkid 查看 UUID,一劳永逸。

常见的挂载失败原因,我整理了一张表:

报错信息 可能原因 快速解决
mount: special device does not exist 设备节点不存在或设备名变了 用 lsblk 确认设备名,更新 fstab
mount: wrong fs type 文件系统类型指定错误 用 blkid 查看实际类型,修正 -t 参数
mount: you must specify the filesystem type 内核无法自动识别文件系统 手动指定 -t ext4 或 -t xfs
mount: /data: mount point does not exist 挂载点目录不存在 mkdir -p /data 创建目录
mount: /data: device is busy 挂载点或设备被占用 lsof /data 或 fuser -m /data 找出占用进程

注意:千万不要在生产业务高峰期执行 mount -a 重挂所有 fstab 条目。我曾经见过有人手滑把根分区重新挂载到空目录上,系统直接失联。稳妥的做法是先手动挂载测试,确认没问题再写进 fstab。

4.2 文件系统损坏

文件系统损坏,说白了就是磁盘上的元数据结构乱了。表现通常是:读写报 I/O error、目录打不开、df 卡住不动。我记得有一次,一台跑了三年的 NFS 服务器突然报 Structure needs cleaning,用户全在叫——文件读不出来也删不掉。

为什么会损坏?常见原因有:

  • 非正常关机——断电、强制重启,日志没来得及回放。
  • 磁盘硬件故障——坏道、控制器异常。
  • 内核 bug 或驱动问题——少见,但遇到了就是大坑。
  • 超频或内存错误——写入时数据出错,污染了文件系统。

修复工具,Linux 下主要靠 fsck。但不同文件系统有不同玩法:

文件系统 修复命令 注意事项
ext3/ext4 fsck.ext4 -y /dev/sdb1 必须卸载后执行,-y 自动回答 yes
XFS xfs_repair /dev/sdb1 同样需要卸载,XFS 的修复速度通常比 ext4 快
btrfs btrfs check --repair /dev/sdb1 建议先 btrfs check 只读检查,确认问题再修复

重要原则:修复前一定要先做磁盘镜像或快照。尤其是生产环境,fsck 修错了可能丢更多数据。我个人的流程是:先 dd 整盘到备用盘,再在副本上跑修复。

超级块损坏是比较特殊的情况。ext 文件系统在每个块组都存了超级块副本,主超级块坏了可以用备份的:

# 查看备份超级块位置(ext4 为例)
mke2fs -n /dev/sdb1

# 使用备份超级块挂载
mount -o sb=32768 /dev/sdb1 /mnt

# 或者用 fsck 指定备份超级块修复
fsck.ext4 -b 32768 /dev/sdb1

嗯,这里要注意:mke2fs -n 只是模拟创建,不会真的格式化磁盘。它会把备份超级块的位置列出来,你挑一个离主超级块最近的用就行。

4.3 inode 耗尽问题

inode 耗尽,这是个经典坑。你 df -h 一看磁盘还剩 50G,但应用就是报 No space left on device。为什么会这样?因为文件系统能存的文件数量是有限的,这个上限由 inode 数量决定。

我遇到过最夸张的一次,是某个日志系统没配轮转,一晚上生成了 800 多万个几字节的小文件。第二天早上整个分区 inode 用尽,连 touch 一个新文件都做不到。

排查方法很简单:

# 查看 inode 使用情况
df -i

# 找出哪个目录 inode 消耗最多
for i in /*; do echo $i; find $i -xdev -type f | wc -l; done

# 更精细的排查——统计子目录文件数
find /data -xdev -type d | while read dir; do
  count=$(find "$dir" -maxdepth 1 -type f | wc -l)
  echo "$count $dir"
done | sort -rn | head -20

我的经验:df -i 显示的 IUsed% 超过 80% 就要警惕了。尤其是邮件队列、临时文件目录、容器 overlay 层,这些地方最容易 inode 爆炸。我曾经在 /var/spool/postfix 里清理过 200 多万封退信……

清理 inode 的核心思路是删文件,但要注意:

  • 普通删除——rm -f 直接删,但文件太多时 rm * 会报 Argument list too long。
  • 批量删除——用 find 配合 xargs 或 delete 参数:
# 删除 7 天前的小文件
find /data/tmp -type f -mtime +7 -delete

# 或者用 xargs 分批删
find /data/tmp -type f -name "*.log" -print0 | xargs -0 rm -f

# 如果文件数量实在太多,用 perl 或 rsync 清空目录
rsync -a --delete empty_dir/ /data/tmp/

避坑指南:我曾经在清理 inode 时直接用 rm -rf /data/tmp/*,结果因为文件太多,rm 进程跑了几小时还没结束,期间系统负载飙升。后来学乖了,用 find ... -delete 或 rsync --delete,效率高得多。另外,如果文件被进程占用,删了文件但 inode 不会释放——得先 kill 进程或重启服务。

预防 inode 耗尽,我建议从这几个方面入手:

  • 创建分区时规划好 inode 数量——mkfs.ext4 -N 10000000 /dev/sdb1 可以指定 inode 数量,或者用 -T small 优化小文件场景。
  • 配置日志轮转——logrotate 是标配,别偷懒。
  • 监控 inode 使用率——Zabbix、Prometheus 都支持,阈值设到 80% 告警。
  • 定期清理临时目录——/tmp、/var/tmp 这些地方最容易藏污纳垢。

最后说一句,文件系统的问题,很多时候是「防大于治」。你花十分钟配好监控和轮转,可能就避免了一次凌晨三点的大故障。嗯,这些都是我踩过的坑换来的经验,希望对你有用。