🔥 故障现象

某天早上,服务突然报警:

[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 的人都知道,这种 dfdu 不一致的情况,通常是由于:

文件已被删除,但仍被进程打开,导致 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.confdiskFallBehindThreshold 没来得及生效,但我们仍保留了它作为兜底策略:

diskMaxUsedSpaceRatio=95
diskFallBehindThreshold=5368709120
fileReservedTime=48

同时,配置 logrotate 使用 copytruncate 避免日志文件句柄丢失:

/var/lib/docker/containers/*/*.log {
    rotate 7
    daily
    compress
    copytruncate  # 关键!避免 lsof +L1 出现 deleted
}

📌 经验总结

问题原因解决方案
dfdu 差异大被删除但未释放的文件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 + 重启容器 → 释放空间
    ↓
服务恢复正常 ✅

🚀 后续建议

  1. 定期清理 Docker 缓存
    docker system prune -a && docker builder prune -a
    
  2. 监控 df vs du 差异,设置告警
  3. 避免在生产机频繁构建镜像,使用专用 CI 节点
  4. 使用 copytruncate 处理日志轮转

🔚 写在最后

这次故障让我深刻体会到:

不要迷信配置,要追根溯源;不要只看应用层,要关注基础设施。

一个简单的 dfdu 的差异,背后可能是几十 GB 的“幽灵占用”。而 docker builder prune -a 这个命令,也该成为运维同学的日常工具之一。

更多推荐