news 2026/9/16 2:49:01

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
磁盘空间告警排查:df/du对不上、inode耗尽等7个深坑复盘

晚上十一点,告警群突然连着弹出三条消息:/data 使用率 93%、95%、97%,我第一反应是日志又刷爆了,第二反应是千万别碰上 inode 耗尽。爬起来开终端,深呼吸,先做三分钟定位,而不是直接冲上去rm -rf

磁盘空间告警这个问题,表面上看都是“磁盘满了”,但背后原因千差万别。我这些年踩过的坑加起来,能写满一整篇:dfdu对不上、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-depth=1 /data 2>/dev/null | sort -rh | head -20

这个命令有几个关键点。-x必须带,意思是不要跨文件系统统计。如果/data下面挂着独立分区或 tmpfs,不带-x会把别的分区的容量也加进来,统计结果和df对不上,又白白多踩一个坑。sort -rh是按人类可读数字反向排序,最大的目录直接出现在最上面。

找到最大目录后继续往下一层钻:

du -x -h --max-depth=2 /data/logs 2>/dev/null | sort -rh | head -20

反复几次,基本能定位到具体是哪个子目录、哪个文件在疯狂增长。这个“逐层下钻”看起来简单,但比你直接du -sh *然后拿眼睛扫要快得多。

1.3 第三步:动手删除前,先查进程占用

空间告警时最忌讳的就是“找到一个大文件顺手就rm了”。在 Linux 上,rm只是把文件名从目录项里摘掉,如果这个文件正被某个进程打开,它的数据块并不会立刻释放,df显示的使用率可能毫无变化。更麻烦的是,你已经把名字删了,再想定位是谁占着它就得绕一大圈。

所以我的原则是:任何大文件在删除之前,先看一眼它是不是被进程占用:

lsof +L1 2>/dev/null | head -50

+L1会列出所有被进程打开但已经删除的文件,这是排查“已删除不释放”最直接的工具。下面详细说这个坑的原理和清理方式。

2. du 和 df 对不上的两个深坑

2.1 坑2:df 显示已满,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)配置好。不少项目默认配置了按天轮转,但因为copytruncatecreate的参数选错,导致轮转出来的文件没有被正确接管,最后又演变成“日志文件被进程打开后删不掉”。

2.3 坑3:隐藏的挂载点让统计对象根本不一致

另一个常见的dudf对不上,原因是挂载点掩盖。很多业务目录下面会单独挂一块盘,比如/data/logs是独立分区。如果你执行df /data,看到的是/data所在分区的情况;而日志不断增长的其实是/data/logs挂载的那块盘。两边根本不是同一个文件系统,自然对不上。

反过来,如果你执行du -sh /data时没有加-xdu会跨到/data/logs的独立分区里去统计,把另一块盘的占用也算进来,和df /data的结果严重偏离。

我的建议是:每次排查都先df -h把所有分区的使用率罗列出来,确认到底是哪块盘在告警。如果告警的是/data,但/data/logs是独立挂载盘,你得单独对/data/logsdfdu,这才是准确的数据。目录下有隐藏挂载点的情况,用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 万个小文件,每个文件只有几百字节,空间才用了不到 10GB,inode 却直接耗尽。

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-depth=2 /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 {} \;。原因是-deletefind的内置动作,不需要为每个文件启动外部进程,对几十万小文件来说效率天差地别。如果你删的还是太慢,可以先mv把目录改名,再后台慢慢删,先把写入能力恢复。

长期治理有几个方向:

  • 高频小临时文件尽量用 tmpfs 或单独分区隔离,避免影响到主业务文件系统。
  • 业务端能优化的优先优化:小文件合并写入、按时间归档、限制目录内文件总个数。
  • 格式化阶段就规划好 inode 密度。ext4 可以用mkfs.ext4 -i 8192 /dev/sdb1把 bytes-per-inode 调小,从而增加可用 inode 数;xfs 可以通过mkfs.xfs -i maxpct=10调整 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 -lhstat看逻辑大小,会看到 10G。当你在排查“磁盘没减少但文件看着很大”时,看到duls -lh对不上,要想到稀疏文件这回事,别拿它当磁盘占用大户反复研究,方向偏了。

判断方式:

stat 文件名

SizeBlocks两个字段。Size是逻辑大小,Blocks是实际占用的 512 字节块数。如果Size很大但Blocks很小,基本就是稀疏文件。这类文件常见于数据库预分配文件、某些加密盘镜像、部分下载工具临时文件。处理上一般不需要特殊操作,但要注意不要用du的默认输出去判断磁盘占用,必要时用du --apparent-size看逻辑大小。

4.2 坑6:rm -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 可以用debugfsextundelete,xfs 的原生恢复能力很弱,通常只能靠备份恢复。

