紧急排查:Linux删文件后空间不释放?罪魁祸首竟是它!​

内容分享3天前发布
0 2 0

问题现象:删除文件后磁盘空间为何不释放?

你是否遇到过这种令人困惑的情况:

  • 使用 df -h 查看磁盘,显示使用率高达 95%
  • 但用 du -sh /var/log 统计目录大小,却发现只占用了 10GB
  • ️ 明明用 rm -f catalina.out 删除了 50GB 的日志文件
  • ❌ 磁盘空间却丝毫没有恢复!

这不是磁盘故障,也不是文件系统错误
而是有进程仍在”偷偷”占用已被删除的文件!

理解 Linux 文件删除机制

关键概念:文件删除 ≠ 空间立即释放

在 Linux 系统中,文件删除操作的实际流程是:

  1. 删除目录项:rm 命令只是移除文件在目录中的链接(unlink)
  2. 引用计数减一:每个文件都有链接计数,归零时空间才可回收
  3. 进程持有文件描述符:如果有进程仍打开该文件,空间不会被释放

通俗比喻:图书馆借书模型

将文件系统比作图书馆:

  • 文件 = 书籍
  • 目录项 = 图书目录卡片
  • 进程 = 借阅者
  • rm 命令 = 从目录中移除图书卡片
  • :如果借阅者仍拿着书,书就不能上架供他人使用

只有等所有借阅者归还书籍(进程关闭文件),书架空间才能真正释放。

实战排查:三步定位问题进程

第一步:扫描被进程占用的已删除文件

#!/bin/bash
# find_deleted_files.sh - 扫描被进程占用的已删除文件

echo " 扫描被进程占用但已删除的文件..."
echo "=========================================="

# 使用 lsof 查找被标记为 deleted 的文件
lsof +L1 2>/dev/null | grep deleted | while read line; do
    # 提取进程信息和文件大小
    command=$(echo $line | awk '{print $1}')
    pid=$(echo $line | awk '{print $2}')
    user=$(echo $line | awk '{print $3}')
    fd=$(echo $line | awk '{print $4}')
    size=$(echo $line | awk '{print $7}')
    filename=$(echo $line | awk '{for(i=9;i<=NF;i++) printf $i" "; print ""}')
    
    # 转换大小为人类可读格式
    if [ -n "$size" ] && [ "$size" -gt 0 ]; then
        human_size=$(numfmt --to=iec $size 2>/dev/null || echo "${size}KB")
    else
        human_size="未知"
    fi
    
    echo "⚠️  发现: PID=$pid, 进程=$command, 用户=$user, 大小=$human_size"
    echo "   文件: ${filename}"
    echo "   FD: $fd"
    echo "---"
done

# 如果没有找到,显示成功信息
if ! lsof +L1 2>/dev/null | grep -q deleted; then
    echo "✅ 未发现被进程占用的已删除文件"
fi

关键输出解读:

COMMAND  PID  USER   FD   TYPE DEVICE SIZE/OFF    NODE NAME
java    1234 root    1w   REG  253,0 52428800 1234567 /app/logs/catalina.out (deleted)
nginx   5678 www     5w   REG  253,0 1073741824 7654321 /var/log/nginx/access.log (deleted)

重点关注列:

  • PID:占用文件的进程ID
  • SIZE/OFF:文件占用空间大小(字节)
  • (deleted):标记文件已被删除但未释放

第二步:深入分析问题进程

#!/bin/bash
# analyze_process.sh - 分析占用已删除文件的进程

if [ $# -eq 0 ]; then
    echo "用法: $0 <进程PID>"
    echo "示例: $0 1234"
    exit 1
fi

PID=$1

echo " 分析进程 PID: $PID"
echo "========================"

# 检查进程是否存在
if [ ! -d "/proc/$PID" ]; then
    echo "❌ 错误: 进程 $PID 不存在"
    exit 1
fi

# 获取进程基本信息
echo " 进程基本信息:"
ps -fp $PID 2>/dev/null || echo "   无法获取进程信息"

# 获取进程打开的所有已删除文件
echo -e "
 进程打开的已删除文件:"
lsof -p $PID 2>/dev/null | grep deleted | while read line; do
    fd=$(echo $line | awk '{print $4}')
    size=$(echo $line | awk '{print $7}')
    filename=$(echo $line | awk '{for(i=9;i<=NF;i++) printf $i" "; print ""}')
    
    if [ -n "$size" ] && [ "$size" -gt 0 ]; then
        human_size=$(numfmt --to=iec $size 2>/dev/null || echo "${size}KB")
    else
        human_size="未知"
    fi
    
    echo "   FD: $fd, 大小: $human_size, 文件: $filename"
done

# 获取进程的资源使用情况
echo -e "
 进程资源使用:"
if [ -f "/proc/$PID/status" ]; then
    echo "   内存(RSS): $(grep VmRSS /proc/$PID/status | awk '{print $2 $3}')"
    echo "   虚拟内存: $(grep VmSize /proc/$PID/status | awk '{print $2 $3}')"
fi

# 获取进程启动时间和运行时间
if [ -d "/proc/$PID" ]; then
    start_time=$(stat -c %Y /proc/$PID)
    current_time=$(date +%s)
    run_time=$(( (current_time - start_time) / 3600 ))
    echo "   运行时间: ${run_time}小时"
fi

第三步:安全释放磁盘空间

根据进程类型选择合适的释放方案:

方案一:优雅重启/重载(推荐)

#!/bin/bash
# safe_space_release.sh - 安全释放磁盘空间

PID=$1
COMMAND=$2

case $COMMAND in
    "nginx")
        echo " 优雅重载 Nginx..."
        nginx -t && nginx -s reload
        ;;
    "tomcat"|"java")
        echo " 重启 Tomcat/Java 应用..."
        # 请根据实际路径修改
        /opt/tomcat/bin/shutdown.sh
        sleep 5
        /opt/tomcat/bin/startup.sh
        ;;
    "apache"|"httpd")
        echo " 优雅重启 Apache..."
        systemctl reload apache2
        ;;
    *)
        echo "⚠️  未知服务类型,尝试优雅终止..."
        kill -TERM $PID
        sleep 3
        if ps -p $PID > /dev/null; then
            echo "❌ 优雅终止失败,进程仍在运行"
        else
            echo "✅ 进程已终止"
        fi
        ;;
