在CDN的完整链路中,用户请求会先到达距离最近的边缘节点,如果缓存未命中,边缘节点就必须回到源站拉取原始资源,这个过程称为回源。很多人把优化重点放在了用户到边缘节点这一段,却忽略了边缘节点到源站这一段同样要消耗带宽和时间。回源压缩就是针对这段链路的优化手段:让源站在把数据发给边缘节点之前先进行压缩,从而减少回源传输量、降低源站带宽压力、加快边缘节点拿到内容的速度。

回源压缩的工作原理与HTTP协商机制
回源压缩的核心是HTTP协议中标准的编码协商机制。当边缘节点向源站发起回源请求时,可以在请求头中携带Accept-Encoding字段,声明自己能够理解哪些压缩格式,例如Accept-Encoding: gzip, br。源站收到这个请求头后,如果支持对应的压缩能力,就会将响应内容压缩后在Content-Encoding响应头中标明实际使用的压缩算法,同时附带Vary: Accept-Encoding,告知缓存体系这份资源的不同压缩版本是有差异的。
整个流程看起来简单,但有几个细节容易被忽略。第一,回源请求的Accept-Encoding未必和用户原始请求的完全一致。一些CDN厂商允许在控制台配置回源时是否携带压缩协商头,如果配置不当,可能出现用户浏览器明明支持Brotli,边缘节点回源时却没有声明br,导致源站只返回了Gzip版本,用户侧的更优压缩格式就用不上。
第二,Vary: Accept-Encoding对缓存键的影响非常关键。边缘节点在缓存资源时,通常会把不同的Content-Encoding作为缓存变体分开存储。如果源站没有正确返回Vary头,边缘节点可能把Gzip版本的内容直接发给一个只接受br的客户端,或者更糟的情况,把压缩后的二进制数据当作未压缩内容发给不支持解压的客户端,出现乱码问题。因此在开启回源压缩之前,务必确认源站的响应头配置是完整的。
压缩算法的选择:Gzip、Brotli与Zstandard的取舍
Gzip是最经典的压缩算法,几乎所有源站软件都原生支持,兼容性最好,CPU开销也相对温和。对于以文本为主的HTML、CSS、JS内容,Gzip通常能把体积压缩到原来的三分之一左右,这对回源链路的带宽节省已经相当可观。
Brotli是Google推出的压缩算法,在文本压缩率上比Gzip高出百分之十五到二十左右,尤其在压缩级别调高时优势更明显。主流浏览器和大多数CDN边缘节点都已支持br编码。需要注意的是,Brotli的高压缩级别对CPU的消耗显著高于Gzip,源站如果配置为最高级别压缩,在大流量回源场景下可能把CPU打满,反而拖慢响应速度。实践中常用4到5级的Brotli作为平衡点。
Zstandard是近年兴起的选择,压缩速度和压缩率的平衡表现出色,特别是在动态内容回源场景下,它的高压缩速度意味着源站可以更快地完成压缩并开始传输。不过目前HTTP标准中对zstd编码的支持还在普及阶段,需要在源站和边缘节点两端都确认支持后再启用。下面的Nginx配置展示了常见的压缩配置方式:
# 源站Nginx开启gzip压缩 gzip on; gzip_comp_level 5; gzip_min_length 1024; gzip_types text/plain text/css application/javascript application/json; # 开启brotli(需要安装ngx_brotli模块) brotli on; brotli_comp_level 4; brotli_types text/plain text/css application/javascript application/json;
除了算法本身,还有一个关键判断是哪些内容值得压缩。图片、视频这类本身已经是压缩格式的文件,再压缩几乎无法减少体积,反而白白消耗CPU。因此无论在源站还是边缘节点,都应该明确指定压缩的MIME类型范围,并对小于1KB左右的响应跳过压缩,因为过小的内容压缩收益甚至抵不上压缩本身的开销和头部信息增加的体积。
边缘节点的缓存策略与二次解压问题
回源压缩拿到的是压缩内容,那么边缘节点应该缓存压缩版本还是解压后的原始内容?这是实践中最值得推敲的设计点。目前主流CDN的做法是缓存压缩版本,并根据客户端请求的Accept-Encoding匹配对应的缓存变体。这种方式的好处是边缘节点不需要消耗CPU做解压,回源一次之后,支持相同编码的用户请求可以直接命中缓存转发。
另一种思路是边缘节点回源拿到压缩内容后先解压,缓存原始内容,再根据每个用户的实际能力按需压缩。这种模式下缓存键更简单,不会因为编码差异导致缓存碎片化,命中率理论上更高,但代价是边缘节点要承担全部的压缩计算。对于命中率很高的热点资源,第一种方案明显更经济;对于命中率偏低的动态或半动态内容,两种方案的差异就要结合具体业务测算。
缓存碎片化问题在开启多编码支持后会显现出来。假设边缘节点分别缓存了Gzip和Brotli两个版本,理论上等于把同一资源的缓存空间需求放大了一倍,冷门资源可能两个版本都难以稳定命中。一些CDN通过在回源时统一请求单一编码来规避这个问题,也就是无论用户要什么格式,回源一律拿Gzip或者干脆不压缩,边缘节点拿到后再按需转换。这种策略牺牲了一点回源传输效率,换来了更高的缓存命中率,是典型的工程权衡。
最后需要提醒的是动态内容的回源压缩。对于接口类响应,边缘节点通常不缓存,每次请求都会触发回源。此时回源压缩的收益主要体现在带宽节省上,但要注意源站压缩会增加首字节的延迟,特别是大响应体在高压缩级别下,可能出现等压缩完成才开始传输的情况。Nginx的gzip模块默认会等压缩缓冲区填满才发出数据,对延迟敏感的接口可以调整gzip_buffers和gzip_comp_level参数,或者干脆对这类接口禁用压缩,改用更轻量的传输优化手段。
综合来看,回源压缩不是简单地打开一个开关,而是涉及编码协商、算法选择、缓存变体和延迟敏感度多个维度的系统性配置。建议的做法是:文本类静态资源在源站开启Gzip加Brotli双编码并正确设置Vary头,动态接口按延迟要求评估压缩级别,同时在CDN控制台确认回源时的编码协商策略与缓存变体逻辑,这样才能把回源链路的优化真正落地。