记一次 RocketMQ “磁盘满”故障排查:du 和 df 的差异暴露了 Docker 缓存黑洞
🔥 故障现象
某天早上,服务突然报警:
[request-id=8f7cd56b-caee-4dbe-8c85-05e9dfa817f8, response-code=50001]
service not available now. It may be caused by one of the following reasons:
the broker's disk is full [CL: 0.94 CQ: 0.94 INDEX: 0.94],
messages are put to the slave, message store has been shut down, etc.
看着熟悉的错误码和“disk is full”,第一反应是:RocketMQ 磁盘空间不足,拒绝写入消息。
但接下来的操作让我陷入了困惑……
🔍 初步排查:配置调整为何无效?
Step 1:修改 broker.conf
根据经验,我立刻进入 broker 容器,修改配置文件:
# 允许使用 99% 磁盘空间
diskMaxUsedSpaceRatio=99
# 至少保留 5GB 才拒绝写入
diskFallBehindThreshold=5368709120
重启容器后,期待服务恢复……但错误依旧!
日志仍然显示:
[CL: 0.94 CQ: 0.94 INDEX: 0.94]
明明设置了 99%,为什么还是报“磁盘满”?难道配置没生效?
Step 2:检查磁盘使用情况
我执行了:
df -h
输出如下:
Filesystem Size Used Avail Use%
/dev/mapper/ubuntu--vg-ubuntu--lv 490G 434G 34G 93% /
确实,根分区用了 93%,接近阈值。
但我马上意识到一个问题:RocketMQ 的数据目录 /root/store 真的有这么大吗?
于是执行:
du -sh /root/store
结果让我震惊:
30G /root/store
什么?!df 显示用了 434G,du 却只统计出 30G?
这中间差了 400G 去哪了?!
💡 灵光一闪:被删除但未释放的文件
熟悉 Linux 的人都知道,这种 df 和 du 不一致的情况,通常是由于:
文件已被删除,但仍被进程打开,导致 inode 未释放,空间无法回收
我立刻想到:是不是 Docker 或某个服务在疯狂写日志,又被删了但句柄没关?
于是运行:
lsof +L1
输出赫然出现大量 (deleted) 文件:
java 12345 ... /var/lib/docker/overlay2/xxxxx/merged/root/logs/rocketmqlogs/broker.log (deleted)
node 67890 ... /var/lib/docker/containers/yyyyy/yyyyy-json.log (deleted)
...
这些是 Docker 容器的日志文件,虽然被 logrotate 或手动清理了,但 Docker daemon 仍在写入,导致空间一直被占着!
🧹 深入挖掘:Docker 自身也在“吃”磁盘
除了运行时日志,我还怀疑 Docker 的构建缓存是否也占用了大量空间。
执行:
docker system df
输出:
TYPE TOTAL ACTIVE SIZE RECLAIMABLE
Images 15 5 8.2GB 5.1GB (62%)
Containers 20 15 3.5GB 1.2GB (34%)
Local Volumes 8 5 2.8GB 1.6GB (57%)
Build Cache 1 - 380GB 380GB (100%)
😱 Build Cache 居然占了 380GB!且全部可回收!
原来 CI/CD 流水线频繁构建镜像,却从未清理缓存,Docker 把所有中间层都留着,形成了一个“存储黑洞”。
✅ 最终解决方案
1. 清理 Docker 构建缓存(释放 380GB)
docker builder prune -a
⚠️ 注意:这会清除所有构建缓存,下次构建会变慢,但在非构建高峰期执行是安全的。
执行后,df -h 显示可用空间从 34G 回升到 410G,问题迎刃而解。
2. 重启 RocketMQ Broker(释放被删除的日志文件句柄)
docker restart rmq-broker
这一步让 Java 进程关闭了对 (deleted) 日志文件的引用,系统真正回收了空间。
3. (补充)优化配置防止再次发生
虽然这次 broker.conf 的 diskFallBehindThreshold 没来得及生效,但我们仍保留了它作为兜底策略:
diskMaxUsedSpaceRatio=95
diskFallBehindThreshold=5368709120
fileReservedTime=48
同时,配置 logrotate 使用 copytruncate 避免日志文件句柄丢失:
/var/lib/docker/containers/*/*.log {
rotate 7
daily
compress
copytruncate # 关键!避免 lsof +L1 出现 deleted
}
📌 经验总结
| 问题 | 原因 | 解决方案 |
|---|---|---|
df 与 du 差异大 | 被删除但未释放的文件 | lsof +L1 查找并重启进程 |
| Docker 占用巨大 | 构建缓存未清理 | docker builder prune -a |
| RocketMQ 报磁盘满 | 实际是宿主机磁盘满 | 根本解决磁盘问题,而非只调配置 |
✅ 排查流程图
[服务报错] → "disk is full"
↓
df -h → 磁盘使用率 >90%
↓
du -sh / → 发现实际文件远小于 df
↓
lsof +L1 → 找到 (deleted) 文件
↓
docker system df → 发现 Build Cache 巨大
↓
docker builder prune -a + 重启容器 → 释放空间
↓
服务恢复正常 ✅
🚀 后续建议
- 定期清理 Docker 缓存:
docker system prune -a && docker builder prune -a - 监控
dfvsdu差异,设置告警 - 避免在生产机频繁构建镜像,使用专用 CI 节点
- 使用
copytruncate处理日志轮转
🔚 写在最后
这次故障让我深刻体会到:
不要迷信配置,要追根溯源;不要只看应用层,要关注基础设施。
一个简单的 df 和 du 的差异,背后可能是几十 GB 的“幽灵占用”。而 docker builder prune -a 这个命令,也该成为运维同学的日常工具之一。
更多推荐
所有评论(0)