磁盘报警100%,rm删了文件空间还没回来——inode、deleted文件、lsof的坑
本文目录
先说结论
磁盘 100% 的四种真实原因:① 确实有大文件(最简单);② inode 用完了(小文件太多);③ 文件被 rm 了但进程没关,空间不释放(最坑);④ 容器 overlayfs 层累积(K8s 环境)。四种的排查命令完全不同。
df 和 du 会对不上。df 看的是文件系统元数据(super block),du 看的是实际文件大小。如果有 deleted 但 open 的文件,df 大 du 小,差值就是未释放的空间。
处理被 deleted 但占空间的文件,不要直接重启进程,优雅的方式是用 `> /proc/
先把这段讲顺;面试官追问时,再展开下面的原理和细节。
详细讲解
这道题是典型的 '线上经验分水岭'。没踩过坑的人会说 'df 看一下、du 找大文件、rm 删掉'。踩过坑的人会告诉你:磁盘 100% 有四种真实原因,rm 完空间不回来是最坑的一种,因为 Linux 不释放被进程打开的文件——这是 Unix 的'特性',不是 bug。面试能讲清楚 `lsof | grep deleted` 这招的,面试官基本就认定你在线上救过火了。
业务场景:Databricks worker 节点磁盘 100%,rm 完没用
某出海硬件品牌跑 Databricks 的时候,有一次 worker 节点磁盘告警 98%。`du -sh /data/*` 找到是 /data/shuffle/ 目录下有 200G 文件,看起来是 Spark 临时 shuffle 文件没清理。`rm -rf /data/shuffle/spark-*` 之后,`du -sh /data` 降到 50G,但 `df -h` 还是显示 98%——空间完全没释放。这就是典型的 deleted 文件被进程持有。
# 典型症状:df 和 du 差距很大
df -h /data
# /dev/nvme0n1 500G 490G 10G 98% /data
du -sh /data
# 50G /data <-- 差了 440G,全是 deleted 文件
# 定位谁持有 deleted 文件
lsof +L1 /data | head -20
# COMMAND PID USER FD SIZE/OFF NLINK NODE NAME
# java 12345 spark 87 10737 0 12 /data/shuffle/spark-xxx/block_0.data (deleted)
# java 12345 spark 88 21474 0 13 /data/shuffle/spark-xxx/block_1.data (deleted)
# 统计某个进程持有多少 deleted 字节
lsof -p 12345 | awk '/deleted/ {sum += $7} END {print sum/1024/1024/1024 " GB"}'
根因:Spark executor 作业跑完后死在那里没退出(heartbeat 失败但进程没被清理),它持有的 shuffle 文件虽然在文件系统上被 rm 了,但 inode 因为还有 fd 引用所以没释放。临时解决是 kill 掉这些 executor,长期解决是改 YARN/Spark 的 cleanup 配置。
手段一:df / du / du -h --max-depth 的正确姿势
df 和 du 各有分工:df 看文件系统,du 看文件。排查磁盘必须两个一起用。
# df -h:看所有文件系统使用率
df -h
# df -i:看 inode 使用率(容易被忽略的一个)
df -ih
# Filesystem Inodes IUsed IFree IUse%
# /dev/nvme0n1 30M 29M 1M 98% <-- inode 快满了
# du -sh:某个目录总大小
du -sh /data
# du 按目录分层排序(最常用的排查命令)
du -h --max-depth=1 /data | sort -h
# 只看前 10 大目录(递归)
du -ah /data 2>/dev/null | sort -hr | head
# 注意 du 排除其他挂载点(避免把别的分区也统计进来)
du -sh --one-file-system /
# 还可以用 ncdu(ncurses 版,交互式)
ncdu /data
经验:排查顺序固定是:`df -h` → `df -ih` → `du --max-depth=1` 逐层下钻 → `lsof +L1` 查 deleted。一套下来磁盘问题基本都能定位。
手段二:df -i 查 inode 满
inode 满也是一种 '磁盘满',但 df -h 看不出来,只有 df -i 能看。典型场景是目录里有几百万个小文件,文件总大小才几 G,但 inode 占了几千万。
# 看 inode 使用率
df -ih
# 找 inode 大户:哪些目录文件数最多
for d in /var/* /data/*; do
echo -n "$d: "
find $d 2>/dev/null | wc -l
done | sort -t: -k2 -rn | head
# 更精确:统计每个目录的文件数(一级目录)
find /data -maxdepth 2 -type d -exec sh -c 'echo "$(find "$1" -maxdepth 1 | wc -l) $1"' _ {} \; | sort -rn | head
# 清理:批量删 ls 和 rm 都会卡死(参数太多)
# 错误:rm /data/tmp/* → Argument list too long
# 正确:
find /data/tmp -type f -mtime +7 -delete
# 或者
find /data/tmp -type f -mtime +7 -print0 | xargs -0 rm
踩过的坑:生产机 inode 满之后,连 `mkdir` 都失败,`touch` 也失败,但 df -h 显示磁盘只用了 30%。一定要看 `df -ih` 才知道。解决除了删文件,还可以扩展 inode(某些文件系统支持),或者直接 reformat。
手段三:lsof 查 deleted 文件(最坑的情况)
`lsof +L1
# 查整个文件系统的 deleted 文件
lsof +L1 /data
# 查某个进程的所有 deleted 文件
lsof -p 12345 | grep deleted
# 经典写法(兼容性最好)
lsof | grep deleted | awk '{print $2, $7, $NF}' | sort -k2 -rn | head
# 释放空间的三种办法:
# 方法 1(最暴力):kill 进程,不推荐
kill -9 12345
# 方法 2(优雅):用 /proc/pid/fd 清空文件内容(不用重启)
# 先找到 fd
ls -l /proc/12345/fd | grep deleted
# lrwx... 87 -> /data/bigfile.log (deleted)
# 然后清空
> /proc/12345/fd/87
# 方法 3:优雅重启进程(生产推荐)
systemctl restart xxx
重点:方法 2 的 `> /proc/pid/fd/87` 是杀手锏。原理是 Linux 一切皆文件,fd 也是文件,直接重定向空字符串进去会截断它指向的底层文件。比 kill 进程优雅 10 倍,生产上处理日志文件被进程 open 特别好用。
手段四:find 找大文件和老文件
排查'哪个目录变大了'之后,进一步要找具体是哪个文件。find 是首选工具。
# 找大于 1G 的文件
find /data -type f -size +1G -exec ls -lh {} \;
# 找最近 1 天修改的大文件(排查突然变大的元凶)
find /data -type f -size +100M -mtime -1 -exec ls -lh {} \;
# 找超过 30 天没访问的大文件(清理候选)
find /data -type f -atime +30 -size +100M
# 只在本文件系统内找(防止跨挂载点)
find /data -xdev -type f -size +1G
# 统计每个扩展名的总大小
find /data -type f -name '*.log' -exec du -ch {} + | tail -1
# 删除前先确认:-print 和 -delete 的区别
find /tmp -name '*.tmp' -mtime +1 -print # 先看
find /tmp -name '*.tmp' -mtime +1 -delete # 再删
四种'磁盘满'对比
| 症状 | df -h | df -i | 排查命令 | 处理方式 |
|---|---|---|---|---|
| 确实有大文件 | 100% | 正常 | du -h --max-depth=1 | 删或迁移 |
| inode 满 | 正常 | 100% | find 数小文件 | 清理小文件 |
| deleted 未释放 | 100% | 正常 | lsof +L1 | 重启或 >/proc/fd |
| 容器层累积 | 100% | 正常 | du /var/lib/docker | docker prune |
面试高频追问
追问一:一个 10G 的日志文件正在被 nginx 写,你 rm 了会怎么样?
三件事:① 文件名从目录树消失,`ls` 看不见了;② 磁盘空间不会释放,因为 nginx 还持有 fd;③ nginx 继续往那个 fd 写,但写进去的数据你看不见了(因为没有文件名能定位)。等 nginx 重启或 reopen log 的时候,fd 关闭,inode 引用归零,空间才真的释放。正确的 log 切割姿势是:① 用 `logrotate` 的 `copytruncate`,或者 ② 用 `mv + kill -USR1 nginx`(让 nginx 重新 open log 文件)。直接 rm 是事故的开始。
追问二:log4j 配置了 rotate 策略,但磁盘还是被日志占满了,怎么排查?
典型坑,四个可能:① log4j 只保留 N 个日志但没配 `fileNamePattern` 的 totalSizeCap,旧日志一直在;② rotate 配的是时间,但日志涌太快导致当前文件就已经很大了;③ 应用 bug,日志级别写成了 DEBUG,量暴涨;④ 最坑的一种——log4j 滚动后,老的日志文件被 rm 了但 fd 没关,`lsof +L1` 会看到一堆 deleted。第 ④ 种我在某短视频平台踩过,原因是 log4j 自定义 appender 有 bug,滚动时没关旧 fd。
追问三:Docker 环境下磁盘满了,容器里看又正常,怎么办?
Docker 的磁盘占用分三部分:镜像(/var/lib/docker/image)、容器层(/var/lib/docker/overlay2)、卷(/var/lib/docker/volumes)。容器里的 df 看的是容器的 rootfs 和挂载的卷,看不到宿主机的镜像和其他容器占用。排查:① 宿主机 `du -sh /var/lib/docker/*` 看哪块大;② `docker system df -v` 看详细占用;③ `docker system prune -a --volumes` 清理(注意会删没在用的镜像和卷,谨慎);④ 如果是容器日志占用太多,设 `--log-opt max-size=100m --log-opt max-file=3`。
实战踩坑:Spark shuffle 文件 200G 全是 deleted
某出海硬件品牌的 Databricks worker,有一次告警磁盘 98%。我 ssh 上去看:
# 1. df -h 先看
df -h /data
# /dev/nvme0n1 500G 488G 12G 98% /data
# 2. du 看实际占用
du -sh /data
# 60G /data <-- df 和 du 差 428G,标准 deleted 文件症状
# 3. lsof +L1 定位
lsof +L1 /data | awk '{print $1, $2}' | sort | uniq -c | sort -rn | head
# 1245 java 23451 <-- 12345 号 executor 持有 1245 个 deleted 文件
# 986 java 23892
# 4. 这俩 java 进程是啥
ps -p 23451 -o cmd=
# /jdk/bin/java ... org.apache.spark.executor.CoarseGrainedExecutorBackend
# 5. 看 executor 状态:已经 disconnected 了但没死
jstack 23451 | grep State | sort | uniq -c
# 它是僵死状态,不会自动退出
# 紧急处理:kill 这些僵死 executor
kill 23451 23892
# 几秒后空间释放
df -h /data
# /dev/nvme0n1 500G 60G 440G 12% /data
根因和长期修复:spark.cleaner.referenceTracking.cleanCheckpoints 没开,再加上 executor 异常退出时 shuffle cleanup 没跑完,导致 deleted 文件堆积。修复:① 开 referenceTracking;② 设置 `spark.cleaner.periodicGC.interval=5min` 定期清;③ 加监控,`lsof +L1` 超过一定阈值就告警。上线之后再没出现过。
总结
磁盘满这道题考的是你有没有真的处理过线上告警。四种原因(大文件/inode/deleted/容器层)对应四套排查命令,能讲清楚 `lsof +L1` 和 `> /proc/pid/fd/N` 这招的,基本就是资深线上人员。面试时主动提 df 和 du 会对不上的场景,面试官会直接在心里给你加分。
下一篇聊调度系统经典故障:DataWorks 任务半夜挂了,日志只有一句 'No space left on device',怎么系统性排查?
你们线上最坑的一次磁盘故障是什么?评论区聊聊~
关注「Wood聊大数据」| 9年大数据老兵,聊技术、聊职场、聊成长