DEBUG.WIKI
京东云;让 AI 进入真实业务;零售 · 物流 · 客服 · 硬件;查看方案 →
ZgoCloud:多地区部署,按需选节点;日本、美国、香港、德国
Evoxt;最高 6.0GHz CPU;高频云服务器 · 免费每周备份;查看配置 →

Linux 删除大日志后磁盘空间未释放,lsof 仍显示 deleted

删除日志后 df 占用不降,可能仍有进程持有旧文件。用 lsof 核对链接数、设备和持有者,再通过日志重开或受控重启释放引用,避免强杀和盲目截断。

删掉几个大日志后,目录里的文件消失了,du 统计也下降,但 df 仍显示文件系统接近满额。此时进程列表里可能还挂着这些日志:lsof 的文件名后带着 (deleted)。

此时应检查是否有进程仍打开已删除的文件。Linux 删除文件名后,如果还有进程持有该文件的打开引用,文件内容可以继续存在,写日志的程序甚至还能继续往里写。只有相关引用释放后,这部分空间才有机会回收。

不过,看到 (deleted) 并不能直接判定它就是几 GB 空间的去向。零字节临时文件也会被主动删除后继续使用;同一个文件还可能在多个进程、多个描述符中重复出现。下面以 Linux 上的普通日志文件为范围,用 GNU df、du 和 lsof 定位,再让所属应用关闭旧日志。

先对齐文件系统,再比较占用

先检查原日志目录所在的文件系统。下面的路径是示例,替换成已删除日志的父目录,不要再把已不存在的文件名传给 df:

df -hT /var/log/myapp
df -ih /var/log/myapp

第一条查看空间、文件系统类型和挂载点,第二条查看 inode 使用情况。如果主要耗尽的是 inode,而不是数据块,应转向大量小文件的排查,不能把所有 No space left on device 都归到大日志上。

df 报告整个文件系统;du 按仍能访问到的目录和文件统计。要判断两者是否有明显缺口,应在同一文件系统范围比较。假设上一步显示独立挂载点为 /srv/data,可以读取:

sudo du -xsh /srv/data

/srv/data 必须换成实际挂载点;如果是根文件系统,则检查 /。-x 避免把下层其他文件系统的目录一起计入,-s 只显示合计。检查大目录可能产生明显 I/O,应选择合适时段,并留意权限拒绝等错误,不能忽略错误后把不完整统计当成结论。

不要只把某个日志子目录的 du 与整块文件系统的 df 相减,也不要用 du -sh * 的合计作为完整证据。目录统计范围、隐藏文件、文件系统自身开销等都会影响比较。两者存在差额,只能作为继续查找的线索。

找出链接数为零但仍打开的文件

先查看当前可见的已解除链接文件:

sudo lsof -nP +L1

+L1 选择链接数小于 1 的打开文件,比按文件名模糊搜索更直接。重点核对普通文件的 COMMAND、PID、FD、DEVICE、SIZE/OFF、NLINK、NODE 和 NAME。

其中,PID 标识持有文件的进程;FD 是该进程内的文件描述符;NLINK 为 0 表示目录链接已经消失。DEVICE 和 NODE 可以帮助识别同一文件,避免把多个打开记录的大小重复相加。SIZE/OFF 根据文件类型和输出情况表示大小或偏移,不能对所有行直接求和,更不能把逻辑文件长度一律当成实际占用块数。

确认记录属于刚才检查的文件系统,且文件大小和删除时间线符合故障。若只想查看一个文件系统,可用其真实挂载点缩小范围,例如:

sudo lsof -nP +aL1 /srv/data

如果锁定了 PID,可以再看该进程持有的已删除文件。下面的 12345 只是示例数字:

sudo lsof -nP -a -p 12345 +L1

这里保留 -a,让“指定 PID”和“链接数小于 1”同时成立。不能随意去掉它,因为 lsof 的多项选择通常按 OR 组合。

空结果不等于已经排除所有持有者。读取权限、进程可见范围以及容器的 PID、挂载视图都可能影响定位。应在能看到所属进程和目标文件系统的环境里检查;不要用容器内的一次空输出证明宿主机没有占用,也不要用宿主机不对应的路径判断容器日志。

优先让应用重新打开日志

需要让持有者关闭旧文件,重新创建同名路径本身没有这个作用。即使在原位置新建空日志,已经打开旧文件的进程也不会自动切换到新文件。

先确认 PID 对应的服务和日志用途。有些应用提供“重新打开日志”的专门操作,可以在不中断整个服务的情况下切换文件。必须按该应用的控制方式执行,不能把某种信号套用到所有进程。

例如,仅在已确认持有者是 Nginx,并且使用与运行实例一致的可执行文件、配置和路径前缀时,可使用:

sudo nginx -s reopen

它请求 Nginx 主进程重新打开日志,再通知工作进程切换。若实例启动时指定了 -c 或 -p,控制命令也应对应同一实例;不要误发给另一套 Nginx。操作后检查日志路径可创建、权限正确,并确认新请求确实写入新日志。这个例子不适用于任意应用,也不保证其他进程没有继续持有旧文件。

如果应用没有可靠的重开日志方式,才考虑在维护窗口重启所属服务。对于由 systemd 管理的系统服务,可以先根据实际 PID 核对归属:

sudo systemctl status 12345

确认单元、业务影响、可用配置及启动条件后,再替换下面的示例服务名:

sudo systemctl restart myapp.service
sudo systemctl status myapp.service

重启会中断该服务的工作;磁盘仍满时,新进程也可能因无法创建文件而启动失败。先安排必要的临时容量或其他已确认可清理内容,不要让“释放日志”变成一次无法恢复的停服。服务状态正常后还应检查业务请求和新日志写入。

重启也不是无条件的释放保证:同一个日志可能仍被别的进程持有,systemd 的文件描述符存储机制也可能跨重启保留资源。若旧记录仍在,应继续核对持有者和服务配置,不要重复强杀进程。

用旧文件记录消失和可用空间变化验收

操作前记下目标文件的设备、节点号和持有 PID;操作后重新执行对应文件系统的 lsof +aL1 检查,并用相同路径再次运行 df。需要确认的是旧文件的所有相关持有记录已经释放,而不是服务换了一个 PID 就算完成。

同时检查新日志是否持续写入、应用是否正常响应。如果旧记录消失但空间变化不符合预期,不要继续删除更多日志。并发写入、检查范围和文件系统自身的占用仍需分别核实;这一步没有证据就不能归因于缓存或文件系统损坏。

不要把 kill -9 作为常规清理,也不要批量向 /proc/PID/fd/FD 写入空内容。这可能绕过应用正常收尾,或截断仍被程序使用的数据;即使名字像日志,也要考虑日志保留要求和正在读取它的其他进程。

如果已删除的日志还需留存,必须在关闭最后引用之前安排到其他有空间位置的受控保存,确认完整性及敏感信息权限;不要边释放边期待能从原文件名恢复。已经完成的删除无法靠重启回退。后续应按应用支持的方式轮转、重开并保留日志,避免直接删除正在写入的文件。