2026/9/16 2:49:06

磁盘空间告警排查:df/du对不上、inode耗尽等7个深坑复盘

磁盘空间告警排查:df/du对不上、inode耗尽等7个深坑复盘 晚上十一点告警群突然连着弹出三条消息/data 使用率 93%、95%、97%我第一反应是日志又刷爆了第二反应是千万别碰上 inode 耗尽。爬起来开终端深呼吸先做三分钟定位而不是直接冲上去rm -rf。磁盘空间告警这个问题表面上看都是“磁盘满了”但背后原因千差万别。我这些年踩过的坑加起来能写满一整篇df和du对不上、inode 耗尽、文件删了但空间没释放、删文件删到系统 IO 打满……每一种都曾让我在深夜里怀疑人生。这篇文章我把最典型的 7 个深坑完整复盘一遍重点讲df/du对不上、inode 耗尽、已删除文件不释放这三类最让人头疼的情况最后会附上一份可以直接抄作业的排查流程和命令模板。先声明一下下面所有命令和思路都以 Linux 环境为例CentOS / Ubuntu / Debian 都适用文件系统主要讲 ext4 和 xfs这两个在生产环境里最常见。1. 告警来了先别慌按这三步做初步定位1.1 第一步先分清楚是空间满还是 inode 满坑1很多人看到“磁盘空间告警”就直接开始找大文件删这是最危险的起点。第一步一定要先区分到底是数据块空间满了还是 inode 表满了。这两个都叫“磁盘满了”但处理方式完全不同。df -h看的是我们通常意义上说的磁盘空间反映的是文件系统里数据块的占用情况而df -i看的是 inode 使用率。inode 是每个文件或目录的元数据索引一旦 inode 被占满即使磁盘还剩大量空闲块系统也会报No space left on device。我每次排查都先跑两条命令df -h df -i输出大概是这样的$ df -h Filesystem Size Used Avail Use% Mounted on /dev/sda1 99G 95G 3.8G 97% / $ df -i Filesystem Inodes IUsed IFree IUse% Mounted on /dev/sda1 6.5M 6.1M 0.4M 94% /如果df -h显示 / 分区用了 97%基本就是空间问题如果df -h只用了 40%但df -i的 IUse% 已经到了 100%那就是 inode 耗尽。这个判断是整个排查的地基地基错了后面全白干。1.2 第二步用 du 逐层定位目录级“大户”确认是空间问题后再用du一层层往下找看哪个目录最占空间。我的习惯是先统计文件系统根目录下的第一层子目录du -x -h --max-depth1 /data 2/dev/null | sort -rh | head -20这个命令有几个关键点。-x必须带意思是不要跨文件系统统计。如果/data下面挂着独立分区或 tmpfs不带-x会把别的分区的容量也加进来统计结果和df对不上又白白多踩一个坑。sort -rh是按人类可读数字反向排序最大的目录直接出现在最上面。找到最大目录后继续往下一层钻du -x -h --max-depth2 /data/logs 2/dev/null | sort -rh | head -20反复几次基本能定位到具体是哪个子目录、哪个文件在疯狂增长。这个“逐层下钻”看起来简单但比你直接du -sh *然后拿眼睛扫要快得多。1.3 第三步动手删除前先查进程占用空间告警时最忌讳的就是“找到一个大文件顺手就rm了”。在 Linux 上rm只是把文件名从目录项里摘掉如果这个文件正被某个进程打开它的数据块并不会立刻释放df显示的使用率可能毫无变化。更麻烦的是你已经把名字删了再想定位是谁占着它就得绕一大圈。所以我的原则是任何大文件在删除之前先看一眼它是不是被进程占用lsof L1 2/dev/null | head -50L1会列出所有被进程打开但已经删除的文件这是排查“已删除不释放”最直接的工具。下面详细说这个坑的原理和清理方式。2. du 和 df 对不上的两个深坑2.1 坑2df 显示已满du 统计还不到一半已删除未释放这个现象我遇到太多次了。某次线上监控告警/var使用率 100%我赶紧执行du -sh /var结果只有 20G而df显示/var已经用了 100G差了整整 80G。第一次遇到的人很容易以为是系统出 bug 了其实原理特别简单。df统计的是整个文件系统已用的块它看的是文件系统层而du是从目录树往下遍历统计的是每个文件占用的块。如果一个文件已经被rm删除它的名字从目录树中消失了du自然统计不到它但如果有进程仍然持有这个文件的句柄文件数据块就还挂在文件系统上没有被释放df照样把它算进已用空间。于是出现df大、du小的经典差异。打个比方你往垃圾桶里丢了一张纸条但手还攥着纸条的另一端没松手。垃圾桶以为自己还满着只有你松手进程释放文件描述符垃圾才真正被清走。在 Linux 世界里“攥着纸条的手”就是打开文件的进程。这类情况最常见的就是服务在写日志比如 Java 应用用 logback 写文件运维用rm删了日志但 JVM 进程没重启旧的文件描述符还指向那个已删除的 inode于是日志空间永远释放不了。2.2 实战找到被进程占用的已删除文件并释放定位这种“幽灵文件”我最常用的命令是lsof -nP 2/dev/null | grep deleted | awk {print $1, $2, $7, $NF} | sort -k3 -rh | head -30输出里你会看到类似这样的行java 2345 49152000 /tmp/logs/app.log (deleted) nginx 8712 10485760 /var/log/nginx/access.log (deleted)第一列是进程名第二列是 PID第三列是文件大小字节最后一列是原始路径加(deleted)标记。按第三列倒序排列一眼就能看到是谁占着最大的“幽灵空间”。找到进程后释放方式有两种。第一种是重启进程或者让业务方 reload让进程重新打开一个新的日志文件旧的 fd 自然释放。第二种是直接清空进程持有的 fd不用重启进程# 找到进程持有的 fd 编号 ls -l /proc/2345/fd | grep deleted # 把 fd 指向的文件直接截断为 0 : /proc/2345/fd/3注意: 会瞬间将 fd 对应文件截断为空df使用率立刻下降业务进程还能继续往里面写相当于把日志文件“清空重来”。这个方法在不能重启进程的生产环境特别有用但操作前一定要确认 fd 编号不要弄错。需要提醒的是清空日志文件只是治标如果你不希望日志无限增长还是要把日志轮转logrotate配置好。不少项目默认配置了按天轮转但因为copytruncate和create的参数选错导致轮转出来的文件没有被正确接管最后又演变成“日志文件被进程打开后删不掉”。2.3 坑3隐藏的挂载点让统计对象根本不一致另一个常见的du和df对不上原因是挂载点掩盖。很多业务目录下面会单独挂一块盘比如/data/logs是独立分区。如果你执行df /data看到的是/data所在分区的情况而日志不断增长的其实是/data/logs挂载的那块盘。两边根本不是同一个文件系统自然对不上。反过来如果你执行du -sh /data时没有加-xdu会跨到/data/logs的独立分区里去统计把另一块盘的占用也算进来和df /data的结果严重偏离。我的建议是每次排查都先df -h把所有分区的使用率罗列出来确认到底是哪块盘在告警。如果告警的是/data但/data/logs是独立挂载盘你得单独对/data/logs做df和du这才是准确的数据。目录下有隐藏挂载点的情况用mount | grep /data一眼就能看出来。3. inode 耗尽比空间满更隐蔽的深坑3.1 坑4空间还有 60%却报写不进去有一次同事反馈某个应用写不了文件系统一直报No space left on device。我查了df -h磁盘空间明明还剩 60%瞬间意识到八成是 inode 满了。赶紧执行df -i果然/data的 inode 已用 100%。也就是说文件系统里所有可用的文件索引节点已经分配完了就算有再多的空间也创建不了新文件。inode 和空间的对应关系在磁盘格式化时基本就定死了。以 ext4 为例默认每个 inode 对应的字节数有一个默认值如果块大小是 4KB默认 bytes-per-inode 是 16384那么理论上一个 1TB 的分区最大 inode 数约等于 1TB 除以 16KB也就是 6400 万左右。这个数决定了文件系统大约能容纳多少个小文件。如果你的应用疯狂创建几十 KB、几 KB 甚至 0 字节的小文件inode 消耗速度会远超空间消耗速度。这个场景在缓存目录、消息队列临时文件、日志按秒拆分、监控采集落盘目录里极其常见。我遇到过最夸张的一次某个采集目录下堆了 380 万个小文件每个文件只有几百字节空间才用了不到 10GBinode 却直接耗尽。3.2 快速定位哪个目录在疯狂生成小文件inode 耗尽之后是找不到单个大文件的问题在于文件数量太多。定位思路要从“找大文件”切换成“找文件数量多的目录”。我常用的命令find /data -xdev -printf %h\n 2/dev/null | sort | uniq -c | sort -rn | head -20这条命令会把/data下所有文件的父目录打印出来统计每个目录下的文件数量按数量倒序。输出第一眼就能看到哪个目录堆了几十万甚至几百万个小文件。比如3802310 /data/cache/tmp 1204500 /data/msgqueue/spool 88931 /data/logs/2024/06如果find遍历太慢可以用du --inodes试试GNU coreutils 8.22 以上版本支持这个选项du -x --inodes --max-depth2 /data 2/dev/null | sort -rn | head -20它会在目录级别直接统计 inode 数量速度比find全量遍历快非常多。3.3 临时清理与格式化参数的长期规划确认是 inode 耗尽后紧急处理就是删掉过期的小文件。比如缓存目录下的临时文件设定保留 3 天后批量删除find /data/cache -type f -mtime 3 -delete 2/dev/null这里我用了-delete而不是-exec rm {} \;。原因是-delete是find的内置动作不需要为每个文件启动外部进程对几十万小文件来说效率天差地别。如果你删的还是太慢可以先mv把目录改名再后台慢慢删先把写入能力恢复。长期治理有几个方向高频小临时文件尽量用 tmpfs 或单独分区隔离避免影响到主业务文件系统。业务端能优化的优先优化小文件合并写入、按时间归档、限制目录内文件总个数。格式化阶段就规划好 inode 密度。ext4 可以用mkfs.ext4 -i 8192 /dev/sdb1把 bytes-per-inode 调小从而增加可用 inode 数xfs 可以通过mkfs.xfs -i maxpct10调整 inode 最大占比。已经运行的 ext4 文件系统如果 inode 不够单纯resize2fs扩空间不会改变已有 inode 数量通常只能重新格式化再恢复数据或者迁移到 inode 规划更充裕的新文件系统。关于“能不能给 ext4 动态加 inode”网上有些说法但我明确说明下ext4 没有原生、可在生产环境简单操作的动态 inode 扩容机制不要被野路子误导。踏踏实实做容量规划或者数据迁移是最稳妥的。4. 另外三个高频坑一次讲透4.1 坑5稀疏文件让 du 和 df 呈现“倒挂”比df大、du小更迷惑的一种情况是du显示的文件大小远大于df上实际减少的空间或者反过来文件明明很大但du统计出来很小这就是稀疏文件。稀疏文件指逻辑大小很大、实际占用磁盘块很少的文件。比如程序用lseek跳着写文件中间留了大量空洞文件逻辑上可能有 10GB实际占用的磁盘块只有 1MB。这种情况下du默认按照实际占用块统计可能只显示 1M但用ls -lh或stat看逻辑大小会看到 10G。当你在排查“磁盘没减少但文件看着很大”时看到du和ls -lh对不上要想到稀疏文件这回事别拿它当磁盘占用大户反复研究方向偏了。判断方式stat 文件名看Size和Blocks两个字段。Size是逻辑大小Blocks是实际占用的 512 字节块数。如果Size很大但Blocks很小基本就是稀疏文件。这类文件常见于数据库预分配文件、某些加密盘镜像、部分下载工具临时文件。处理上一般不需要特殊操作但要注意不要用du的默认输出去判断磁盘占用必要时用du --apparent-size看逻辑大小。4.2 坑6rm -rf 百万小文件直接把系统 IO 打满有一次磁盘告警原因是日志目录里有上百万个小文件。我直接rm -rf那个目录结果命令执行到一半系统 iowait 飙升到 90% 以上rm卡在那里一动不动df看着也没降多少。原因很简单删除一个文件不只是删数据块还要逐条更新目录项、inode 位图、文件系统日志。海量小文件意味着海量元数据操作磁盘 IO 瞬间被打满业务读写也跟着受牵连。处理这种场景我有两个习惯。第一个如果目录可以整体放弃优先“先改名、后清理”。把原目录mv成.deleting让业务用新目录重新创建然后再后台慢慢删除旧目录避免影响线上mv /data/logs /data/logs.deleting mkdir /data/logs chmod 750 /data/logs chown app:app /data/logs第二个删除海量小文件不要直接裸rm -rf我一般会选低峰期用 rsync 的 delete 模式处理rsync -a --delete /tmp/empty_dir/ /data/logs.deleting/命令执行完目录里的文件也就清得差不多了。在没有 rsync 的环境至少用find /data/logs.deleting -type f -delete也比直接rm -rf稳定。说到底删除海量小文件拼的是元数据 IO 的效率硬删不是不行但代价是系统性能会瞬间崩掉。4.3 坑7误删文件后又继续写入恢复无门还有一类坑不是环境的问题是人手的问题告警太急删错文件。比如在生产目录上执行rm -rf通配符写错把正在使用的配置、日志、甚至备份文件删了。误删之后最忌讳的是继续在这个文件系统上大量写入。正确顺序是第一时间把相关分区挂载成只读或者先停掉可能写入的进程马上用工具尝试恢复。ext4 可以用debugfs或extundeletexfs 的原生恢复能力很弱通常只能靠备份恢复。这里我更想强调的是习惯在生产环境执行删除前我几乎不会直接敲rm而是先把“删除清单”列出来用find或ls先确认一遍再删。比如find /data/app/logs -name *.log -mtime 30 | head -50先看清单确认无误再执行删除。尤其是涉及通配符、变量拼接的命令务必先echo展开看看。这个习惯能帮你避免 90% 的误删事故。5. 一套可以直接复制的排查流程与预防建议5.1 从告警到定位的 10 分钟排查模板把上面这些坑归纳成一条流水线。我每次碰到磁盘空间告警基本按下面的顺序执行10 分钟内能定位 80% 的问题。步骤命令目标1df -h确认哪些分区空间使用率高2df -i确认是否 inode 耗尽3lsof L1 | grep deleted排查已删除未释放的文件4du -x --max-depth1 -h /path | sort -rh定位目录级空间大户5du --inodes --max-depth2 /path | sort -rn定位 inode 大户6ls -lh /proc/进程PID/fd配合第 3 步找到持有的文件句柄7stat 怀疑文件确认稀疏文件等异常执行完第 1、2 步基本就能把问题归成四类空间满、inode 满、已删未释放、挂载点或稀疏文件导致的假象。后面步骤按类对号入座不用盲目乱扫。5.2 处理动作的优先级排序处理优先级我一般这样排如果有业务进程还保持着已删除文件优先释放这些句柄。这个动作能最快恢复业务写入能力代价也最低。如果是 inode 耗尽先去清临时缓存目录。这类目录通常有业务自清理逻辑或过期时间删旧文件风险最小。如果是真正的空间满先找日志文件。很多场景下日志轮转没有真正生效才是告警的主因。最后才动大文件手工迁移或删除。动手前一定要确认文件用途和备份状态。还有一个需要提醒的误区很多人看到磁盘告警就顺手truncate -s 0截断大文件。这个操作本身没问题但如果该文件正被进程持续写入你截断之后进程继续写会产生一个大小重新增长的文件下次告警依旧出现。更合理的做法是让业务服务自己处理比如配置好 logrotate 的copytruncate或create或者重启业务进程让它重新打开新文件。5.3 长期运维习惯把磁盘告警扼杀在萌芽最后说几个我长期沉淀下来的预防习惯。日志轮转要强制落地。logrotate 的daily、size、rotate数量都要检查尤其注意copytruncate和create的区别选择要匹配业务进程的写入方式。监控不仅要盯df -h更要把df -i的使用率加进告警。inode 告警通常比空间告警提前一周甚至一个月出现提前处理比事后救火舒服太多。临时目录要设置 cleanup 定时任务。我习惯每天凌晨跑一次find mtime -delete把过期临时文件清掉代价很低收益很大。大文件迁移和清理要写进操作文档。别等告警来了再临时翻命令越慌越容易出错。养成习惯后磁盘告警基本就是一条流水线df -h、df -i、lsof | grep deleted、du -x下钻定位、处理、恢复一气呵成。从我自己的实战经验看磁盘告警救火并不难难的是每次都不分青红皂白就乱删一通。搞清楚du/df差异背后的文件系统原理理解 inode 和空间的独立消耗逻辑再加上一份清晰的排查流程晚上十一点的告警群也会变得没有那么吓人。