一、背景
在一台 Ubuntu 服务器上遇到磁盘占用困惑:
- 业务侧感觉磁盘"莫名其妙"变大
du统计目录大小时,看起来"没那么多"- 但
df却显示根分区已经实打实占用了 TB 级空间
这类问题非常典型,尤其常见于文档转换服务(LibreOffice/unoconv)、日志系统、长时间运行的 Java/Go 服务。
本文记录一次真实排查过程,从现象到最终根因定位,并给出"终极治理方案"。
二、现象
1)用 find 找大文件
bash
sudo find / -type f -size +1G -printf "%s\t%p\n" 2>/dev/null | sort -nr | head -50
输出中出现了:
/proc/kcore巨大- Ollama 模型 blobs
- NLLB/MT5 模型
/swapfile
2)用 du 看一级目录
bash
sudo du -h -d 1 / 2>/dev/null | sort -hr
你得到类似:
749G /
665G /data
68G /usr
8.0G /var
...
看起来合计合理,似乎没有 1.5TB 的量级。
3)用 df 看真实占用
bash
df -h
关键行:
/dev/nvme0n1p2 3.7T 1.5T 2.1T 42% /
疑点出现:
du汇总感知 ~749Gdf真实已用却是 1.5T- 差额接近 700G+
三、关键认知:du 与 df 统计口径不同
du:统计目录树能"看到"的文件大小df:统计文件系统真实占用空间
当出现差额巨大时,最常见原因就是:
文件被删除了,但仍被某个进程打开(句柄未释放)。
这类文件对
du来说不可见,但对df来说仍占空间。典型表现为 "幽灵占用"。
四、排查第一坑:/proc/kcore
find 里常见的"吓人第一名":
/proc/kcore
它是内核虚拟内存映像:
- 看起来 TB 级
- 不占真实磁盘
- 无需处理
以后找大文件建议排除:
bash
sudo find / -path /proc -prune -o -type f -size +1G -printf "%s\t%p\n" 2>/dev/null | sort -nr | head -50
五、真正的对账姿势:du 对齐 df
为了避免统计跨文件系统导致误差,使用 -x:
bash
sudo du -xhd 1 / 2>/dev/null | sort -hr
-x 意味着:
- 只统计根分区同一文件系统
- 便于和
df -h /直接对应
六、核心排查命令:lsof +L1
当怀疑"幽灵文件"时,直接上:
bash
sudo lsof +L1
这会列出:
- link count = 0
- 但仍被进程打开的文件
七、关键发现:soffice.bin 持有超大 deleted 临时文件
实际输出中出现大量:
soffice.b ... /tmp/lo_job_xxx/luXXXX.tmp (deleted)
并且大小惊人,例如:
298464378880(约 278GB)93539921920(约 87GB)55G+、53G+多个
这些加总:
完全足以解释 df 与 du 的 TB 级差额。
此时可以下结论:
✅ 根因不是磁盘"乱涨",而是LibreOffice headless 转换进程异常残留导致的句柄占用。
八、现场止血方案(立刻见效)
这类 (deleted) 文件不用 rm,删不掉也没意义。
唯一有效动作:结束持有它的进程。
1)温和结束 specific PID
bash
sudo kill -TERM <pid1> <pid2> ...
2)一键结束当前用户的 soffice
bash
pkill -u tanji -f soffice.bin
3)必要时强制
bash
sudo kill -KILL <pid>
4)验证
bash
df -h /
sudo lsof +L1 | head -50
一般会看到 根分区已用空间明显下降。
九、为什么会发生?
在文档转换链路中,常见模式是:
- 服务调用
unoconv或libreoffice --headless - 高并发或异常超时
- 产生大量
/tmp/lo_job_*临时文件 - 清理逻辑提前删除文件
- 但 soffice 仍持有句柄
- 导致磁盘"隐形被吃掉"
尤其当你做:
- 邮件附件解析
- Office → PDF
- 批量归档/索引
这种问题概率很高。
十、终极治理方案(建议你直接落地)
目标是把 LibreOffice 转换从:
"随手起进程 + 默认 /tmp + 无超时"
升级为:
"受控 Worker + 强制超时 + 独立 TMPDIR + 独立 UserInstallation + 并发闸门 + 兜底清场"
方案 1:每个任务使用独立 TMPDIR + 独立 Profile + timeout
推荐模板:
bash
JOB_TMP=/data/tmp/lo_job_$(date +%s)_$$
mkdir -p "$JOB_TMP"
timeout 120s \
env TMPDIR="$JOB_TMP" \
libreoffice --headless --nologo --nofirststartwizard --norestore \
-env:UserInstallation="file://$JOB_TMP/profile" \
--convert-to pdf --outdir "$JOB_TMP/out" "/path/to/input.docx"
rm -rf "$JOB_TMP"
三剑合璧:
timeout:超时必杀TMPDIR:远离系统 /tmpUserInstallation:隔离 profile,避免锁与缓存膨胀
方案 2:限制并发(强烈建议)
无论 Go/Java:
- 全局 semaphore 限制 2~4 并发
- 避免同时起几十个 soffice
方案 3:兜底清场脚本 + 定时任务
/usr/local/bin/lo-janitor.sh(示例)
bash
#!/usr/bin/env bash
set -euo pipefail
THRESH=$((5*1024*1024*1024)) # 5GB
sudo lsof -nP +L1 2>/dev/null \
| awk '
$1 ~ /soffice/ && $7 == 0 {
size=$5; pid=$2; name=$9;
if (size ~ /^[0-9]+$/) print pid, size, name
}
' \
| while read -r pid size name; do
if [ "$size" -gt "$THRESH" ]; then
echo "Killing soffice pid=$pid size=$size name=$name"
sudo kill -TERM "$pid" || true
sleep 2
sudo kill -KILL "$pid" || true
fi
done
赋权:
bash
sudo chmod +x /usr/local/bin/lo-janitor.sh
用 cron 每 5 分钟跑:
bash
sudo crontab -e
加入:
bash
*/5 * * * * /usr/local/bin/lo-janitor.sh >/var/log/lo-janitor.log 2>&1
十一、经验总结
1)遇到"df 大、du 小"
优先怀疑:
- deleted-but-open 文件
- 快照(btrfs/zfs)
- 容器层(docker overlay)
2)三板斧顺序
bash
df -h /
sudo du -xhd 1 / 2>/dev/null | sort -hr
sudo lsof +L1
90% 的"神秘磁盘膨胀"都能快速定位。
3)文档转换服务的正确姿势
- 独立任务临时目录
- 独立 LibreOffice profile
- 强制超时
- 并发控制
- 守护清场
十二、结语
这次排查的本质不是"磁盘真的莫名其妙变大",
而是soffice.bin 持有已删除的大型临时文件,形成了典型的"幽灵占用"。
只要把转换链路工程化治理:
你就能从"频繁救火"进入"长期稳定"。