esac

# 验证空间释放
echo -e "
 空间释放结果:"
df -h /var | grep -v Filesystem

方案二:强制终止(谨慎使用)

#!/bin/bash
# force_kill_process.sh - 强制终止进程(最后手段)

PID=$1
PROCESS_NAME=$(ps -p $PID -o comm= 2>/dev/null)

if [ -z "$PROCESS_NAME" ]; then
    echo "❌ 进程 $PID 不存在"
    exit 1
fi

echo "⚠️  警告:即将强制终止进程"
echo "   进程: $PROCESS_NAME (PID: $PID)"
echo "   这可能导致数据丢失或服务中断!"

read -p "❓ 确认继续?(y/N): " confirm

if [[ $confirm =~ ^[Yy]$ ]]; then
    echo " 终止进程 $PID ..."
    kill -9 $PID
    
    if [ $? -eq 0 ]; then
        echo "✅ 进程已终止"
        echo -e "
 磁盘空间变化:"
        df -h /var | grep -v Filesystem
    else
        echo "❌ 终止进程失败"
    fi
else
    echo "✅ 操作已撤销"
fi

预防措施:建立长效机制

1. 正确的日志轮转配置

错误示范:

# ❌ 危险操作:直接删除当前日志文件
rm -f /var/log/app.log
touch /var/log/app.log

正确配置(logrotate):

# /etc/logrotate.d/myapp
/var/log/myapp/*.log {
    daily
    rotate 7
    compress
    delaycompress
    missingok
    notifempty
    
    # 方案A:适用于无法接收信号的程序
    copytruncate
    
    # 方案B:适用于支持信号重载的程序
    postrotate
        # 对Nginx发送USR1信号重新打开日志
        /bin/kill -USR1 $(cat /var/run/nginx.pid 2>/dev/null) 2>/dev/null || true
    endscript
}

2. 应用层日志管理最佳实践

Java 应用:

<!-- logback.xml 配置示例 -->
<configuration>
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>app.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>app.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <maxFileSize>100MB</maxFileSize>
            <maxHistory>30</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} - %msg%n</pattern>
        </encoder>
    </appender>
</configuration>

3. 自动化监控告警系统

#!/bin/bash
# disk_space_monitor.sh - 磁盘空间监控告警

THRESHOLD=85
CURRENT_USAGE=$(df /var | awk 'NR==2 {print $5}' | sed 's/%//')

# 检查磁盘使用率
if [ $CURRENT_USAGE -gt $THRESHOLD ]; then
    echo "⚠️  磁盘使用率告警: ${CURRENT_USAGE}%"
    
    # 检查是否有未释放的已删除文件
    DELETED_FILES=$(lsof +L1 2>/dev/null | grep deleted | wc -l)
    if [ $DELETED_FILES -gt 0 ]; then
        echo " 发现 ${DELETED_FILES} 个被占用但已删除的文件"
        
        # 生成详细报告
        REPORT_FILE="/tmp/deleted_files_report_$(date +%Y%m%d_%H%M%S).txt"
        lsof +L1 2>/dev/null | grep deleted > $REPORT_FILE
        
        # 发送告警(可根据需要配置邮件、钉钉、企业微信等)
        echo " 发现被进程占用的已删除文件,请及时处理" | 
        mail -s "磁盘空间告警 - $(hostname)" -a $REPORT_FILE admin@example.com
    fi
fi

# 加入crontab,每5分钟检查一次
# */5 * * * * /opt/scripts/disk_space_monitor.sh

快速诊断流程图

磁盘空间异常 → df/du 结果不一致
        ↓
lsof +L1 扫描已删除但被占用的文件
        ↓
发现目标进程 → 分析进程类型和服务
        ↓
    ┌─────────────────┐
    │ 选择处理方案    │
    └─────────────────┘
            ↓
    ┌─────────────────┬─────────────────┐
    │ 优雅重启/重载   │ 强制终止进程    │
    │ (推荐)          │ (最后手段)      │
    └─────────────────┴─────────────────┘

总结

核心要点回顾:

  1. 理解机制:Linux 中文件删除只是解除链接,空间释放需要进程关闭文件
  2. 排查工具:lsof +L1 是定位问题的关键命令
  3. 解决方案:优先使用优雅重启(reload/restart),慎用强制终止
  4. 预防为主:正确配置日志轮转,建立监控告警

记住这个简单公式:

问题排查 = lsof +L1(定位) + 优雅重启(解决) + 日志轮转(预防)

通过掌握这些技能,你就能在磁盘空间”消失”的迷雾中快速定位问题,确保系统稳定运行。提议将文中的脚本部署到生产环境,建立自动化的监控和处理机制。

© 版权声明

相关文章

2 条评论

  • 头像
    德扑圈安卓系统有吗 读者

    这就是Linux垃圾的地方,按理说,比如Windows里,被占用的文件根本删都不能删的

    无记录
    回复
  • 头像
    Sep18thW 读者

    收藏了,感谢分享

    无记录
    回复