拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

磁盘空间排查实战:du命令参数选择与定位技巧

磁盘空间排查实战:du命令参数选择与定位技巧

干运维和用服务器的人基本都遇到过这个经典场景:某天监控突然报警,说磁盘使用率超过90%,或者业务进程开始报"No space left on device",你连上服务器先敲一句df -h,发现根分区已经100%了。然后呢?到底是哪个目录、哪个文件把空间吃光的?这时候du就是手边最顺手的排查工具。很多人只会du -sh,但真遇到几十TB的数据盘、上百万小文件的目录,命令参数选不对,光是等待扫描就能让你怀疑人生。

这篇文章把磁盘空间管理里du命令的常用场景、参数选择、排查实战和踩坑经历都梳理一遍。内容既适合刚入门的Linux用户照着操作,也能给已经写过不少命令的老伙计们查漏补缺。我不会罗列一堆命令大全,而是用解决实际问题的思路,把du这个工具讲透。

1. 磁盘空间管理的基本功:先搞清楚du到底在算什么

1.1 从块到目录:du和ls看到的东西不一样

很多新手会疑惑:为什么我用ls -l看某个文件显示1G,用du看同一个文件却显示1.1G?这俩命令数出来的东西根本不是同一个维度。

ls -l展示的是文件的逻辑大小,也就是文件内容本身的字节长度。而du是disk usage的缩写,它统计的是文件系统为存放这个文件实际分配的磁盘块数。Linux文件系统以块为单位分配空间,一个块通常是4K(ext4、xfs默认是4K块)。一个只有1字节的文件,逻辑大小是1字节,但它至少要占一个块,也就是4K。所以大量小文件占用的空间,会比它们内容总和要大得多。

更离谱的是稀疏文件。比如你创建一个文件,往里写了1G的空字节,但故意在文件系统层面没有分配块,ls -lh会显示1G,du -sh却可能显示0或者很小。如果你只凭ls的大小去做清理计划,很容易误判。生产环境里数据库文件、虚拟机磁盘镜像、日志文件都可能出现这种情况。

目录本身在Linux里也是一个文件,里面记录着文件名与inode的对应关系。du统计一个目录时,会把这个目录文件自身占用的块也一并算进去。所以du -sh /var的结果,实际上是/var目录树里所有文件、子目录、隐藏文件、特殊文件(比如socket、fifo)实际占用磁盘块的总和。

1.2 du的统计范围:跨文件系统与挂载点

du默认会跟随路径往下走,如果某个路径下挂载了其他文件系统,它也会一并统计。举个例子:你的根分区/已经用了80%,你执行du -sh /想看根目录下是什么占空间。结果发现/mnt/data是一个独立的2T数据盘挂载点,du会把那2T数据也全部算到/的统计结果里。这时候你得到的是一个混合数字,根本无法定位根分区自己的问题。

解决办法是-x参数,也叫--one-file-system,意思是只在同一个文件系统内进行统计,跨文件系统的挂载点直接跳过。

排查根分区磁盘满时,我的习惯是固定使用:

du -h -x --max-depth=1 /

这样可以只看根分区自身内容,把/proc、/sys、/dev、/tmp下的tmpfs以及其他挂载点全部排除掉,统计结果才真正反映根分区的压力。

1.3 软链接、硬链接与重复计算

软链接(symlink)默认不会被du跟随,它只统计软链接文件自身的块大小。只有当你显式加-L参数时,du才会顺着软链接去统计目标文件或目录的大小。其实日常排查中我基本不用-L,因为软链接指向的内容通常在其真实路径下,你直接统计那个真实路径就够了,加了-L反而可能把同一个目录重复统计,得出离谱的结果。

硬链接是另一个坑。硬链接和原文件指向同一个inode,也就是同一份磁盘数据,只是多了个文件名。du在扫描一个目录树时,内部会有个去重机制,同一个inode遇到第二次的时候会跳过,不会重复计算。但是如果你把两个目录分开执行du -sh,再拿结果做减法,就可能把同一个硬链接文件在两个目录里各算一次,最终导致判断失真。

