HTTP压缩是降低网络传输开销的核心手段,但大多数站点只使用通用压缩算法,比如gzip或Brotli的默认参数。当Apache作为反向代理缓存大量API响应或动态页面时,内容之间往往存在高度相似的字段名、固定头部和重复文本片段。如果能为这些内容训练一个专属的共享字典,压缩算法就能用更短的索引指向这些常见模式,从而获得远高于默认字典的压缩率。本文聚焦于如何在Apache代理缓存场景下训练并使用自定义压缩字典,让缓存内容以更小的体积、更快的速度返回给客户端。

压缩字典训练的原理与价值
Brotli和Zstandard这类现代压缩算法支持在压缩和解压时加载一个外部字典。字典本质上是常见字符串或字节序列的集合,算法在编码数据时,如果发现输入片段与字典中的某个条目匹配,就可以输出一个指向该条目的短引用,而不是把完整内容再写一遍。对于通用文件,内置字典已经足够好;但对于特定业务域的数据,例如电商商品的JSON响应、日志系统的固定格式文本,训练出来的字典能把那些高频出现的键名、单位、标签等压缩成极短的符号。
在代理缓存环境中,这一优势被进一步放大。Apache代理通常缓存了大量来自同一套后端服务的响应,这些响应的结构非常稳定,差异只在少量动态数值上。如果我们针对这些缓存内容训练一个字典,并让所有客户端在解压时使用同一个字典,那么每个响应都可以享受字典带来的额外压缩收益。实测中,针对某一类API响应训练字典后,Brotli压缩体积可比默认参数缩小20%以上,而Zstandard使用训练字典甚至能逼近或超过Brotli默认压缩的效果。
不过,字典训练并非免费午餐。字典必须与压缩数据一起被客户端获知,否则解压会失败。在HTTP场景下,这通常通过一个共享的字典URL来实现,或者由服务端在响应头中声明字典标识。对于Apache代理来说,我们需要把训练好的字典文件放在可访问的位置,并保证代理缓存的变体与字典版本严格对应。下面先介绍如何得到这样一个字典。
训练自定义压缩字典的实操步骤
训练字典的第一步是收集代表性样本。样本应该覆盖代理缓存中占比最大的内容类型,并且数量要足够多,通常建议至少几百个文件,大小从几KB到几百KB不等。这些样本可以是日志文件中导出的响应体,也可以是从缓存目录中提取的典型对象。以Brotli为例,其官方提供了brotli命令行工具,其中包含--dictionary相关选项,但更推荐使用独立的字典训练工具brotli-dict或者用Zstandard自带的zstd --train命令。
对于Zstandard,训练命令非常简单。假设样本文件都放在samples目录下,执行以下命令即可生成一个字典文件:
zstd --train samples/* -o my_dict.zdict
如果样本数量多且体积大,可以调整--maxdict参数控制字典大小,一般64KB到1MB比较合适。训练完成后,可以用zstd -D my_dict.zdict测试压缩效果,对比不使用字典时的压缩率。对于Brotli,官方仓库提供了brotli工具,但字典训练需要额外编译dictionary子项目,或使用第三方脚本。一个更简单的替代方案是先用Zstandard训练字典,再用支持Brotli自定义字典的库(如brotli-node或python-brotli)进行压缩。
训练字典后,需要将其部署到客户端能访问的位置。在Apache代理场景中,我们可以把字典文件放在一个静态URL下,例如/dict/my_dict.zdict,然后在响应头里声明这个字典的地址。不过,更常见的做法是在服务端完成压缩,把压缩结果和字典一起缓存起来,客户端通过HTTP的Content-Encoding和Dictionary相关扩展头(如RFC 8871的Content-Dictionary)识别。Apache目前对自定义字典的直接支持有限,但我们可以通过预压缩文件的方式间接实现。
在Apache代理缓存中启用预压缩与字典复用
Apache的mod_brotli模块本身并不支持加载外部字典进行动态压缩,它只会使用内置字典。要实现字典复用,最实用的方案是“预压缩 + 扩展名协商”:在后端服务或CI流程中,用训练好的字典对响应内容进行预压缩,生成.br或.zst文件;Apache通过mod_negotiation或RewriteRule识别这些预压缩变体,并让代理缓存存储它们。这样,缓存中保存的就是已经使用自定义字典压缩过的二进制内容,客户端请求时直接返回,无需Apache再执行压缩。
首先,在Apache配置中启用类型映射和重写规则。假设静态文件目录为/var/www/cache,其中原始文件为data.json,预压缩Brotli文件为data.json.br。可以使用以下配置片段:
Options +MultiViews
AddEncoding br .br
AddType application/json .json
RewriteEngine On
RewriteCond %{HTTP:Accept-Encoding} br
RewriteCond %{REQUEST_FILENAME}.br -f
RewriteRule ^(.*)$ $1.br [L,T=application/json,E=no-gzip:1]
Header set Content-Encoding br env=no-gzip
Header set Vary Accept-Encoding这段配置的核心是当客户端支持Brotli且存在对应的.br文件时,把请求重写到预压缩文件,并设置正确的Content-Encoding。同时,代理缓存需要配置为区分不同编码的变体。在mod_cache的配置中,必须确保CacheVaryByHeaders包含Accept-Encoding,否则同一URL的不同压缩版本会互相覆盖。例如:
CacheEnable disk / CacheRoot /var/cache/apache2/mod_cache_disk CacheVaryByHeaders Accept-Encoding
如果还涉及到自定义字典版本,还需要把字典标识也加入Vary头。可以在响应头中设置Content-Dictionary指向字典文件的URI,并把该头也加入CacheVaryByHeaders。这样当字典更新时,缓存系统会自动生成新的缓存变体,而不会污染旧的缓存条目。
字典版本管理同样重要。一旦训练数据发生变化,比如后端API字段调整,旧字典对新内容的压缩收益就会下降,甚至可能因为字典不匹配而导致解压错误(尽管现代压缩算法会回退到无字典模式,但性能会受影响)。建议为字典文件使用版本号命名,例如my_dict_v1.zdict,并在预压缩文件中记录使用的字典版本。在部署新字典时,同步更新响应头中的字典标识,并触发缓存清理或版本化URL。
最后,对于不同类型的缓存内容,比如JSON API响应和HTML页面,它们的结构差异很大,使用同一个字典收益有限。实践中可以根据内容类型拆分训练样本,分别训练多个字典。Apache代理可以根据URL路径或响应Content-Type来路由到不同的预压缩目录,每个目录对应一个字典版本。这样既保证了压缩率最大化,又避免了跨类型内容污染字典带来的副作用。
Apache代理缓存压缩字典训练Brotli压缩修改时间:2026-08-26 16:45:17