问题现象

在一次日常巡检中,发现服务器磁盘空间异常:

1
2
3
4
5
6
7
8
# df 显示 /data 已用 447G
df -h /data
Filesystem      Size  Used Avail Use% Mounted on
/dev/sdb1       500G  447G   53G  90% /data

# 但 du 统计只有 52G
du -sh /data
52G	/data

447G vs 52G,差距近 400G!这不是眼睛花了,也不是 df/du 抽风。

df 与 du 结果对比


问题定位

第一步:确认文件系统类型

1
2
mount | grep /data
/dev/sdb1 on /data type ext4 (rw,relatime)

/data 是 ext4 文件系统(非 btrfs/zfs,排除快照占空间的可能)。

第二步:排查残留文件

1
2
3
4
5
6
7
# 检查 lost+found(fsck 残留)
ls -la /data/lost+found/
# 很小,排除

# 检查隐藏的大文件
find /data -type f -size +1G 2>/dev/null | head -20
# 没有异常

第三步:关键命令 - 查找已删除但仍被占用的文件

1
lsof +L1 | grep /data

输出结果:

lsof +L1 输出显示 deleted 文件

1
2
3
4
java    2487  vale   52w   REG  253,16  104859430  /data/.../rocketmqlogs/stats.log (deleted)
java    2487  vale   49w   REG  253,16  104859836  /data/.../transaction.log (deleted)
java    2487  vale   45w   REG  253,16  39150059417 /data/.../store.log (deleted)
java    2487  vale   43w   REG  253,16  235962624  /data/.../broker.log (deleted)

实锤了!

  • 进程:java(RocketMQ Broker,PID 2487)
  • 状态(deleted) —— 文件已被删除,但进程还在持有 FD
  • 大小:store.log 单文件 39GB,其他几个加起来几十 GB

为什么 du 看不到这些文件?

命令统计范围当前情况
df -h文件系统块使用情况447G 已用 —— 被删除但 FD 未释放的文件仍占用块
du -sh目录树可见文件52G —— 被删除的文件在目录中已不可见

这就是那近 400G 差距的来源。


根因分析

发生了什么?

  1. RocketMQ Broker 持续写入日志文件
  2. 运维/脚本执行了 rm -f store.log 等日志清理
  3. 但是:Broker 进程没有重启,JVM 仍然持有旧的文件描述符(FD)
  4. 结果:磁盘空间被"幽灵文件"占用,但文件系统目录里已经看不到

为什么 RocketMQ 容易出现这个问题?

  • RocketMQ 默认使用 logback/log4j 记录日志
  • 不支持标准的 logrotate rename + truncate 机制
  • 只要外部删除日志而 Broker 不重启,必中这一刀

解决方案

紧急恢复(推荐)

重启 RocketMQ Broker,释放被占用的 FD:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 找到 Broker 进程
ps -ef | grep runbroker

# 方式1:手动启动模式
cd /data/timevale/esign/public/rocketmq/bin
./mqshutdown broker
sleep 5
./runbroker.sh &

# 方式2:systemd 模式
systemctl restart rocketmq-broker

重启瞬间

  • 所有 (deleted) FD 关闭
  • 400G+ 空间立刻回收
  • df -h 数字会"跳变"

验证:

1
2
df -h /data
# 现在 Used 应该回到 ~50G 左右

不推荐:不重启清空 FD(风险自担)

如果确实不能重启 Broker(生产高峰期):

1
2
# 直接清空特定 FD(示例:PID 2487 的 fd 45)
: > /proc/2487/fd/45

⚠️ 风险

  • 可能破坏 RocketMQ 日志完整性
  • JVM 不一定能正确处理
  • 不建议在生产环境使用

预防措施

方案 A:时间滚动 + 定期重启(最稳)

1
2
3
# 日志只按时间保留,不直接 rm
# 定期(如凌晨 4 点)重启 Broker
0 4 * * * systemctl restart rocketmq-broker

优点:简单可靠,RocketMQ Broker 重启成本极低
缺点:有短暂服务中断

方案 B:官方日志滚动配置(推荐生产)

修改 logback_broker.xml / log4j2.xml

1
2
3
4
5
6
7
<RollingFile name="RocketmqBroker" fileName="${LOG_HOME}/broker.log">
    <Policies>
        <TimeBasedTriggeringPolicy interval="1"/>
        <SizeBasedTriggeringPolicy size="1GB"/>
    </Policies>
    <DefaultRolloverStrategy max="10"/>
</RollingFile>

要点

  • 禁止外部 logrotate
  • 所有清理交给 JVM 自己处理

方案 C:监控告警

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# 创建监控脚本 /usr/local/bin/check_df_du_diff.sh
#!/bin/bash
MOUNT=/data
DF_USED=$(df -m $MOUNT | awk 'NR==2{print $3}')
DU_USED=$(du -sm $MOUNT 2>/dev/null | awk '{print $1}')
DIFF=$((DF_USED - DU_USED))

# 如果差距超过 50GB,告警
if [ $DIFF -gt 51200 ]; then
    echo "[ALERT] $MOUNT df/du 差异 ${DIFF}MB,可能存在 FD 泄漏" | logger -t disk_monitor
    # 发送告警通知...
fi

添加到 crontab:

1
*/10 * * * * /usr/local/bin/check_df_du_diff.sh

排雷 Checklist

检查项操作目的
日志轮转后systemctl reload nginx 或重启应用释放旧 FD
自研服务不要无限写单文件,定期切割 + reload防止单文件膨胀
空间报警同时看 df 和 du,对比差异早期发现 FD 泄漏
Java 服务优先使用 JVM 内置日志滚动避免外部 rm 导致泄漏

一句话总结

你的 400G 磁盘空间没有消失,是被"删了但还在用"的文件幽灵抱着不撒手 👻

看到 df 高、du 低、Java 服务、日志目录 → 不用想,先执行 lsof +L1


参考命令速查

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# 查看已删除但仍被占用的文件
lsof +L1

# 按目录筛选
lsof +L1 | grep /data

# 查看特定进程打开的删除文件
lsof -p <PID> | grep deleted

# 查看进程打开的 FD 列表
ls -la /proc/<PID>/fd/

# 统计各进程持有的删除文件大小
lsof +L1 | awk '{s+=$7} END {print "Total bytes:", s}'

本文记录于 2026-02-06,CentOS 7 + RocketMQ 环境