磁盘满了先别急着删日志——用 df、du 与 lsof 找出真正占空间的文件
线上服务器最常见的一类“假故障”就是磁盘告警:监控显示根分区使用率超过 90%,人一登录服务器就条件反射地 rm -rf 日志目录,结果空间没回来不说,还可能删掉正在写入的关键数据。正确的做法是三步走:先看哪个分区满了,再找到底是哪个目录在变大,最后确认有没有“已删除但还被进程占着”的文件。下面逐一讲清楚。
第一步:df,看哪个分区满了
df 报告的是文件系统层面的空间使用情况,先确认告警到底落在哪个挂载点上:
$ df -h
Filesystem Size Used Avail Use% Mounted on
devtmpfs 7.8G 0 7.8G 0% /dev
tmpfs 7.8G 0 7.8G 0% /dev/shm
tmpfs 7.8G 1.1G 6.7G 15% /run
/dev/vda1 99G 93G 1.2G 99% /
tmpfs 7.8G 0 7.8G 0% /sys/fs/cgroup
几点说明:
- 只看
Use%高的那一行。上例中是根分区/到了 99%,/run只是内存盘(tmpfs),不用管。 - 排查时建议加上
-T看文件系统类型:df -hT,xfs 与 ext4 在空间回收行为上有差异。 - 顺手看一眼 inode:
df -i。空间明明还有,inode 却耗尽(比如某个目录下堆了几百万个小文件)时,同样写不进文件,表现和磁盘满一模一样,很容易误判。
第二步:du,从大目录往下钻
确认是 / 满了之后,用 du 沿着目录树往下钻。建议按层级来,不要一上来就全盘扫描:
$ du -sh /var/* 2>/dev/null | sort -rh | head
38G /var/lib
4.2G /var/log
2.1G /var/spool
$ du -sh /var/lib/* 2>/dev/null | sort -rh | head
35G /var/lib/mysql
2.6G /var/lib/docker
两分钟就定位到 /var/lib/mysql 占了 35G。几个经验:
2>/dev/null过滤掉权限不足的报错,免得满屏红字干扰判断;sort -rh按人类可读的大小倒序,head只看前几名;- 定位到目标目录后,用
ls -lhS按大小排序看具体是哪些文件:ls -lhS /var/lib/mysql | head。
为什么 du 和 df 的数字经常对不上?
这是很多人困惑的地方:du -sh / 显示只用了 60G,df 却说用了 93G。常见原因有三个:
- 已删除但仍被进程打开的文件:文件名删了,目录项没了,
du统计不到;但进程还拿着文件句柄,内核不会释放数据块,df照样计入已用空间。这是最常见的一种,第三步专门处理。 - 挂载点遮挡:比如先往
/data里写了 20G 文件,之后又把一块新盘挂载到/data上,那么这 20G 对du /data不可见(被挂载盖住了),df却依然计入原分区。排查时可以用du -x限制不跨文件系统统计。 - 稀疏文件与块统计口径差异:
du按实际占用的数据块统计,df按文件系统已分配统计,稀疏文件(sparse file)会让两者出现差异,一般差得不多,不用深究。
记住结论就行:df 算的是文件系统的账,du 算的是目录树的账。两本账对不上时,先怀疑“删了但没释放”和“挂载遮挡”。
第三步:lsof,揪出“删了但还占着”的文件
这是最容易被忽略的一步。很多人删完日志发现 df 的 Use% 纹丝不动,就以为“删错了地方”,其实文件是删掉了,只是空间没释放:
$ lsof +L1
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NLINK NODE NAME
java 18234 app 7w REG 253,0 32212254720 0 /var/log/app/app.log (deleted)
nginx 19012 root 12w REG 253,0 8589934592 0 /var/log/nginx/access.log (deleted)
lsof +L1 列出链接数小于 1(即文件名已被删除)但仍被进程打开的文件。上例中:
- 一个 30G 的
app.log,文件名已经删了,但 Java 进程(PID 18234)还拿着写句柄,30G 空间一分没释放; - 一个 8G 的
access.log,被 nginx 进程占着。
处理办法(按推荐顺序):
- 重启或重载服务:最干净。
systemctl restart app或nginx -s reload,进程退出后句柄关闭,空间立即释放。能接受短暂重启的优先选这个。 - 截断文件:不能重启的服务,用重定向把文件清空,而不是删除:
通过$ : > /proc/18234/fd/7/proc/<PID>/fd/<FD>把文件截断为 0,进程的写偏移会被重置,空间立即释放,服务不用停。注意这是清空,不是删除。 - 千万不要直接
rm正在写的文件:删了文件名空间不释放,还丢了排查线索;更糟的是有些程序按 inode 写入,删了重建同名文件后,进程还在往旧 inode 写,新文件永远是空的,日志会“凭空消失”。
小技巧:
lsof | grep deleted也能查,但+L1更精准;只查单个进程用lsof -p <PID>。
预防:别让磁盘“突然”满
救火不如防火,三个习惯:
- 日志轮转:给应用日志配
logrotate,按天或按大小切割、压缩、保留若干份。/etc/logrotate.d/app示例:/var/log/app/*.log { daily rotate 7 compress missingok copytruncate }copytruncate对无法重载日志的程序特别有用——先复制再截断,不用重启服务。 - 监控阈值分级:磁盘使用率告警不要只设 90% 一档,建议 75% 预警、85% 告警、95% 紧急,分级处理才有缓冲时间。同时把 inode 使用率也纳入监控。
- 定期巡检:每周跑一次
du -sh看 Top 目录的增长趋势,增长异常的目录提前扩容或迁移;也可以用ncdu交互式查看,更直观。
总结
| 步骤 | 命令 | 解决的问题 |
|---|---|---|
| 1 | df -h / df -i |
哪个分区满了,顺带看 inode |
| 2 | du -sh + sort -rh |
哪个目录最大,逐层下钻 |
| 3 | lsof +L1 |
已删除但被占用的文件,重启或截断释放 |
下次磁盘告警响起时,先别急着删日志:df 定位分区,du 找到大目录,lsof 揪出“删了但没放”的文件。三步走完,空间回来了,数据也没丢,这才是真正的“清理”。
- 点赞
- 收藏
- 关注作者
评论(0)