<h2>一、最诡异的报错:磁盘明明还有空间</h2>
<p>IDC 运维最怕遇到一类问题:<code>df -h</code> 一看磁盘还剩几十个 G,可程序一写文件就报 <code>No space left on device</code>(设备上没有剩余空间)。第一次遇到的运维往往会懵:空间明明够啊,怎么会写不进去?其实答案往往只有一个——<strong>inode 耗尽了</strong>。</p>
<p>这类事故在邮件服务器、缓存服务器、session 集中存储的机器上尤其高发。本文从原理到实战,讲清楚 inode 是什么、怎么定位、怎么清理、怎么根治。</p>

<h2>二、inode 是什么:文件系统的"户口本"</h2>
<p>Linux 文件系统(ext4/xfs)在格式化时会把存储空间分成两大部分:一部分存数据块(文件内容),一部分存 inode(索引节点)。每个文件或目录都必须占用一个 inode,里面记录着文件的权限、属主、大小、时间戳以及数据块指针。可以粗暴地理解为:<strong>inode 是文件的"户口本",没有户口本,内容再多也落不了地</strong>。</p>
<p>关键点在于:inode 的数量在<strong>格式化分区时就固定了</strong>(ext4 默认每 16KB 数据空间分配一个 inode)。也就是说,一个分区能存放的文件总数是写死的。如果你创建了海量的小文件(比如几百万个几 KB 的缓存文件),哪怕总容量只用了 1%,inode 也可能先被耗光。</p>
<p>查看 inode 使用情况的命令:</p>
<pre># 查看各分区 inode 使用率(IUse% 列就是 inode 使用率)
df -i

# 只看根分区
df -i /

# 查看文件系统详细 inode 信息
tune2fs -l /dev/sda1 | grep -i inode</pre>

<h2>三、快速定位:三分钟确认是不是 inode 问题</h2>
<p>遇到"空间充足却写不进文件",按下面顺序排查:</p>
<pre># 第一步:确认空间确实够
df -h

# 第二步:查看 inode 使用率——重点看 IUse% 是否接近 100%
df -i

# 第三步:确认当前文件系统类型
stat -f /</pre>
<p>如果 <code>df -i</code> 显示 <code>IUse%</code> 已经是 100%,且 <code>Filesystem</code> 对应挂载点就是你写文件的目录所在分区,那基本可以实锤:<strong>inode 耗尽</strong>。此时可以顺手确认一下是哪个目录在疯狂吃 inode。</p>

<h2>四、谁在疯狂消耗 inode?三大经典元凶</h2>
<h3>元凶一:邮件队列堆积</h3>
<p>邮件服务器(Postfix/Exim)投递失败时,退信会堆积在 <code>/var/spool/postfix/</code> 或 <code>/var/spool/mqueue/</code> 下,一封邮件一个文件。被垃圾邮件轰炸时,几小时就能攒出几十万个小文件,直接把 inode 打满。</p>
<h3>元凶二:缓存/临时目录的小文件</h3>
<p>PHP 的 session 文件默认存在 <code>/var/lib/php/sessions/</code>,一个会话一个文件;Nginx 的 proxy_cache、fastcgi_cache 也会生成海量缓存碎片;<code>/tmp</code> 目录如果被程序滥用同样危险。这些目录是 inode 消耗的"重灾区"。</p>
<h3>元凶三:日志未轮转</h3>
<p>某些程序(尤其是自己写的脚本)往固定文件名里追加日志却从不切割,日志文件本身只有一个 inode,这倒不消耗 inode——但要注意,如果程序按时间戳每天新建一个日志文件且从不清理,几个月下来同样会累积几万个小文件。</p>

<h2>五、清理实战:找到元凶并干掉它</h2>
<p>定位哪个目录文件最多:</p>
<pre># 统计各一级目录下的文件数量(慎用,全盘扫描较慢)
for d in /*; do echo "$d: $(find $d -type f 2>/dev/null | wc -l)"; done

# 更精准:直接统计常见重灾区
find /var/spool -type f 2>/dev/null | wc -l
find /var/lib/php/sessions -type f 2>/dev/null | wc -l
find /tmp -type f 2>/dev/null | wc -l

# 找出某个目录下最老的文件,确认是否还能清理
find /var/lib/php/sessions -type f -mtime +7 | head -20</pre>
<p>确认后按需清理,以 session 目录为例:</p>
<pre># 删除 7 天前的 session 文件
find /var/lib/php/sessions -type f -mtime +7 -delete

# 删除邮件队列中所有退信(先备份再删更稳妥)
find /var/spool/postfix/deferred -type f -mtime +3 -delete

# 清理 /tmp 下 3 天前的临时文件
find /tmp -type f -mtime +3 -delete</pre>
<p>清理完再执行 <code>df -i</code>,确认 IUse% 降下来后,业务即可恢复写入。</p>

<h2>六、根治方案:别等爆了才处理</h2>
<h3>方案一:建分区时规划好 inode 数量</h3>
<p>对于已知会存海量小文件的分区,格式化时可以显式指定 inode 密度:</p>
<pre># ext4:每 8KB 数据分配一个 inode(默认一般是 16KB)
mkfs.ext4 -i 8192 /dev/sdb1

# xfs:按空间比例指定 inode 数量(每 512B 一个 inode 块)
mkfs.xfs -i size=512 -d agcount=4 /dev/sdb1</pre>
<h3>方案二:把"小文件重灾区"单独分区</h3>
<p>把 session、缓存、邮件队列这类目录单独挂一个分区,并配大 inode 密度,即使爆了也只影响该分区,不会拖垮根分区和业务数据。</p>
<h3>方案三:定时巡检脚本</h3>
<pre>#!/bin/bash
# /usr/local/bin/check_inode.sh
USE=$(df -i / | awk 'NR==2 {print $5}' | tr -d '%')
if [ "$USE" -gt 85 ]; then
echo "$(date '+%F %T') 警告: 根分区 inode 使用率已达 ${USE}%" >> /var/log/inode_warn.log
# 可在此追加告警:mail/sms/webhook
fi</pre>
<p>配合 crontab 每 10 分钟跑一次,做到"爆之前先报警"。</p>

<h2>七、小结</h2>
<p>inode 耗尽属于典型的"低频高杀伤"故障:平时遇不到,一遇到就是生产事故。核心记住三点:<strong>df -i 看 inode、小文件目录是重灾区、格式化时规划好 inode 密度</strong>。把这套排查思路记熟,下次再看到"No space left on device"就能三分钟定位、十分钟解决。</p>

文中这类批量操作,我们平时是用自研的[七亿批量远程管理工具](https://blog.7ehl.com/index.php/archives/3/)做的,免费,支持 RDP / SSH / SFTP / FTP。

另外,云主机我们也做(韩国、香港机房),有需要的老板可以瞅一眼:[www.7ehl.com](https://www.7ehl.com/)。