静态资源加载慢,是很多人第一次接触服务器优化时最直接的痛点。打开一个网页要等好几秒,图片转圈、样式一闪一闪、脚本半天不出来,多半不是网速真的差,而是服务器把没压缩过的文件原封不动地丢给了浏览器。HTML、CSS、JavaScript、JSON 这些文本文件,平时存着可能不大,但传成百上千个请求时,累积出来的带宽就很吓人了。Apache HTTP Server 提供了内容编码功能,可以在服务器端把文本内容压缩后再发出去,浏览器收到后自动解压还原。这套机制配置起来不复杂,收益却非常明显。
一、为什么要关心静态资源压缩
1.1 不压缩会怎样
假设一个网页要加载三个 JavaScript 文件,每个 200KB,再加上两个 CSS 文件,每个 100KB,还有一堆 JSON 接口数据。如果没有压缩,差不多就是 800KB 裸着传。传统家庭带宽如果是 20Mbps,理论下载速度也就 2.5MB/s 左右,按实际情况打个折,800KB 可能需要五六秒。这还不算图片。遇到移动网络,用户流量费用也直接跟字节数挂钩。服务器端的出口带宽同样是钱,大文件传得越多,成本越高。
1.2 压缩后的收益
文本类资源都有很高的重复模式,比如 CSS 里的选择器、JS 里的关键字、JSON 里的字段名。压缩算法能把这些重复内容用更短的标记替代。一般 HTML 能压掉 70% 左右,CSS 和 JS 也能压掉 60% 到 80%,JSON 的压缩率同样可观。也就是说,原本 800KB 的文本,压缩后可能只有 200KB 到 300KB。页面体积小了,下载时间自然缩短,用户体验会明显提升。对服务器来说,带宽压力减轻,响应速度看起来也更快了。
二、Apache 实现内容编码的基本思路
2.1 mod_deflate 模块
Apache 做内容压缩,核心是 mod_deflate 模块。它基于 zlib 库实现,支持 gzip 和 deflate 编码。现代浏览器都支持 gzip,所以默认都用 gzip。这个模块是 Apache 自带的标准模块,安装 Apache 时通常已经编译好,只是没启用。
需要先确认模块是否加载。不同系统加载方式略有区别,但主流发行版都有类似命令。
2.2 哪些资源适合压缩
不是所有文件都适合压缩。文本类资源,像 text/html、text/css、text/plain、application/javascript、application/json、application/xml、image/svg+xml,压缩效果很好。而图片里的 JPEG、PNG,视频里的 MP4、WebM,这些格式本身已经做了压缩,再压一次几乎没收益,还要白占 CPU。另外像 ZIP、GZIP 压缩包、安装包等,本身已经是压缩格式,也绝对不能重复压。所以配置时要把压缩范围限定在文本类 MIME 类型上。
三、动手配置一个可用的压缩方案
3.1 开启模块
以常见的 Linux 服务器为例,需要保证 Apache 配置中加载了 mod_deflate。可以检查模块是否存在。
# 查看 Apache 已编译/已加载的模块,并筛选 deflate
httpd -M | grep deflate
# 如果没有任何输出,说明模块未启用,需要在配置中加上下面这行
# LoadModule deflate_module modules/mod_deflate.so
3.2 基础配置示例
下面是一份最基础但已经能用的压缩配置。我们把它放在 Apache 主配置或者虚拟主机配置里都可以。技术栈:Apache HTTP Server 配置文件。
# 只有当 deflate 模块存在时,才加载下面的配置
<IfModule mod_deflate.c>
# 压缩级别设置为 6,这是性能和压缩率的平衡点
# 取值范围是 1 到 9,数字越大压缩率越高,但 CPU 消耗也越大
DeflateCompressionLevel 6
# 对常见的文本 MIME 类型启用压缩
# 注意:这里只压缩文本类,不碰图片和视频
AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css text/javascript
AddOutputFilterByType DEFLATE application/javascript application/json application/xml
AddOutputFilterByType DEFLATE application/rss+xml application/atom+xml image/svg+xml
</IfModule>
AddOutputFilterByType 是核心指令,它告诉 Apache:当响应的 Content-Type 属于后面列出的类型时,就把响应交给 DEFLATE 过滤器做压缩。文件扩展名不重要,关键看服务器返回的 Content-Type。
3.3 验证配置是否正确
写完配置后,不能直接覆盖线上文件,先做语法检查。
# 检查 Apache 配置语法,如果没有错误会输出 Syntax OK
apachectl configtest
# 语法通过后,重新加载配置,不会中断服务
apachectl graceful
3.4 用命令行验证压缩效果
配置生效后,能不能确定真的压缩了?可以用 curl 模拟浏览器发送 Accept-Encoding: gzip 请求头,再看看服务器返回的响应头里有没有 Content-Encoding: gzip。技术栈:Shell 命令行工具。
# 请求一个 JS 文件,请求头里带上 gzip 压缩标识
curl -H "Accept-Encoding: gzip" -sI http://127.0.0.1/your-app.js | grep -i 'content-encoding'
# 如果输出类似下面这样,说明压缩已生效
# content-encoding: gzip
还可以直接对比压缩前后文件大小,用 curl 下载后看字节数。
# 普通请求,下载完整内容,输出实际大小
curl -s -o /dev/null -w "原始大小: %{size_download}\n" http://127.0.0.1/your-app.js
# 带 gzip 压缩请求,输出压缩后大小
curl -H "Accept-Encoding: gzip" -s -o /dev/null -w "压缩后大小: %{size_download}\n" http://127.0.0.1/your-app.js
注意,带 Accept-Encoding: gzip 时,下载到的是压缩后的二进制内容,所以 %{size_download} 会小很多。这两组数字一对比,就能看出压缩效果。
四、进阶:按文件类型精细控制
4.1 不同类型资源差别对待
有些文件虽然属于文本,但里面已经内嵌了 base64 编码的图片,压缩效果可能一般。另外,有些老的浏览器对某些压缩类型支持不好,需要特殊处理。Apache 提供了 BrowserMatch 指令来针对历史浏览器做兼容。
4.2 排除已经压缩过的资源
为了避免对已经压缩的格式做无用功,需要设置环境变量,阻止 deflate 对这些请求生效。我们可以用 SetEnvIfNoCase 根据请求 URI 的扩展名来排除。
<IfModule mod_deflate.c>
DeflateCompressionLevel 6
# 压缩文本类资源
AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css text/javascript
AddOutputFilterByType DEFLATE application/javascript application/json application/xml
AddOutputFilterByType DEFLATE application/rss+xml application/atom+xml image/svg+xml
# 以下扩展名不压缩,因为它们本身已经是压缩格式
# 包括常见图片、音视频、压缩包、office 文档等
SetEnvIfNoCase Request_URI \.(?:gif|jpe?g|png|mp4|mp3|avi|zip|gz|bz2|xz|rar|7z|pdf|docx?|xlsx?|pptx?)$ no-gzip dont-vary
# 对老浏览器做兼容处理
BrowserMatch ^Mozilla/4 gzip-only-text/html
BrowserMatch ^Mozilla/4\.0[678] no-gzip
BrowserMatch \bMSIE !no-gzip !gzip-only-text/html
# 给响应头加上 Vary,避免代理服务器缓存出错
Header append Vary User-Agent env=!dont-vary
</IfModule>
Header 指令需要 mod_headers 模块。Vary: User-Agent 告诉代理服务器:这个文件的内容可能因为客户端不同而变化,请根据 User-Agent 区分缓存。这样能避免某些代理把 gzip 版本缓存后发给了不支持压缩的浏览器。
4.3 配置示例加注释
上面的配置已经比较完善。如果只想精细化控制特定目录下的资源,可以放在 <Location> 或 <Directory> 里,只对局部路径生效。比如只压缩 /static 目录下的文件。
<IfModule mod_deflate.c>
# 只对 /static 路径下的资源做压缩
<Location /static>
AddOutputFilterByType DEFLATE text/css application/javascript
</Location>
</IfModule>
不过实际项目中,压缩通常是对全站生效的,只要 MIME 类型正确就行。
五、缓存与压缩的配合
5.1 设置缓存头
压缩减少传输体积,缓存减少重复请求。两者结合效果更好。静态资源通常可以设置较长的缓存时间。使用 mod_expires 和 mod_headers 可以做到。
# 假设已经加载了 mod_expires 和 mod_headers
<IfModule mod_expires.c>
ExpiresActive On
# CSS 和 JS 缓存 30 天
ExpiresByType text/css "access plus 30 days"
ExpiresByType application/javascript "access plus 30 days"
</IfModule>
<IfModule mod_headers.c>
# 给 css/js 文件添加显式缓存头
<FilesMatch "\.(css|js)$">
Header set Cache-Control "public, max-age=2592000"
</FilesMatch>
</IfModule>
注意,mod_deflate 在压缩资源时,不会影响缓存头。响应头里可以同时存在 Content-Encoding: gzip 和 Cache-Control: max-age=2592000。浏览器第一次解压后,在缓存有效期内不会重新下载。
5.2 与 ETag 和 Last-Modified 的相容
Apache 默认会生成 ETag 和 Last-Modified。开启压缩后,ETag 需要考虑内容编码差异。理想情况下,同一个文件压缩和不压缩应该有不同的 ETag,或者通过 Vary 头来区分。Apache 的 mod_deflate 会自动处理一部分逻辑,但为了稳妥,建议始终配置 Vary 头。尤其当你的站点放在 CDN 或反向代理后面时,这个头非常重要。
# 确保所有 DEFLATE 响应都带上 Vary: Accept-Encoding
<IfModule mod_headers.c>
Header append Vary Accept-Encoding env=!dont-vary
</IfModule>
Accept-Encoding 才是真正与压缩编码相关的头。加上这个,代理缓存就能区分“压缩版”和“非压缩版”,避免给不支持 gzip 的客户端发去乱码。
六、注意事项与避坑指南
6.1 不要压缩图片和音视频
图片和音视频本身就是高度压缩过的,再 gzip 一遍体积几乎不会变小,反而会白白消耗服务器 CPU。JPEG、PNG、GIF、WebP、MP4、WebM、MP3 这些格式都别加进去。配置中尽量用 MIME 类型白名单,而不是扩展名黑名单,因为黑名单容易漏。如果确实要用黑名单,也把常见类型都写全。
6.2 压缩等级与 CPU 开销
DeflateCompressionLevel 设得越高,压缩率越高,但 CPU 时间增长也越明显。级别 9 和级别 6 相比,压缩率提升很小,但 CPU 消耗可能高好几倍,特别是在高并发场景下,会导致服务器响应变慢。一般建议 5 或 6。如果服务器 CPU 比较紧张,也可以设置 4。同时,尽量做好缓存,让压缩后的结果能命中缓存,减少重复压缩。
6.3 代理服务器与 Content-Encoding 透传
如果网站前面有 Nginx 反向代理、CDN 或负载均衡器,你必须确认它们不会对压缩内容做重复处理。有些代理会自动再压缩一次,或者把 Content-Encoding 头剥离。最稳妥的做法是在 Apache 这里统一压缩,然后配置代理只做转发,不要再压缩。同时,所有经过压缩的响应都要带 Vary: Accept-Encoding,否则可能给某些浏览器造成问题。
6.4 别漏掉 MIME 类型
AddOutputFilterByType 匹配的是服务器响应头的 Content-Type。如果某个 JS 文件返回的 Content-Type 是 application/octet-stream,而不是 application/javascript,那么即使扩展名是 .js,也不会被压缩。因此,要保证 Apache 的 mime.types 正确,或者使用 AddType 主动补充。
# 手动指定一些常见文件扩展名对应的 MIME 类型
AddType application/javascript .js
AddType application/json .json
AddType image/svg+xml .svg
6.5 小心 BOM 和特殊响应
如果文件带有 UTF-8 BOM,压缩不会影响 BOM,但有些老浏览器在解压后解析 BOM 可能会出问题。另外,对于已经设置 Content-Encoding 的响应,比如某些 CGI 脚本自行压缩过,mod_deflate 要避免重复压缩。如果你自己写程序输出内容,最好让 Apache 统一处理,程序中不要手动输出 Content-Encoding 头。
七、技术优缺点总结
7.1 优点
- 减少带宽占用:文本资源体积普遍下降 60% 以上,服务器出口流量压力明显减小。
- 提升加载速度:用户下载的数据包变小,尤其在弱网环境下,感知最明显。
- 节省成本:按流量计费的服务器,能省下真金白银。
- 配置简单:Apache 模块机制成熟,几行指令就能对全站生效。
- 兼容性好:gzip 是浏览器和服务器之间的通用语言,目前基本所有客户端都支持。
7.2 缺点
- 消耗 CPU:压缩需要额外计算,高并发场景下可能成为瓶颈。
- 重复压缩风险:如果配置不当,或者和代理服务器配合不好,可能产生二次压缩,浪费性能。
- 对已压缩内容无效:图片、视频、压缩包等资源不会受益,反而会增加处理时间。
- 调试变复杂:压缩后浏览器开发者工具里看到的是解压内容,但抓包看到的是压缩流,排错时需要多留意。
7.3 应用场景
适合做内容编码压缩的典型场景包括:企业官网、博客、内容管理系统、电商站点的静态资源(CSS/JS/字体/JSON 数据接口)、以及使用了 Apache 作为前置服务器的单页应用。对于静态文件为主的站点,压缩几乎是必选优化项。对于图片和视频占比很高的站点,可以只压缩页面本身和 API 返回的 JSON,不要盲目给所有文件开压缩。
八、文章总结
Apache HTTP Server 的内容编码压缩,是提升静态资源传输效率最简单有效的手段之一。通过 mod_deflate 模块,把文本类资源压缩后再发给浏览器,不仅让页面加载更快,也让服务器带宽更省心。配置时,关键是选对 MIME 类型、控制压缩级别、设置好 Vary 头、排除已经压缩过的文件。同时,要意识到压缩并不是万能的,对图片和视频不要抱有期望,对 CPU 开销也要有合理预期。在实际生产环境中,建议先在测试环境开启,用 curl 验证压缩效果,再用 apachectl graceful 平滑发布。只要配置得当,一个看起来不起眼的 gzip 压缩,往往能让整个站点的访问体验迈上一个台阶。
评论
围绕“Apache HTTP Server静态资源压缩:优化传输效率的内容编码配置实践”参与讨论