最稳妥的做法是:不要在两个目录上分别du然后相减,而是从它们的共同父目录开始,一次du -sh整体看完。

2. du命令的实操套路与参数选择

2.1 最常用的组合:du -sh和du -h --max-depth=1

先说最核心的三板斧。

第一板斧是看整体大小:

du -sh /data

-s表示汇总(summary),只输出总计一行;-h是human-readable,自动转换成K、M、G等单位。这个命令适合快速了解一个目录的总空间占用。

第二板斧是找大头目录:

du -h --max-depth=1 /var

--max-depth=1表示只显示指定目录下第一层子目录的大小,不会无限向下递归展示。输出结果大概是:

4.0K /var/spool 120M /var/log 8.0K /var/mail 2.3G /var/lib ...

这样你一眼就能看出/var/lib是元凶,然后进去继续用同样的命令下探:

du -h --max-depth=1 /var/lib

这种逐层下探的模式,是定位大目录最快的方式。--max-depth=2可以一次性看到孙层目录,但输出行数会爆炸,一般排查环境不推荐。

第三板斧是列表式对比:

du -sm /var/* | sort -rn | head -20

-m以MB为单位输出整数,再用sort -rn按数字倒序,直接把最大的20个条目顶到前面。这在看大量目录时效率极高。

2.2 排除不需要的目录:--exclude

有些时候你知道某些目录不用管,但du还是会老老实实地扫进去。比如统计网站根目录/var/www时,你想排除里面的备份目录/var/www/backup,可以用:

du -sh --exclude=/var/www/backup /var/www

--exclude支持通配符,排除所有日志文件也可以这么写:

du -sh --exclude='*.log' /var/log

注意--exclude和-x是两回事。-x是跨文件系统跳过,--exclude是路径模式匹配跳过。排查根分区时我经常两个一起用:

du -h -x --max-depth=1 --exclude=/proc --exclude=/sys /

虽然-x本来就会跳过这些挂载点,但明确写出来更清楚,也防止有些伪文件系统没挂载但路径存在的情况。

2.3 统计inode占用:du --inodes

磁盘满不只有空间满,还有inode满这一种形态。inode是文件系统里记录文件元数据的索引节点,每一个文件或目录都会消耗一个inode。当你的分区里充满了大量几字节的小文件,可能空间还剩很多,但inode已经用完了,同样写不进新文件。

遇到这种情况,df -i会显示inode使用率100%。怎么定位是哪个目录生成了海量文件?用du加--inodes参数:

du --inodes -h --max-depth=1 /data

它会统计每个目录下所有条目(文件+子目录)的inode占用数量。输出大体是:

123456 /data/tmp 2456 /data/cache ...

看到/data/tmp有十几万条目,基本就锁定了问题来源,通常是临时文件或未清理的缓存。

2.4 单位与精度控制:-b、-m、-k

du的输出单位默认是1024字节的块数,但不同环境下可能受环境变量影响,不够直观。所以实际使用中我很少不加-h,偶尔需要精确字节数的时候会用-b:

du -sb /data

-b等价于--block-size=1,显示精确到字节的总数。如果你想用固定单位,已经知道目标大概是几百M,可以用-m强制以MB输出,省去sort -n时因为K、M、G混合排序出错的问题。

du -m --max-depth=1 /data | sort -rn | head

如果目录大到GB级别,用-g也很方便。这些参数本质上都是设置--block-size,你完全可以写--block-size=1M来强制以M显示,效果一样。

2.5 用du的时间维度做变化趋势:--time

磁盘是某个具体时间点开始满的,如果能知道哪些目录在那段时间有新文件写入,排查效率会高很多。du有个容易忽略的参数--time:

du --time -h --max-depth=1 /var

它会在每条结果后面显示该目录内所有文件中最新的mtime(文件内容修改时间)。展示效果类似:

2.3G 2025-04-07 14:22 /var/lib 120M 2025-04-07 16:30 /var/log ...

通过时间戳,你能快速锁定昨天半夜突然写入大量数据的目录。不过注意,这里记录的是目录内文件的最后修改时间,不是目录本身的最后修改时间。这个参数不常被提到,但在事后追溯异常写入来源时非常有用。

3. 实战:磁盘空间排查完整流程

3.1 先df确定挂载点水位

处理磁盘满问题,第一步永远是df,不应该是du。因为df直接读取文件系统的超级块和元数据,能瞬间告诉你每个挂载点的使用率。du需要遍历目录树,是个慢操作,在还不确定问题盘是哪块的时候,不要贸然全盘扫描。

我常用的检查命令:

df -hT

-T会显示文件系统类型,比如ext4、xfs、tmpfs、overlay。容器环境里更要注意区分,docker overlay2的镜像层也会占空间,它通常在/var/lib/docker下面,通过df -h能看到overlay挂载点。

再补一句:

df -i

看inode使用率。如果/dev/sda1的IUse%接近100%,那就别走空间排查路线了,直接按3.2里--inodes的思路去找目录。很多人只知道df看空间,忽略了df -i,但inode耗尽和空间耗尽的表象完全一样,都是"No space left on device",排查方向完全不同。

3.2 用du逐层定位大目录

确定目标分区后,从挂载点开始第一层扫描。假设df -h显示/分区使用率95%,执行:

du -h -x --max-depth=1 / 2>/dev/null

这里加2>/dev/null是为了屏蔽权限不足的报错,否则输出会被大量"Permission denied"刷屏。扫描结果大概长这样:

0 /proc 0 /sys 1.2M /etc 45G /var 12G /usr 3G /home ...

明显/var是最大的,继续下探:

du -h -x --max-depth=1 /var 2>/dev/null

发现/var/log/journal占到38G。接着再往下:

du -h -x --max-depth=1 /var/log/journal

或者更直接:

ls -lhS /var/log/journal

ls -lhS是按文件大小倒序列出文件,对已经明确到比较浅的层级时,比du还快。整个定位过程就是"df确认盘,du找目录,ls找文件"三步。

3.3 大文件的精确定位:du与find配合

du擅长统计目录聚合大小,但你最终要抓出具体的超大文件,还是要靠find。我常用的命令是这样:

find /data -xdev -type f -size +100M -printf '%s %p\n' | sort -nr | head -20

解释一下各个部分:

  • -xdev:限定在当前文件系统,和du的-x同理,避免扫到挂载盘。
  • -type f:只看普通文件,不包含目录。
  • -size +100M:筛选大于100M的文件,数值可以按需调整。
  • -printf '%s %p\n':按"字节数 路径"格式输出,每个一行。
  • sort -nr:按字节数从大到小排序。
  • head -20:只取前20条。

输出样例:

31562340864 /data/app.log 128849018880 /data/db.sql ...

记得-xdev一定带上,不然find会跨挂载点,你在根分区找文件却扫到数据盘上,白白浪费时间。

还有一种方式是du -a:

du -a --max-depth=1 /data 2>/dev/null | sort -rn | head

-a会连同文件一起输出,不再只是目录。但du -a的排序结果会混入大量目录条目,而且默认单位可能是块,不够直观。我更推荐find加-printf的组合,性能上两者差不多,但find的格式可控性更强。

3.4 清理手段和删除技巧

找到大文件后,不要急着rm -rf。先确认这个文件是不是正被进程占用。特别是日志文件,服务进程打开着文件句柄,你rm掉文件名,空间不会立刻释放,因为进程还持有那个被删除文件的inode。磁盘使用率依然100%,你以为删了,实际全白干。

排查被占用但已删除的文件:

lsof +L1

+L1会列出所有link count为0但还被进程打开的文件。看到输出后,比如:

java 12345 user 2w REG 253,1 1568000 1048576 /tmp/.tmp (deleted)

确认这个文件是某个java进程留下的,处理方式通常是重启这个进程,或者确认后直接kill,空间才会真正释放。

如果是仍在使用的日志文件,正确做法是清空而不是删除:

truncate -s 0 /var/log/app.log

truncate -s 0把文件大小截断为0,文件句柄还在,进程能继续往里写,空间立刻回收。比rm再重启进程安全得多。

对于目录中成千上万的小文件,不要直接rm -rf整个目录。目录项过多时rm -rf会先遍历挨个删除,很慢,而且万一中途出错容易留下半个目录。可以先删除内部文件,再删目录。也可以用:

find /data/tmp -type f -delete

find -delete的删除速度和资源占用都比rm -rf好一点,但这属于优化项,不是强制的。

3.5 一分钟定位占空间最大目录的懒人脚本

每次手动敲那一长串命令有点烦,我把常用的逻辑写成了一个脚本,直接放进sit或~/.bashrc里:

function topdir() { local target="${1:-.}" du -h -x --max-depth=1 "$target" 2>/dev/null | sort -hr | head -20 }

用法很简单:

topdir /var topdir .

sort -hr是按人类可读的数值排序,GNU sort支持-h参数识别K、M、G后缀。输出直接就是带单位的倒序列表。再配一个按文件找大文件的函数:

function bigfile() { local target="${1:-.}" local size="${2:-+100M}" find "$target" -xdev -type f -size "$size" -printf '%10s %p\n' | sort -rn | head -30 }

调用示例:

bigfile /data +200M

这样排查磁盘问题基本就是两条命令的事了。

4. 常见问题与排查技巧实录

4.1 du为什么扫半天不出来

这是被问得最多的一个问题。du的本质是递归读取目录项,路径下文件数量越多,扫描时间越长。如果你有一个目录存了500万个小文件,du -sh /data可能要跑十几分钟。

优化的思路有这几条:

一是限制深度,前面已经提过。二是用-x把不需要关心的挂载点排除,尤其NFS挂载盘,扫描远程网络盘的速度完全取决于网络,会慢到让你崩溃。三是用ionice降低IO优先级,避免临时扫盘影响线上业务的磁盘性能:

ionice -c3 du -sh /data

-c3代表idle级别,只有在磁盘没有其他IO请求时才跑,对业务影响最小。四是把结果缓存起来,而不是反复扫描。比如你需要在一天内多次排查同一个大目录,第一次扫描完把结果存文件:

du -h --max-depth=1 /data > /tmp/du_result.txt 2>/dev/null

后面直接查看文件或用grep筛选,省掉80%时间。

4.2 权限不足导致统计不准确

普通用户执行du -sh /var/cache时,如果没权限读取里面的子目录,du会输出一条Permission denied,然后继续统计它能读到的部分。最终结果偏小是必然的。

遇到这种场景,正确姿势是用sudo:

sudo du -h -x --max-depth=1 /

如果你是排查线上机器,又没有sudo权限,那只能接受这个偏小结果,同时知道哪些路径没被统计到。2>/dev/null会把所有报错信息吞掉,方便看结果,但不要因此忽略权限缺口。

4.3 du显示大小与ls -l不一致,是正常的吗

正常,而且太正常了。前面讲过,ls -l是逻辑大小,du是物理占用块数。两者的区别在以下场景特别明显:

  • 大量小文件:du显示远大于ls各文件大小之和。
  • 稀疏文件:du显示远小于ls的大小,甚至接近0。
  • 日志文件被频繁写入后又被截断:ls和du都可能显示一个非典型的当前状态。

做空间清理时,应该以du的统计为准,因为磁盘能不能放下数据,看的是物理块是否够用,不是逻辑字节是否够。

4.4 du统计出的大小和df不一致

du /data汇总整个目录树得到的总和,df /data看的是整个文件系统已用空间。两者天然会存在差异,常见原因:

  • 文件系统元数据。ext4和xfs的inode表、日志、位图等都要占用空间,这部分属于文件系统的"开销",du统计不到。
  • 保留块。ext4文件系统默认保留5%空间给root用户,df使用率会因为这5%显得偏高。
  • 已删除但未释放的文件。这就是4.2提到的经典问题:文件被删了,但进程还占着句柄,df认为空间被占用,du统计目录时却找不到这个文件。

如果df显示100%,du统计根目录只有50%,优先怀疑是不是有大量deleted文件占用。检查方法:

lsof +L1 | grep deleted

找到后按情况重启进程或kill进程。一般情况下,处理完deleted文件,df的数字就会回落。

4.5 硬链接导致的误判

曾经有个同事统计项目目录时发现明明没有多少数据,du -sh /project_a和du -sh /project_b加起来却比磁盘总容量还大。最后查出来是两个目录硬链接了同一个大文件,单独看每个目录都完整带上了这个文件的容量,从父目录统一看时才会被去重。

这个案例给我们的经验是:判断多个目录的容量对比时,尽量从它们的共同父目录一次统计,不要先分别统计再相加减。如果非得分开统计,遇到硬链接场景要注意结果可能偏大,进一步核实的时候用ls -l看一下文件的link count。

4.6 目录明明没看到大文件,du却显示很大

这种情况常见于三类原因。

第一,隐藏文件。du默认统计所有隐藏文件,但你在ls时忘了加-a,自然觉得目录空空,实际.cache、.npm、.local里的内容可能已经吃了几G。排查时用ls -la看隐藏项。

第二,子目录挂载掩盖了真实内容。如果某个目录曾经有数据,后来又挂载了新磁盘在这个目录上,新磁盘会掩盖掉旧内容。du不加-x时会把挂载盘内容整个算进来,你看着像目录大,其实是底下挂了个大文件系统。反过来,你把挂载点umount掉,原本被掩盖的旧文件就又会冒出来,占用空间一样被统计到。这种情况风险管理上叫"挂载点下面有藏宝",清理前先确认挂载关系再动手。

第三,被删除但仍被进程持有的文件,这在4.4里已经细说。

4.7 从du到完整的磁盘空间管理习惯

du命令本身只是排查工具,真正根治磁盘问题是管理习惯。给你几个我实践下来有效的办法。

定时输出du快照。写一个cron任务,每天凌晨把关键目录的大小保存到带日期的文件:

0 2 * * * du -h --max-depth=1 /data > /var/log/diskusage/$(date +\%F).txt 2>&1

磁盘快满时,直接翻历史文件看哪个目录在某天突然增长,比事后补救高效太多。

安装ncdu作为交互式补充。ncdu本质上是一个增强的du,它先从根目录扫描,然后给你一个终端交互界面,可以用方向键上下浏览、下钻子目录、甚至可以按大小排序,还能直接删除选中的目录。如果服务器允许装,apt install ncdu或yum install ncdu都很简单。我在内网机器上常用它做深度排查,比纯命令舒服很多。

配合日志轮转和容器日志限制。很多运维事故不是业务bug,而是日志没人管。logrotate配置好按大小或日期切割,docker容器启动时加--log-opt max-size=100m --log-opt max-file=3,就能把绝大多数日志暴涨问题保质保量地压制住。

最后再分享一个我个人的小体会:清理磁盘空间时,永远不要把"df恢复"当作唯一目标。要顺手记录一下到底是谁、什么时候、往哪里写了数据,从根源上把问题堵住。du给你的只是结果,真正值钱的是你通过它定位到的那条"异常写入路径"。多花两分钟把这条路径扒出来打个补丁,下次爆炸时间就能拖得更远。

返回列表