在搭建反向代理或正向代理服务时,Apache的代理缓存功能可以把后端响应暂存到本地磁盘,从而提升响应速度并降低上游负载。但当缓存内容以原始未压缩形态落盘,特别是包含了大量文本、脚本、样式表等资源时,磁盘占用会迅速膨胀。通过把压缩机制嵌入缓存流程,Apache能够在存储阶段直接写入压缩后的数据,从而显著减少缓存目录的体积,同时不影响客户端获取内容。

Apache代理缓存与压缩的协作原理
Apache实现代理缓存主要依赖mod_proxy、mod_cache以及具体的缓存后端如mod_cache_disk。当请求经由代理转发到后端,响应返回后,如果开启了缓存,模块会根据缓存规则决定是否保存。传统配置中,很多管理员只启用了磁盘缓存,却没有让响应经过压缩过滤器,结果保存的就是后端吐出的原始字节。对于HTML、CSS、JavaScript等文本类资源,未压缩与压缩的体积差距往往在三倍到五倍之间。
要让缓存落盘即是压缩形态,必须在过滤器链中正确排列mod_deflate。Apache的处理阶段里,压缩属于输出过滤器,而缓存模块在保存响应体时,实际保存的是经过输出过滤器处理后的内容。因此只要在代理响应出口处启用DeflateCompressionLevel并设置合适的AddOutputFilterByType,缓存模块写盘时拿到的就已经是gzip或deflate后的字节流。这里需要注意,压缩动作发生在缓存存储之前,而不是客户端请求命中后再压缩,所以空间节省是持久性的。
另一个关键点是内容协商。如果后端本身返回了Content-Encoding: gzip,Apache通常不会再次压缩,而是原样缓存这份已压缩数据。如果后端返回未压缩内容,而代理侧启用了deflate,则会压缩后存储。为了避免重复压缩或错误解压,应当通过CacheIgnoreHeaders谨慎处理编码相关头,并确认mod_cache不会因Vary头过于复杂而拒绝缓存。理解这条过滤器链,是节省空间的基础。
具体配置示例与代码说明
下面给出一段典型的Apache配置,展示如何为磁盘代理缓存开启压缩存储。该配置假设使用基于磁盘的缓存,并针对常见文本类型启用deflate。我们将缓存目录放在/var/cache/apache2/proxy,并限制最大体积以防止无限增长。
<IfModule mod_cache.c>
CacheEnable disk /
CacheRoot /var/cache/apache2/proxy
CacheDirLevels 2
CacheDirLength 1
CacheMaxFileSize 10485760
CacheMinFileSize 1
</IfModule>
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/html text/plain text/css
AddOutputFilterByType DEFLATE application/javascript application/json
DeflateCompressionLevel 6
</IfModule>
<IfModule mod_proxy.c>
ProxyRequests Off
ProxyPass /api/ http://127.0.0.1:8080/api/
ProxyPassReverse /api/ http://127.0.0.1:8080/api/
</IfModule>
在上述配置中,CacheEnable disk /开启了针对根路径的磁盘缓存;AddOutputFilterByType把deflate过滤器绑定到多种文本 MIME 类型上。由于代理响应在返回客户端前会经过输出过滤器,因此写入缓存的文件已经是压缩状态。我们可以通过命令行查看缓存目录中的文件大小,对比未开启deflate时的体积,通常能发现明显缩小。
有一点容易被忽略:如果后端已经启用了压缩,而代理又再次添加deflate,可能导致数据被压缩两次,客户端解压一次后拿到的是乱码或再次压缩流。为此可以在代理配置中使用RequestHeader unset Accept-Encoding禁止向后端发送压缩请求,让代理统一负责压缩。这样后端始终返回明文,代理压缩后缓存并响应,逻辑更清晰,也更容易估算空间收益。
空间收益对比与常见误区
我们用一组简化数据说明差异。假设某站点有五千个静态文本资源,平均原始大小约二十千字节,未压缩缓存需要约一百兆磁盘;启用等级为六的gzip后,平均大小降到五千字节左右,缓存仅占约二十五兆。对于拥有海量小文件或频繁构建缓存的代理场景,这种四分之三的空间削减直接延缓了磁盘扩容需求,也减少了inode消耗。
常见误区之一是认为mod_cache会自动压缩。实际上缓存模块只负责存储与命中,不会主动调用压缩算法,必须显式挂载deflate过滤器。其二,有人把CacheIgnoreNoLastMod或CacheDefaultExpire设置得不合理,导致大量响应因缺失过期头而不被缓存,反而觉得压缩没用。其三,误用Vary: Accept-Encoding且未正确处理,可能造成同一资源存多份不同编码副本,抵消了空间优势。正确做法是统一在代理侧压缩,并让缓存以单一压缩副本服务所有支持解压的客户端。
从运维角度看,还应定期用htcacheclean工具清理过期缓存,避免压缩节省的空间被陈旧文件慢慢占回。结合监控脚本统计缓存目录大小与命中率,可以直观验证压缩存储带来的收益。当代理承载的内容以文本为主时,这套方案几乎是无代价的优化,既降低磁盘压力,也因响应体变小而减少了出向带宽。