我的第一台正经 VPS 是在 2013 年前后买的,那时候挑机器只看两个数字:内存和流量。磁盘那一栏写的是「20G」,我默认它就是 20G,直到有一天 WAL 文件把分区写满,MySQL 直接拒绝写入,站点整站白屏,我才第一次认真看 df -h 的输出——那台机器号称 20G,根分区实际只有 9.8G,另外 10G 是服务商预留给系统镜像和快照的空间。从那以后,我买机器会先跑一遍磁盘体检,再谈别的事。
这篇文章只讲一件事:一块盘在 Linux 上报告出来的容量,到底从哪来,又可能在哪一步消失。搞不清楚这个问题的人,扩容时会买错规格,迁移时会算错剩余空间,出故障时会把「磁盘满」误判成「程序有 bug」。下面按我实际的排查顺序来。
一、df 和 lsblk 报的不是同一个数
新手最容易犯的第一个错,是把 df -h 和 lsblk 的输出当成同一回事。它们差别很大,而且差了不止一点点。
# 磁盘厂家说什么
lsblk -b -d -o NAME,SIZE
# 文件系统实际有多少可用
df -hT /lsblk -b 显示的是块设备的裸容量,单位是字节,这是磁盘(或云盘)标称的容量。df -hT 显示的是文件系统层面的容量,它已经扣掉了三样东西:分区表或 LVM 元数据、文件系统自身的元数据(inode 表、日志区、保留块)、以及 ext4 默认给 root 预留的 5%。
所以一块 100G 的云盘,格式化挂载之后 df -h 常常显示 97G,可用 92G。这不是服务商偷容量,三处损耗都是正常的。真正需要警惕的是「差得离谱」,比如 100G 的盘 df 只有 60G,那就要往下查了。
二、五个数一起看:Size、Used、Avail、Use%、Inode
我现在的习惯是一条命令把这五项全打出来,一眼就能判断是哪一类的满:
df -hT -x tmpfs -x devtmpfs
df -i -x tmpfs -x devtmpfs
lsblk -o NAME,SIZE,FSTYPE,MOUNTPOINT,TYPE判断逻辑非常固定,练几次就不会错:
- Used 接近 Size,Use% 接近 100%,但
du加起来远小于这个数:有被删除但仍被进程打开的文件。这是最经典的一种「幽灵占用」,下面单独讲。 - Avail 很小但 Used 也不大:保留块或 inode 被占满。用
df -i看 IUse%,如果接近 100%,问题在 inode 不在字节。 - Size 本身就偏小,和买的时候不符:分区没扩、LVM 逻辑卷没扩、或者云盘扩容后没做文件系统 resize。这三件事是三个独立的步骤,漏一步都不会生效。
顺手把 -x tmpfs -x devtmpfs 加上,可以过滤掉一堆内存文件系统,输出干净很多,出问题时也不容易被 /dev/shm 这种 100% 的行干扰判断。
三、最常见的「幽灵占用」:删了文件空间没回来
这个坑我踩过至少三次,每次都是同一个形态:日志轮转脚本把几百兆的日志 rm 掉了,df 一点没变,du 算出来却是空的。原因很简单,Linux 的文件是「引用计数」语义:文件被 unlink 之后,只要还有进程持有它的文件描述符,数据块就不会释放。
# 找出所有已删除但仍被占用的文件,按大小排序
lsof +L1 2>/dev/null | sort -k7 -rn | head -20
# 也可以直接看某一类进程
lsof -p 1234 | grep deleted注意这里不要背字段号。lsof +L1 的输出里,文件大小那一列的位置随版本和列宽会变,正确做法是看表头,把列对齐之后按「文件大小」那一列排序;或者更稳的方式是用 ls -l /proc/<pid>/fd 单个确认。
处置办法有三种,按优先级排:
- 让进程重新打开文件:对 Nginx 是
nginx -s reopen,对日志收集器是发一个 USR1 信号。这是最优雅的,业务不中断。 - 重启进程:PHP-FPM、应用进程这类不支持 reopen 的,只能 reload 或重启。注意 reload 不一定能释放,因为老 worker 可能还在优雅退出阶段,观察一下
ps再确认。 - truncate 代替 rm:如果文件还没被删,用
: > /var/log/app.log或truncate -s 0,进程会立刻看到长度变成 0,空间马上回来。这是我现在日志清理脚本里唯一允许的清理方式。
顺带说一句,du 与 df 对不上时,不一定都是幽灵文件。挂载点覆盖(一个目录上又挂了一个文件系统,原目录被遮住但数据还在)、du 因为权限不足跳过了某些子目录,也会造成这个现象。所以看到差值得先确认 du 的命令是拿 root 跑的,且没有跨挂载点。
四、inode 满了,磁盘却是空的
第二种「假满」是 inode 耗尽。df -h 显示还有一半空间,程序却报 No space left on device,很多人第一反应是「Linux 出 bug 了」。其实原因通常是某个目录里堆了几十万个几 KB 的小文件。
# 统计各一级目录的 inode 使用
for d in /*; do printf "%-20s %s\n" "$d" "$(find "$d" -xdev -type f 2>/dev/null | wc -l)"; done
# 定位到具体的目录
du -a /var 2>/dev/null | sort -n -r | head -20
find /var/spool -type f 2>/dev/null | wc -l常见的元凶:PHP 的 session 目录没有配 GC、邮件队列积压、临时上传目录没清理、某个脚本每次运行都生成一个带时间戳的小文件。inode 数量在格式化时就固定了,事后不能扩,只能删。
如果确实有小文件长期存在且数量稳定,可以在格式化时用 mkfs.ext4 -i 8192 把「每个 inode 对应多少字节」调小,能拿到更多 inode。但这是重建文件系统的操作,只在新建盘时考虑,绝不要在线上盘上重做。
五、扩容为什么没生效:三个独立的步骤
云盘扩容是这个流程里最容易出错的一环。买完扩容之后 df 没变,几乎每个人都遇到过,原因是扩容其实分三层,每一层都要单独做:
# 1) 块设备层面:能看到新容量了吗
lsblk
# 2) 分区层面(如果是整盘直接用,跳过这一步)
growpart /dev/vda 1
# 3) 文件系统层面
resize2fs /dev/vda1 # ext4
xfs_growfs / # xfs,注意传的是挂载点不是设备三个坑必须记住:
第一,xfs 和 ext4 的参数不一样。resize2fs 传块设备,xfs_growfs 传挂载点。写反了会报错,或者更糟——静默成功但没效果。
第二,xfs 只能扩不能缩。如果盘上跑的是 xfs,扩容之后就别指望再缩回去。这也是我新建数据盘时优先用 ext4 的原因之一,个人站点的数据量不大,能缩的灵活性比性能差异更重要。
第三,LVM 多一层。pvresize、lvextend、resize2fs 三连,一样是三步。很多教程只写了 lvextend,漏了 pvresize,结果 lvextend 报「没有足够的物理扩展」。正确的顺序是先让物理卷认出新容量,再扩逻辑卷,最后扩文件系统。
六、删完数据磁盘不缩小,是文件系统的正常行为
这一点经常被误解成「服务商不给缩容」。删了大文件之后 df 的 Used 降了,但如果是看监控面板里的磁盘使用率(云厂商通常读的是块设备层面),数字可能不会动。这中间有两个原因:文件系统内部的空闲块分布零散,块设备层面看不出变化;云盘自身的容量本就不会因为文件系统删数据而变化。
如果你确实需要把磁盘缩小以降低费用,正确流程是:备份数据、新建一块小盘、把数据拷过去、改挂载、验证、再删旧盘。不要指望在原盘上做「缩小」操作,风险远高于收益。
七、上传目录是个无底洞,要单独盯
对个人站长来说,磁盘吃紧九成的根源都在同一个地方:上传目录。图片、附件、备份文件、临时文件,全部往 uploads 底下堆,几年下来就是几十万个文件、上百 G 体积。我在自己的站点上做过一次统计,uploads 占了全站磁盘的 86%。
处置方法不是「定期删」,而是先量化再定策略:
# 按月份看上传增长
du -sh /var/www/site/uploads/*/ | sort -h | tail -20
# 找出大文件
find /var/www/site/uploads -type f -size +5M -printf "%s\t%p\n" 2>/dev/null | sort -rn | head -20量化之后通常会发现两个特征:少数大文件占了绝大部分体积,绝大多数小文件占了绝大部分 inode。对应两种不同的策略——大文件压缩后归档到对象存储或冷备,小文件按月份清理掉没人访问的。判断「没人访问」用访问日志,别凭感觉删,我自己就因为凭感觉删过一批还在被引用的图片,补回来花了一晚上。
八、我现在的月度磁盘体检清单
固定成习惯之后,磁盘告急这类事故基本不会再出现。五个检查,两分钟跑完:
df -hT看每个挂载点的 Use%,超过 80% 就记一笔。df -i看 IUse%,这是最容易被忽略的一项。lsof +L1看有没有幽灵占用超过 100M 的。- 上传目录本月增量,和上月对比,涨幅突然变大就要查是不是被刷了。
- 备份目录体积,如果备份体积超过源数据两倍,说明备份策略有问题(通常是每次都全量)。
九、小结:磁盘问题的判据只有一条
回头看,磁盘相关的所有「诡异」现象,都能归到一条判据上:先分清是「字节满」还是「inode 满」,再分清是「真占用」还是「引用未释放」,最后才区分是「块设备没扩」还是「文件系统没扩」。这三层判断做完,问题就只剩执行了,不需要猜。
我踩过的那些坑,本质都是跳过了其中一层:以为 df 和 lsblk 是一回事,所以买错规格;以为 rm 完空间就该回来,所以在日志轮转后反复排查;以为扩容只是一个命令,所以扩完不生效又以为服务商有问题。把这三层分开看,磁盘这块基本就没有「玄学」了。