这里我更想强调的是习惯:在生产环境执行删除前,我几乎不会直接敲rm,而是先把“删除清单”列出来,用findls先确认一遍再删。比如:

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-depth=1 -h /path | sort -rh定位目录级空间大户
5du --inodes --max-depth=2 /path | sort -rn定位 inode 大户
6ls -lh /proc/进程PID/fd配合第 3 步找到持有的文件句柄
7stat <怀疑文件>确认稀疏文件等异常

执行完第 1、2 步,基本就能把问题归成四类:空间满、inode 满、已删未释放、挂载点或稀疏文件导致的假象。后面步骤按类对号入座,不用盲目乱扫。

5.2 处理动作的优先级排序

处理优先级我一般这样排:

  1. 如果有业务进程还保持着已删除文件,优先释放这些句柄。这个动作能最快恢复业务写入能力,代价也最低。
  2. 如果是 inode 耗尽,先去清临时缓存目录。这类目录通常有业务自清理逻辑或过期时间,删旧文件风险最小。
  3. 如果是真正的空间满,先找日志文件。很多场景下日志轮转没有真正生效,才是告警的主因。
  4. 最后才动大文件手工迁移或删除。动手前一定要确认文件用途和备份状态。

还有一个需要提醒的误区:很多人看到磁盘告警,就顺手truncate -s 0截断大文件。这个操作本身没问题,但如果该文件正被进程持续写入,你截断之后,进程继续写会产生一个大小重新增长的文件,下次告警依旧出现。更合理的做法是让业务服务自己处理,比如配置好 logrotate 的copytruncatecreate,或者重启业务进程让它重新打开新文件。

5.3 长期运维习惯,把磁盘告警扼杀在萌芽

最后说几个我长期沉淀下来的预防习惯。

日志轮转要强制落地。logrotate 的dailysizerotate数量都要检查,尤其注意copytruncatecreate的区别,选择要匹配业务进程的写入方式。

监控不仅要盯df -h,更要把df -i的使用率加进告警。inode 告警通常比空间告警提前一周甚至一个月出现,提前处理比事后救火舒服太多。

临时目录要设置 cleanup 定时任务。我习惯每天凌晨跑一次find + mtime + -delete,把过期临时文件清掉,代价很低,收益很大。

大文件迁移和清理要写进操作文档。别等告警来了再临时翻命令,越慌越容易出错。养成习惯后,磁盘告警基本就是一条流水线:df -hdf -ilsof | grep deleteddu -x下钻,定位、处理、恢复,一气呵成。

从我自己的实战经验看,磁盘告警救火并不难,难的是每次都不分青红皂白就乱删一通。搞清楚du/df差异背后的文件系统原理,理解 inode 和空间的独立消耗逻辑,再加上一份清晰的排查流程,晚上十一点的告警群也会变得没有那么吓人。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/16 2:48:26

Java命名规则与修饰符最佳实践详解

1. Java命名规则与修饰符基础解析作为一名从业十年的Java开发者&#xff0c;我经常遇到新手在命名和修饰符使用上栽跟头。规范的命名和恰当的修饰符使用&#xff0c;不仅影响代码可读性&#xff0c;更关系到团队协作效率和系统可维护性。今天我们就来深入探讨这两个看似基础却至…

作者头像 李华
网站建设 2026/9/16 2:47:42

Python类型注解的运行时真相:__class_getitem__与泛型机制揭秘

老实说&#xff0c;第一次听到“Type Hint 只在写代码时有价值”这种说法&#xff0c;我是持保留意见的。做了这么多年 Python 开发&#xff0c;从 3.5 的typing模块一路用到现在&#xff0c;我见过太多次“类型提示只是给人看”的论断&#xff0c;但真到排查问题、优化启动速度…

作者头像 李华
网站建设 2026/9/16 2:47:12

种群竞争模型全解析:从微分方程到数值模拟与竞赛应用

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/16 2:46:48

Bandizip 8.0:Windows免费无广告解压终极方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/16 2:45:58

SpringBoot与大数据构建男装推荐系统实战

1. 项目概述&#xff1a;基于SpringBoot与大数据的男装推荐系统这个毕业设计项目构建了一个完整的男装类商品购物网站推荐系统&#xff0c;采用SpringBoot作为后端框架&#xff0c;结合大数据技术实现个性化推荐功能。系统包含完整的源码、部署文档和演示视频&#xff0c;是一套…

作者头像 李华
网站建设 2026/9/16 2:45:46

基于FPGA的BPSK变频器电路设计:从NCO到ADC/DAC的完整实现

简介&#xff1a;面向基于FPGA的BPSK变频器电路设计需求&#xff0c;该压缩包提供了一套从发射端数字上变频到接收端变频解调的完整工程参考。设计将1兆赫兹的BPSK基带信号变频到4兆赫兹中频载波&#xff0c;覆盖直接数字频率合成、正交混频、载波生成等关键模块&#xff0c;并…

作者头像 李华