"磁盘满了"大家都熟悉,但有一种更隐蔽的"满":df -h 显示磁盘才用了 30%,可用空间一大把,可系统就是写不进去文件,报 No space left on device。这时候十有八九是 inode 用完了。它在容量层面看起来完全正常,让人一头雾水,却是缓存服务器、邮件服务器、日志密集业务里非常高频的故障。
这篇文章讲清楚四件事:inode 到底是什么、怎么判断是不是 inode 耗尽、怎么一步步找出"谁吃掉了 inode"、以及怎么安全清理并防止复发。重点是排查方法——因为 inode 耗尽的现场往往有几十万甚至上百万个小文件,用 du 一个个目录翻是低效且容易翻车的,必须用正确的命令按数量排序定位。
先建立一个核心直觉:inode 记录的是"文件有多少个",磁盘容量记录的是"文件加起来多大"。这是两个独立耗尽的维度。 一个只有几字节的临时文件,占用的 inode 和一个几 GB 的电影文件一样多。所以一个小文件泛滥的目录,可能体积很小却把 inode 吃光。
一、inode 是什么,为什么会耗尽
在 ext4/XFS 这类文件系统里,每个文件(以及目录、符号链接等)都对应一个 inode(索引节点)。inode 里保存的是文件的元数据:权限、属主、大小、时间戳、以及数据块的位置指针。文件名本身存在目录项里,指向 inode 编号。
关于 inode,要记住三个关键事实:
第一,inode 数量通常在文件系统创建(mkfs)时就固定了。ext4 默认大约每 16 KB 空间分配一个 inode,也就是说一块 100 GB 的 ext4 大约有 650 万个 inode(经验值,随块大小和 -i 参数变化)。XFS 是动态分配 inode 的,相对不容易耗尽,但在极小容量、超大数量的场景下同样会撞上限。
第二,inode 的消耗与文件大小无关,只与文件数量有关。这是它和"容量满"最本质的区别。
第三,删除文件会释放 inode,但如果文件被进程打开(lsof 能看到 deleted),则 inode 不会立即释放,直到进程关闭文件句柄。
常见的 inode 消耗大户:
| 场景 | 典型目录 | 特征 |
|---|---|---|
| PHP/框架 Session | /var/lib/php/sessions |
每个会话一个文件,清理不及时 |
| 邮件队列 | /var/spool/postfix、/var/mail |
大量小文件堆积 |
| 缓存目录 | /var/cache/*、应用缓存 |
一个 key 一个文件 |
| 图片缩略图 | 应用 upload/cache 目录 | 海量小图 |
| 日志分片 | 按天/按请求切分的日志 | 单目录几十万文件 |
| Git 仓库 | .git/objects |
对象文件极多 |
| 临时文件 | /tmp、/var/tmp |
长期不清理 |
二、确认是不是 inode 耗尽
# 1. 查看 inode 使用情况(关键命令,-i 参数)
df -i
# 只看某个挂载点
df -i /data /var
# 2. 典型输出解读:
# Filesystem Inodes IUsed IFree IUse% Mounted on
# /dev/vdb1 6553600 6553600 0 100% /data <-- IUse% 100% 就是耗尽了
只要 IUse% 接近或达到 100%,而 df -h 的容量还有富余,就可以确定是 inode 耗尽。这时 df -h 的容量数字不用再看,它和问题无关。
进一步确认是哪一类文件导致,可以用 find 统计单目录文件数:
# 统计某个目录下的文件总数(不含子目录递归)
find /data -maxdepth 1 -type f | wc -l
# 递归统计文件总数(大目录会很慢,慎用)
find /data -type f | wc -l
# 统计某目录下直接子目录各自有多少文件(用于定位"大户")
for d in /data/*/; do printf "%8d %s\n" "$(find "$d" -type f 2>/dev/null | wc -l)" "$d"; done | sort -rn | head
三、找出"谁吃掉了 inode"
du(按体积排序)在这里没用,因为 inode 消耗看的是文件数量。正确的方法是按文件数量排序。
# 全盘扫描:按文件数量倒序列出各目录(从根开始,会比较慢)
sudo find / -xdev -type f 2>/dev/null | sed 's#/[^/]*$##' | sort | uniq -c | sort -rn | head -30
# 更实用的做法:先定位到具体的挂载点(比如 /var),逐层下钻
# 第一层:/var 下各子目录的文件数
sudo find /var -maxdepth 1 -type d -exec sh -c 'echo "$(find "$1" -type f 2>/dev/null | wc -l) $1"' _ {} \; | sort -rn
# 锁定到某一层后继续下钻,例如 /var/lib
sudo find /var/lib -maxdepth 1 -type d -exec sh -c 'echo "$(find "$1" -type f 2>/dev/null | wc -l) $1"' _ {} \; | sort -rn | head
# 找出单个目录里文件最多的(更精确的表达)
sudo find / -xdev -type f -printf '%h\n' 2>/dev/null | sort | uniq -c | sort -rn | head -20
find / -xdev 里的 -xdev 很重要:它表示"不跨越文件系统边界",避免扫到网络挂载或其它分区,既快又准。
如果服务器性能一般、文件数量以百万计,全盘 find 可能跑很久。这时优先从"已知的高危目录"入手:/var/spool、/var/lib/php、/tmp、/var/cache、应用自身的 cache 目录。
另外一个强力工具是 du 的 inode 统计:
# 按 inode 数量统计目录(某些版本 du 支持 --inodes)
sudo du --inodes -d 1 /var 2>/dev/null | sort -rn | head
四、安全清理小文件
找到大户之后,清理要格外小心。这里有个最容易出人命的坑:用 rm -rf /path/* 删除百万级文件时,rm 会把所有文件名展开后逐个删除,参数列表可能超出 ARG_MAX 限制而报错,或者耗时极长。
# 错误示范:目录下文件极多时可能报 "Argument list too long"
rm -rf /var/cache/app/*
# 正确做法一:用 find 分批删除,最稳
sudo find /var/cache/app -type f -atime +7 -delete
# 正确做法二:用 find -exec 分批(配合 + 表示尽可能批量)
sudo find /var/cache/app -type f -atime +7 -exec rm -f {} +
# 正确做法三:当目录本身要整体清空时,删目录重建比删文件快得多
# 先确认目录属性(属主、权限、SELinux 上下文),再操作
ls -ld /var/cache/app
sudo rm -rf /var/cache/app
sudo mkdir /var/cache/app
sudo chown www-data:www-data /var/cache/app # Debian/Ubuntu
# sudo chown nginx:nginx /var/cache/app # RHEL/CentOS/Rocky
# 如有 SELinux,恢复上下文:
# sudo restorecon -Rv /var/cache/app
按时间清理是最常用的策略。-atime +7 表示访问时间在 7 天前,-mtime +7 表示修改时间在 7 天前。缓存类文件用 -atime,日志类用 -mtime。
# 清理 /tmp 下 1 天前未访问的临时文件
sudo find /tmp -type f -atime +1 -delete
# 清理 PHP session(先停应用或用 systemd-tmpfiles 更规范)
sudo find /var/lib/php/sessions -type f -atime +1 -delete
# 清空目录内容但保留目录本身(注意 rm -rf 后必须有空格和点)
sudo find /var/log/app -mindepth 1 -delete
风险提示:
find ... -delete和rm -rf都是不可逆操作,删掉的文件无法找回。执行前务必:① 先用find不带-delete跑一遍,把要删的文件列出来确认;② 确认路径没有拼写错误(尤其别把/var/cache写成/var);③ 生产环境先备份或做快照。回滚方式只有备份,rm本身没有回收站。
稳妥的确认流程:
# 第一步:先列出会被删的文件,人工确认
sudo find /var/cache/app -type f -mtime +7 -printf '%TY-%Tm-%Td %s %p\n' | head -50
# 第二步:确认无误后,先删一小批试水
sudo find /var/cache/app -type f -mtime +7 | head -1000 | xargs -r sudo rm -f
# 第三步:观察应用是否正常,再执行全量删除
sudo find /var/cache/app -type f -mtime +7 -delete
五、清理已经删掉但未释放 inode 的文件
有时候你明明删了文件,df -i 却没降。原因是文件被某个进程持有句柄:
# 找出被删除但仍被占用的文件
sudo lsof +L1 | grep -i deleted
sudo lsof | grep deleted | awk '{print $1, $2, $7, $9}'
# 处理方式一:重启对应进程(最干净)
sudo systemctl restart nginx
# 处理方式二:清空文件内容而不删除 inode(对日志最有效,可在线做)
# 注意:这里用的是重定向截断,进程无需重启即可释放
sudo truncate -s 0 /proc//fd/ # 或
: > /path/to/file
用 : > file 截断日志文件是运维常识:它不改变 inode,只把文件内容清空,因此日志进程不需要重启就能继续写入,磁盘空间和 inode 都被释放。
六、从根上防止 inode 耗尽
第一,格式化时就把 inode 给足。 如果一块盘专用于海量小文件,mkfs.ext4 -i 4096 可以把 inode 密度提高约 4 倍(注意:只能格式化时设定,之后再想改必须重建文件系统)。
第二,优先选 XFS。 XFS 动态分配 inode,比 ext4 更适合小文件海量场景。
第三,建立定时清理。 用 systemd-tmpfiles 或 cron 定期清理:
# /etc/tmpfiles.d/myapp.conf 示例
# d /var/cache/app 0755 www-data www-data 7d
# 表示该目录下超过 7 天的文件会被自动清理
sudo systemd-tmpfiles --clean
# cron 示例:每天凌晨 3 点清理
# 0 3 * * * root find /var/cache/app -type f -mtime +7 -delete
sudo crontab -e
第四,给 inode 使用率配监控告警。 监控系统(Zabbix、Prometheus node_exporter 的 node_filesystem_files_free 指标)要单独采集 inode 指标,阈值建议 IUse% > 80% 就告警,而不是只盯着磁盘容量。
七、不同业务场景的处理重点
inode 耗尽不是一个孤立问题,不同业务背后的"病因"不一样,处理策略也应该有差别。
PHP/Java Web 应用:最常见的元凶是 session 文件和框架缓存。这类文件的特点是"每个用户/每个 key 一个文件、生命周期短、清理依赖代码或定时任务"。一旦定时清理失败(比如 cron 没跑、或清理脚本报错无人知晓),文件就会无声无息地堆积。处理重点是恢复定时清理,并给 session 目录设置合理的过期时间。
邮件服务器:/var/spool/postfix 下的队列文件和 /var/mail 下的收件箱会随邮件量增长。如果邮件发送失败重试,队列文件会成倍堆积。这类场景光删文件不够,要同时排查为什么邮件发不出去(DNS、反垃圾策略、目标拒收),否则清理完很快又满。
日志密集型业务:按小时或按请求切分的日志会制造海量小文件,尤其是当应用把日志命名成"包含时间戳+随机数"时。正确做法是从应用侧改成按天切分并配合轮转压缩,而不是事后清理。
Git 仓库 / 代码托管:.git/objects 下的松散对象文件数量会随提交次数迅速膨胀。有效的做法是定期执行 git gc(或让服务端自动 gc),把大量小对象打包成大文件,既省 inode 又提升性能。
容器与 CI 环境:构建缓存、镜像层、临时工作目录会在反复构建中累积。这类环境往往磁盘不大、inode 很少,最容易撞上限,建议给构建目录单独挂载一块容量和 inode 都充足的盘,并定期整体重建。
归纳下来,处理 inode 耗尽有一条通用路径:先按文件数量定位到具体目录,再判断这些文件是"该清、该改还是该迁"。临时缓存类直接清;产生方式不合理的从源头改;确实需要长期保留海量小文件的,就迁到 XFS 或专门的存储上。单纯反复手动删除只是治标,找到产生机制并加上自动清理和告警才是治本。
常见问题
Q1:df -h 没满但写不进文件,一定是 inode 吗?
大概率是,先跑 df -i。但要排除另外两种情况:一是磁盘配额(quota)用尽,二是文件被删除但句柄未释放。两个都用 lsof +L1 和配额命令排查。
Q2:能在线增加 ext4 的 inode 数量吗?
不能。ext4 的 inode 数量在创建时确定,无法在线扩容。只能新建文件系统、迁移数据。
Q3:删了文件 inode 没降怎么办?
说明文件被进程打开着,用 lsof +L1 找到持有者,重启进程或用 truncate 清空。
Q4:find / -type f 太慢怎么办?
加 -xdev 限制不跨文件系统,并从已知高危目录(/var/spool、/var/cache)优先排查,不要一上来全盘扫。
Q5:XFS 会耗尽 inode 吗?
会,但概率比 ext4 低得多,因为 XFS 动态分配。不过它有一个"inode 空间"上限受整体容量影响,极小盘 + 极多小文件时同样会满。
总结
- inode 耗尽和磁盘容量满是两回事:
df -h正常但报No space left on device时,第一时间跑df -i。 - 定位靠"按文件数量排序",不用
du按体积排,用find ... -printf '%h\n' | sort | uniq -c | sort -rn逐层下钻。 - 清理百万级文件别用
rm -rf dir/*,用find -delete分批,先列后删,先试一小批。 - 文件删了 inode 不降,是进程还持有句柄,
lsof +L1定位后用truncate或重启释放。 - 预防的三板斧:格式化时给足 inode、用 XFS、配置定时清理和 inode 使用率告警。
- 所有删除操作前都要先确认路径、先备份,
rm没有回收站,删错了只能靠备份。
企业QQ咨询




