Apache 的 mod_cache 配合 mod_cache_disk 可以把后端响应缓存到本地磁盘,对静态资源或半动态页面的加速效果非常明显。但不少运维人员在上线缓存之后遇到了一个奇怪的故障:磁盘明明还有几百 GB 剩余空间,系统却频繁报 No space left on device,创建文件全部失败。用 df -i 一查才发现,inode 已经 100% 用完了。这个问题的根源往往不在 Apache 本身,而在缓存存储路径的目录结构设计上。这篇文章就来深入聊聊 inode 的工作机制、Apache 缓存目录的哈希散列原理,以及如何通过调整参数和路径规划来优化。

一、先弄懂 inode 为什么会被耗尽
Linux 文件系统里,每一个文件(包括目录)都要占用至少一个 inode。inode 存储的是文件的元数据:权限、属主、时间戳、数据块指针等。文件系统格式化时 inode 数量就固定下来了,以 ext4 为例,默认大约每 16KB 数据空间分配一个 inode。也就是说,一个 1TB 的分区默认有大约 6000 万个 inode,看起来很多,但对缓存场景来说未必够用。
问题出在缓存文件的特殊性。mod_cache_disk 存的缓存对象通常只有几 KB 到几十 KB,极端情况下小于 1KB 的文件也不少见。文件越小,磁盘空间消耗越慢,而 inode 消耗越快。如果后端响应量大,比如图片服务、API 接口缓存,一天写入几十万甚至上百万个小文件完全可能。跑上几周之后,inode 就被这些小文件吃光了,此时磁盘空间可能还剩 80% 以上,这种"有空间没 inode"的故障排查起来非常反直觉。
查看和监控 inode 可以用以下命令:
# 查看各分区的 inode 使用情况 df -i # 查看某个目录下的文件总数 find /var/cache/apache2/mod_cache_disk -type f | wc -l # 找出当前目录下文件数最多的子目录 du --inodes -d 2 /var/cache | sort -rn | head -20
建议把 inode 使用率纳入监控告警,阈值设到 80% 比较稳妥,不要等故障发生才去排查。另外要注意,某些应用(比如 Docker、邮件队列)也会大量消耗 inode,排查时要先确认到底是哪个目录在占用,不要想当然地直接清理。
二、Apache 缓存目录的哈希散列机制
mod_cache_disk 不会把所有缓存文件平铺在一个目录里,因为单个目录下文件数一旦超过几万,文件系统查找会明显变慢,ext4 用哈希索引目录后情况有所改善,但过大的目录在删除、遍历时依然很痛苦。所以 Apache 采用哈希散列的方式,把缓存 URL 对应的键做 md5 运算,然后按字符切分成多级子目录存放。
控制这个行为的指令有两个:CacheDirLevels 定义目录层级数,CacheDirLength 定义每级目录取多少个字符。默认值是 2 和 2,也就是两级目录,每级用 md5 值的 2 个字符命名。配置示例:
# 启用磁盘缓存
LoadModule cache_module modules/mod_cache.so
LoadModule cache_disk_module modules/mod_cache_disk.so
<IfModule mod_cache_disk.c>
CacheEnable disk "/"
CacheRoot "/var/cache/apache2/mod_cache_disk"
CacheDirLevels 2
CacheDirLength 2
CacheMaxFileSize 1000000
CacheMinFileSize 1
</IfModule>
按默认配置,URL 键的 md5 值前 2 个字符作为第一级目录,接下来 2 个字符作为第二级目录,剩下 28 个字符作为文件名。这意味着最多有 16 的 4 次方,也就是 65536 个叶子目录,理论上每个叶子目录平均存放的文件数等于总缓存文件数除以 65536。如果缓存总量达到 500 万个文件,平均每个目录仍有约 76 个文件,尚可接受;但如果缓存规模到几千万级别,单目录文件数就会膨胀,目录查找和 htcacheclean 清理都会变慢。
这里有一个非常关键的坑:修改 CacheDirLevels 或 CacheDirLength 之后,旧缓存文件的存放路径会全部失效。因为哈希切分方式变了,Apache 按新的目录结构去找旧文件,找不到就等于缓存全部作废,只能等它过期后由清理工具慢慢删掉。所以调整参数前要评估影响,最好安排在低峰期,并配合 htcacheclean 手动清理一遍旧缓存。一般推荐的组合是 levels 取 3、length 取 2,或者 levels 取 2、length 取 3,总散列字符数达到 6,可以支撑 1600 万以上的叶子目录数,足以应对绝大多数场景。
三、存储路径规划与清理策略
缓存目录放在哪个分区也很有讲究。最忌讳的是把缓存路径和系统根分区放在一起,缓存爆炸时把根分区的 inode 拖垮,整个系统都会出问题。推荐单独划分一个分区或者至少挂载一个独立磁盘作为缓存盘,比如挂到 /data/cache。格式化这个分区时还可以针对性调整 inode 密度,如果明确知道要存大量小文件,可以用 mkfs.ext4 -i 8192 让每 8KB 空间分配一个 inode,inode 总量翻倍。
清理方面,Apache 自带的 htcacheclean 是标准工具,它可以按总缓存大小做限制,持续后台运行:
# 前台运行,限制缓存总大小为 10G htcacheclean -p /data/cache/mod_cache_disk -l 10G # 后台守护进程模式,每 30 分钟检查一次,限制 20G,控制 inode 友好 htcacheclean -d30 -p /data/cache/mod_cache_disk -l 20G -t -i # 参数说明: # -i 即 idle 模式,只在 Apache 空闲时清理,减少对线上请求的影响 # -t 附带清理缓存中的变体文件
更省心的方式是用 systemd 托管守护进程,写一个 service 单元让 htcacheclean 常驻,开机自启。此外还可以配合 cron 定期统计缓存文件总数,超过预警值就主动调低缓存保留时间或加大清理力度。
四、从架构层面减少小文件数量
如果后端资源本身就是海量碎片化小对象,光靠调整目录层级只是缓解,治本还要从缓存粒度入手。第一步是利用 CacheMaxFileSize 和 CacheMinFileSize 过滤极端文件:小于 1KB 的文件缓存收益本来就低,每次请求都要做磁盘读写和元数据操作,得不偿失,直接放行回源反而更划算;过大的文件则交给 CDN 或对象存储处理。
第二步是考虑换用内存缓存。mod_cache_socache 把缓存放在共享内存里,完全绕开了文件系统和 inode,响应速度也更快,适合缓存条目在几百万以内、服务器内存充裕的场景。配置改动很小:
LoadModule cache_module modules/mod_cache.so
LoadModule cache_socache_module modules/mod_cache_socache.so
LoadModule socache_shmcb_module modules/mod_socache_shmcb.so
<IfModule mod_cache_socache.c>
CacheEnable socache "/"
CacheSocache "shmcb:/var/run/apache2/cache_shmcb(512000000)"
CacheSocacheMaxSize 102400
</IfModule>
第三步,如果缓存规模真的到了千万级,认真评估一下是否该在前置一层引入 Varnish 或 Nginx proxy_cache。Nginx 的缓存同样基于 md5 散列目录,参数思路一致(levels=2:2),但整体内存占用和清理机制更轻量,很多时候和 Apache 混合部署各取所长是更合理的选择。
总结一下,优化 Apache 缓存的 inode 消耗,核心就三件事:理解哈希目录结构并合理设置 CacheDirLevels 与 CacheDirLength,把缓存隔离到独立分区并管控好清理节奏,以及在架构层面控制缓存文件的粒度和总量。三管齐下,inode 耗尽的问题基本不会再找上门。
Apache缓存inode优化mod_cache配置修改时间:2026-09-05 04:46:37