压缩该在程序里做还是 Web 服务器里做,静态文件要不要预压缩
页面和静态资源的压缩,可以在内容管理程序里开启,也可以交给 Web 服务器,还可以把两者接成一条链路。判断点不是哪一层更「高级」,而是压缩开销落在什么时候:动态压缩每次请求都要重新算,预压缩把开销挪到发布阶段,代价是要维护两套文件的同步。
两种做法的开销落在什么时候
交给 Web 服务器做动态压缩时,压缩动作发生在响应生成的路上。nginx 的 gzip 模块本身就是一个以 gzip 方法压缩响应的过滤器,同一份内容被访问多少次就压多少次。并发请求密集时,这部分 CPU 会先抢出来。
预压缩走的是另一条路:文件在构建或发布时就压好,服务器只需要判断客户端支不支持,然后直接发送已有的压缩文件。开销从请求路径前移到发布路径,代价是发布流程要多做一步,并且要保证两套文件是同一版本。
最小长度与类型清单为什么影响收益
动态压缩的收益受两个开关限制。一是长度门槛:nginx 的 gzip_min_length 默认取 20,而且长度只从响应的 Content-Length 头判断——如果响应是分块输出、没有这个头,这一层判断就落空。太小的响应压缩反而多耗 CPU,所以门槛该留,但要理解它按什么判断。
二是类型清单:gzip_types 默认只含 text/html,脚本、样式、接口 JSON 这些要显式加进清单才会被压。清单配错的典型现象是页面小了、静态资源却没变。压缩级别 gzip_comp_level 的取值范围是 1 到 9、默认为 1,级别越高耗时越多,收益并不线性。
还有一项容易漏:gzip_vary 默认是 off,开启后会插入 Vary: Accept-Encoding。前面有缓存层时这个头很重要,否则共享缓存可能把压缩版发给不支持的客户端。
预压缩文件的命名与同步要求
nginx 的 gzip_static 模块允许直接发送带 .gz 扩展名的预压缩文件来代替常规文件。开关有三种取向:on 会检查预压缩文件是否存在,always 则不判断客户端支持情况一律用压缩文件——后者只适合确定所有访问方都支持的场景。
同步要求是这一步的主要风险:官方建议原文件与压缩文件的修改日期和时间保持一致。如果只更新了原文件没重压,客户端拿到的是旧内容的压缩版本,这类问题排查起来很不直观。发布脚本里把「生成 → 压缩 → 更新时间」绑成一步,比事后补救可靠。
两层怎么配合
| 环节 | 谁来做 | 适合的对象 | 注意点 |
|---|---|---|---|
| 动态压缩 | Web 服务器 | 每次都不一样的页面响应 | 类型清单要补全,长度判断依赖 Content-Length |
| 预压缩 | 发布或构建流程 | 静态资源、生成后的页面文件 | 原文件与 .gz 的修改时间保持一致 |
| 可复用页面响应 | 缓存与发布层 | 文章页、列表页这类两次访问内容相同的页面 | 伪静态规则与文件路径要对得上 |
| 传输开销 | 全站 | 页面体积直接影响加载速度 | 高并发时优先把重复压缩的开销挪出请求路径 |
这一层分工的依据很直白:同一篇文章的页面在两次访问之间通常没有变化,每次访问都重新压一遍就是白花 CPU。这类可复用响应交给预压缩或缓存层,动态压缩留给无法预先确定的响应,是比较稳的配法。
反代与缓存层还要补什么
前面挂了反向代理或 CDN 时,要确认压缩结果按客户端能力正确留存,也就是前面提到的 Vary 头。用 Docker 镜像或宝塔面板部署的站点,配置改动往往分散在容器环境、服务器块与面板设置三处,改完要逐层确认是否生效,别只看其中一层。
常见问题
问:程序里已经开了压缩,服务器层还要开吗? 答:看响应是谁生成的。两层都开可能对同一内容压两次,通常保留一层负责动态响应、另一层负责静态文件。
问:压缩级别调高会不会更省带宽? 答:级别与耗时同时上升,收益不线性。默认级别配合合理的类型清单,通常比单纯拉高级别更划算。
问:怎么确认客户端真的收到压缩版本? 答:看响应的编码标识与体积对比,同时用不支持压缩的请求头再发一次做对照。