你是否遇到过这样的情况:服务器磁盘空间莫名其妙被占满,用 df -h 查看确实空间不足,但用 du -sh 逐层查找却找不到大文件?这种"幽灵"般的磁盘空间消失,往往是文件句柄泄漏在作祟。
最近处理的一个生产环境案例:一台Java应用服务器,磁盘使用率在凌晨突然从40%飙升到95%,常规的文件查找工具完全找不到罪魁祸首。最终用 lsof 命令发现,是一个日志组件异常,导致数千个日志文件虽然已被删除,但进程仍持有这些文件的句柄,系统因此无法真正释放磁盘空间。
lsof(list open files)这个看似简单的Linux命令,在实际运维中价值被严重低估。本文将从真实的磁盘空间泄露排查场景出发,详细讲解如何用 lsof 精准定位问题,并提供完整的排查流程和最佳实践。
1. 为什么常规方法查不到"幽灵"文件?
1.1 文件删除的真相:引用计数机制
在Linux系统中,文件删除并不是立即释放磁盘空间。每个文件都有一个引用计数,只有当没有任何进程打开该文件时,空间才会真正释放。
BASH
2
echo "test content" > /tmp/test_file.log
5
tail -f /tmp/test_file.log &
1.2 df与du的结果差异说明了什么
BASH
5
du -sh /path/to/directory
当 df 显示磁盘空间不足,而 du 显示目录大小正常时,基本可以确定存在文件句柄泄漏问题。
2. lsof命令基础:不只是查看打开文件
2.1 安装与基本语法
BASH
5
apt-get install lsof -y
2.2 关键选项解析
BASH
11
lsof +D /path/to/directory
3. 实战:用lsof排查磁盘空间泄露
3.1 第一步:确认是否存在句柄泄漏
BASH
2
lsof | grep deleted | head -10
关键字段说明:
- 第一列:进程名
- 第二列:进程ID
- 第四列:文件描述符(1w表示标准输出,3w表示第三个文件描述符)
- 最后一列:文件路径和(deleted)标记
3.2 第二步:定位问题进程和文件大小
BASH
2
lsof | grep deleted | awk '{print $7}' | awk '{sum+=$1} END {print sum/1024/1024 " MB"}'
5
lsof | grep deleted | awk '{print $2,$7}' | sort | awk '{arr[$1]+=$2} END {for (i in arr) print i, arr[i]/1024/1024 " MB"}'
3.3 第三步:深入分析具体进程
BASH
8
cat /proc/1234/limits | grep "open files"
4. 完整排查案例:Java应用日志文件泄漏
4.1 问题现象描述
监控系统报警:/data 分区使用率超过90%
- 服务器:CentOS 7,Java应用
- 问题时间:凌晨业务高峰期
- 常规排查:
du -sh /data/* 显示总大小仅40G,但分区容量80G已快满
4.2 排查过程记录
BASH
13
lsof | grep deleted | grep /data | awk '{print $2,$7,$9}' | sort -k2 -nr | head -10
4.3 发现问题根源
排查结果发现一个Java进程持有了2000多个已删除的日志文件句柄:
BASH
2
java 12345 appuser 123w REG 8,1 104857600 678901 /data/logs/app-2024-01-15.log (deleted)
3
java 12345 appuser 124w REG 8,1 104857600 678902 /data/logs/app-2024-01-15.1.log (deleted)
4.4 解决方案
BASH
2
sudo systemctl restart application-service
6
ls -la /proc/12345/fd | grep deleted
9
for fd in $(ls /proc/12345/fd | grep -E '^[0-9]+$'); do
10
if readlink /proc/12345/fd/$fd | grep -q deleted; then
12
gdb -p 12345 -batch -ex "call close($fd)"
5. 常见文件句柄泄漏场景与预防
5.1 典型泄漏场景分析
| 场景类型 |
典型表现 |
预防措施 |
| 日志轮转问题 |
logrotate后进程不重载配置 |
使用copytruncate或发送信号重载 |
| 临时文件未关闭 |
程序异常退出未清理临时文件 |
使用try-finally确保文件关闭 |
| 网络连接泄漏 |
Socket文件描述符累积 |
设置连接超时和最大限制 |
| 文件流未释放 |
编程语言中的文件对象未关闭 |
使用with语句或显式close |
5.2 Java应用预防配置
JAVA
2
try (FileInputStream fis = new FileInputStream("file.log");
3
BufferedReader reader = new BufferedReader(new InputStreamReader(fis))) {
5
} catch (IOException e) {
11
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
12
<file>/data/logs/app.log</file>
13
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
14
<fileNamePattern>/data/logs/app.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
15
<maxHistory>7</maxHistory>
16
<totalSizeCap>10GB</totalSizeCap>
5.3 系统级预防设置
BASH
5
echo "* soft nofile 65535" >> /etc/security/limits.conf
6
echo "* hard nofile 65535" >> /etc/security/limits.conf
6. 高级技巧:自动化监控脚本
6.1 监控已删除文件大小的脚本
BASH
9
deleted_size_gb=$(lsof | grep deleted | awk '{sum+=$7} END {print sum/1024/1024/1024}')
12
deleted_size_gb_int=$(printf "%.0f" $deleted_size_gb)
14
if [ $deleted_size_gb_int -ge $THRESHOLD_GB ]; then
15
echo "警报:已删除文件占用 ${deleted_size_gb_int}GB,超过阈值 ${THRESHOLD_GB}GB"
17
lsof | grep deleted | awk '{print $1,$2,$7/1024/1024" MB",$9}' | sort -k3 -nr | head -20
6.2 按进程统计的监控脚本
BASH
5
check_process_handles() {
9
pids=$(pgrep $process_name)
12
handle_count=$(ls /proc/$pid/fd 2>/dev/null | wc -l)
13
if [ $handle_count -gt $max_handles ]; then
14
echo "警告:进程 $process_name (PID: $pid) 打开 $handle_count 个文件描述符"
16
lsof -p $pid | grep deleted | head -10
22
check_process_handles java 1000
7. 生产环境最佳实践
7.1 定期检查与维护
BASH
2
0 2 * * * /opt/scripts/check_deleted_files.sh >> /var/log/file_handle_check.log
5
0 3 * * 1 /opt/scripts/generate_handle_report.sh | mail -s "文件句柄周报" admin@company.com
7.2 应用开发规范
-
文件操作原则:
- 使用try-with-resources(Java)或with语句(Python)
- 确保异常情况下也能正确关闭文件
- 避免在循环中重复打开文件
-
日志管理:
- 配置合理的日志轮转策略
- 设置日志文件大小和数量上限
- 使用日志框架的异步appender
-
监控告警:
- 监控进程的文件描述符数量
- 设置磁盘空间使用率告警
- 建立文件句柄泄漏的自动化检测
7.3 应急响应流程
当发现磁盘空间异常时,按以下顺序排查:
- 使用
df -h 确认磁盘使用情况
- 使用
du -sh 对比实际文件大小
- 如果存在差异,立即使用
lsof | grep deleted
- 定位问题进程和文件
- 根据业务影响评估选择重启或逐步清理
- 根本原因分析和预防措施实施
8. 常见问题排查指南
8.1 lsof命令执行问题
| 问题现象 |
可能原因 |
解决方案 |
| lsof执行很慢 |
系统打开文件过多 |
使用 lsof -w 禁止警告,或指定具体目录 |
| 权限不足 |
普通用户无法查看系统进程 |
使用sudo或以root身份执行 |
| 无输出结果 |
过滤条件太严格 |
先执行 lsof 查看所有结果,再逐步过滤 |
8.2 文件句柄泄漏疑难问题
问题: lsof显示大量已删除文件,但重启应用影响业务怎么办?
解决方案:
BASH
3
lsof -p <pid> | grep deleted | grep log
问题: 如何预防文件句柄泄漏?
解决方案:
- 代码层面:使用资源自动管理机制
- 配置层面:设置合理的文件描述符限制
- 监控层面:建立文件句柄使用率监控
- 流程层面:定期进行代码审查和压测
掌握lsof这个强大的排查工具,能够让你在磁盘空间异常问题时快速定位根源。记住关键排查流程:df/du对比确认 → lsof查找已删除文件 → 定位问题进程 → 采取相应措施。结合自动化监控和预防措施,可以有效避免类似问题的重复发生。
在实际工作中,建议将lsof的基本用法纳入团队的技术储备,建立标准化的排查流程。当磁盘空间报警时,不再需要盲目查找,而是能够精准打击问题根源。