Apache作为反向代理或内容缓存服务器时,mod_cache模块会把后端返回的静态内容缓存在本地磁盘上,再次收到相同请求时直接从磁盘响应,省去了一次回源开销。但缓存到底该分配多大的磁盘空间,很多资料只是笼统地说“视情况而定”,缺少可操作的量化方法。本文给出一套可直接套用的容量规划公式,并说明每个参数如何取值、如何用实际访问数据校验结果。

缓存容量的核心计算公式
缓存容量的本质是:在给定缓存对象数量的前提下,这些对象占用磁盘空间的总和,再乘以一个安全系数。基础公式可以写成:
# 基础容量公式 CacheSize = N × (S + M) × K # N:缓存对象数量(个) # S:缓存对象的平均正文大小(KB) # M:每个对象的元数据开销(约1KB,含头部文件与目录项) # K:安全系数,一般取1.2到1.5
这个公式的关键在于N怎么估。N并不是站点上的文件总数,而是“在缓存有效期窗口内可能被访问的不同URL数量”。举个例子,一个资讯站点每天约50万个不同URL被访问,HTML页面平均大小40KB,图片等静态资源平均120KB,混合平均取80KB,那么按一天的缓存窗口计算:500000 × (80 + 1) × 1.3,约等于5265万KB,也就是50GB左右。如果要保证两天的窗口不被淘汰,就翻倍到100GB。
安全系数K不是随意的。磁盘缓存除了正文,还会生成header文件和body文件两份存储,加上文件系统的块对齐损耗(一个10字节的文件也占用4KB的块),实际占用往往比理论值高20%以上。生产环境建议从1.3起步,观察一段时间磁盘使用率后再微调。
命中率目标与缓存窗口的关系
容量规划不能脱离命中率谈。缓存的字节命中率和缓存窗口长度近似满足“容量越大、窗口越长、命中率越高”的关系,但边际收益递减明显。通常的经验是:容量覆盖最近1天热点内容时命中率可达70%左右,覆盖3天大约到85%,继续加到7天可能只提升到90%。因此先定目标命中率,再反推缓存窗口,最后算容量,比拍脑袋给磁盘大小靠谱得多。
以一个日请求量800万次、去重后不同URL约80万个的站点为例。假设目标是85%的命中率,对应需要3天窗口,那么N = 800000 × 3 = 240万个对象。如果平均对象大小100KB,容量需求为240万 × 101 × 1.3,约305GB。这个数字直接决定了磁盘采购或分区方案,如果硬件只有200GB,就要接受命中率下降,或者缩小缓存范围只缓存图片和CSS、JS这类静态资源,把动态HTML排除在外。
缩小范围是控制容量的有效手段。通过CacheEnable指令只对特定MIME类型或路径开启缓存,可以把最容易命中、体积又可控的资源放进缓存,例如:
<IfModule mod_cache.c>
CacheEnable disk "/static"
CacheEnable disk "/images"
# 动态接口不缓存,避免无谓占用空间
CacheDisable "/api"
</IfModule>
<IfModule mod_cache_disk.c>
CacheRoot "/var/cache/apache2/proxy"
CacheDirLevels 2
CacheDirLength 1
CacheMaxFileSize 1000000
CacheMinFileSize 100
</IfModule>注意配置里的CacheMaxFileSize和CacheMinFileSize,它们直接参与容量计算。设置1MB的上限意味着超大文件不进缓存,公式中的平均大小S会更稳定;设置100字节下限则过滤掉超小响应,减少元数据开销占比。这两个参数调整后,记得重新代入公式核算容量。
htcacheclean自动清理与目录结构设计
算出容量只是第一步,还要防止缓存无限增长。mod_cache_disk本身不会主动删除过期文件,必须依赖htcacheclean工具定期清理,否则磁盘迟早被写满。htcacheclean的容量参数应该设置为公式计算结果的90%左右,留出缓冲:
# 限制缓存总大小为180GB,按正文大小计算 htcacheclean -p /var/cache/apache2/proxy -l 180G -t -n -v # 推荐放到crontab中,每30分钟执行一次 */30 * * * * root htcacheclean -p /var/cache/apache2/proxy -l 180G -t -n >/dev/null 2>&1
CacheDirLevels和CacheDirLength决定了缓存文件的目录分层。默认2层、每层1个字符会产生16×16=256个子目录,每个目录平均存放近万个文件时性能就会下降。按N=240万对象计算,建议设为3层,即16×16×16=4096个目录,每个目录平均约600个文件,ext4或xfs文件系统在这个量级下都能保持良好的读写性能。层也不能太深,超过4层后路径解析开销反而上升。
另一个容易被忽视的点是inode消耗。缓存小文件时,即使磁盘空间充足,inode也可能先耗尽。用df -i命令定期检查inode使用率,格式化文件系统时如果是海量小文件场景,可以增大inode比例。如果缓存对象平均小于50KB,容量公式之外还要单独核算:inode需求约为N的1.2倍,一个默认inode数的1TB分区大约能容纳6100万个文件,通常足够,但值得验证。
用实际数据验证和迭代
公式给出的只是初始值,上线后必须用真实数据回验。Apache的日志模块可以通过%{cache-status}n变量记录每个请求的缓存状态,配置方式如下:
LogFormat "%h %U \"%{cache-status}n\" %b %D" cachelog
CustomLog logs/cache_status.log cachelog日志中的cache-status取值包括HIT(命中)、MISS(未命中并缓存)、REVALIDATED(协商缓存生效)等。用一条简单的awk命令统计命中率:
awk '{count[$3]++} END {
total=0; hit=0
for (s in count) {
total += count[s]
if (s=="HIT" || s=="REVALIDATED") hit += count[s]
}
printf "命中率: %.2f%%\n", hit/total*100
}' logs/cache_status.log如果实际命中率明显低于规划目标,先检查缓存窗口是否被htcacheclean的容量上限提前截断,再确认后端响应是否带了禁止缓存的头(例如Cache-Control: no-cache或缺失Expires)。命中率达标但磁盘利用率低于60%,说明容量给多了,可以下调htcacheclean的限制值,把空间还给其他业务。容量规划本质上是持续迭代的过程:流量增长、内容更新频率变化都会影响公式里的N和S,建议每个季度用最新的访问日志重新代入公式核算一次,让缓存配置始终贴合真实流量形态。