流程
服务器配置
nginx
server {
# 开启gzip压缩服务
gzip on;
# gzip压缩是要申请临时内存空间的,假设前提是压缩后大小是小于等于压缩前的。
# 例如,如果原始文件大小为10K,那么它超过了8K,所以分配的内存是8 * 2 = 16K;再例如,
# 原始文件大小为18K,很明显16K也是不够的,那么按照 8 * 2 * 2 = 32K的大小申请内存。
# 如果没有设置,默认值是申请跟原始数据相同大小的内存空间去存储gzip压缩结果。
# 设置系统获取几个单位的缓存用于存储gzip的压缩结果数据流。
# 例如 4 4k 代表以4k为单位,按照原始数据大小以4k为单位的4倍申请内存。
# 4 8k 代表以8k为单位,按照原始数据大小以8k为单位的4倍申请内存。
# 如果没有设置,默认值是申请跟原始数据相同大小的内存空间去存储gzip压缩结果。
gzip_buffers 2 8k;
# nginx对于静态文件的处理模块。
# 该模块可以读取预先压缩的gz文件,这样可以减少每次请求进行gzip压缩的CPU资源消耗。
# 该模块启用后,nginx首先检查是否存在请求静态文件的gz结尾的文件,如果有则直接返回该gz文件内容。
# 为了要兼容不支持gzip的浏览器,启用gzip_static模块就必须同时保留原始静态文件和gz文件。
# 这样的话,在有大量静态文件的情况下,将会大大增加磁盘空间。我们可以利用nginx的反向代理功能实现只保留gz文件。
gzip_static on|off
# 启用gzip压缩的最小文件,小于设置值的文件将不会压缩 gzip_min_length 1k;
# gzip压缩基于的http协议版本,默认就是HTTP 1.1 gzip_http_version 1.1;
# gzip 压缩级别,1-10,数字越大压缩的越好,也越占用CPU时间,后面会有详细说明 gzip_comp_level 2;
# 需要进行gzip压缩的Content-Type的Header的类型。建议js、text、css、xml、json都要进行压缩;
# 图片就没必要了,gif、jpge文件已经压缩得很好了,就算再压,效果也不好,而且还耗费cpu。
# javascript有多种形式。其中的值可以在 mime.types 文件中找到。
gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png;
# 默认值:off
# Nginx作为反向代理的时候启用,开启或者关闭后端服务器返回的结果,匹配的前提是后端服务器必须要返回包含"Via"的 header头。
# off - 关闭所有的代理结果数据的压缩
# expired - 启用压缩,如果header头中包含 "Expires" 头信息
# no-cache - 启用压缩,如果header头中包含 "Cache-Control:no-cache" 头信息
# no-store - 启用压缩,如果header头中包含 "Cache-Control:no-store" 头信息
# private - 启用压缩,如果header头中包含 "Cache-Control:private" 头信息
# no_last_modified - 启用压缩,如果header头中不包含 "Last-Modified" 头信息
# no_etag - 启用压缩 ,如果header头中不包含 "ETag" 头信息
# auth - 启用压缩 , 如果header头中包含 "Authorization" 头信息
# any - 无条件启用压缩 gzip_proxied [off|expired|no-cache|no-store|private|no_last_modified|no_etag|auth|any] ...
# 是否在http header中添加Vary: Accept-Encoding,建议开启
# 和http头有关系,加个vary头,给代理服务器用的,有的浏览器支持压缩,
# 有的不支持,所以避免浪费不支持的也压缩,所以根据客户端的HTTP头来判断,是否需要压缩 gzip_vary on;
# 禁用IE 6 gzip gzip_disable "MSIE [1-6]\."; }
整个请求过程来看,开启gzip和不开启gip功能,其http的请求和返回过程是一致的,不同的是参数。
当开启HTTP的gzip功能时,客户端发出http请求时,会通过headers中的Accept-Encoding属性告诉服务器“我支持gzip解压,解压格式(算法)为: deflate,sdch”。Accept-Encoding:gzip,deflate,sdch
注意,不是request说自己支持解压,Nginx返回response数据的时候就一定会压缩。这还要看本次Nginx返回数据的格式是什么,如果返回数据的原始数据格式,和设置的gzip_types相符合,这时Nginx才会进行压缩。
Nginx返回response headers是,如果数据被压缩了,就会在Content-Encoding属性中标示gzip,表示接下来返回的response
content是经过压缩的;并且在Content-Type属性中表示数据的原始格式。
最后返回经过压缩的response content给客户端,客户端再进行解压。这里注意一下,在客户端发送的headers里面,有一个deflate,sdch。
Webpack 开启 Gzip
通过 compression-webpack-plugin 可以开启 Gzip 压缩。
new CompressionPlugin({
filename: "[path][base].gz", // 不赋值的话打包后都.gz文件都会生成在build下
algorithm: "gzip",
test: /\.js$|\.css$|\.html$/,
threshold: 10240, // 大于10K的才会被压缩,过小的文件没必要压缩
minRatio: 0.8,
}),
前后对比
压缩前
压缩后
size对应的就是gzip压缩前后的体积
压缩原理
压缩核心之 Deflate
GZIP 的核心是 Deflate,在 RFC 1951 中被标准化,并且在当时作为 LZW 的替代品有了非常广泛的使用。
Deflate 是一个同时使用 LZ77 与 Huffman Coding 的算法,这里简单介绍下这两种算法的大致思路:
LZ77
LZ77 的核心思路是如果一个串中有两个重复的串,那么只需要知道第一个串的内容和后面串相对于第一个串起始位置的距离 + 串的长度。
比如: ABCDEFGABCDEFH → ABCDEFG(7,6)H。7 指的是往前第 7 个数开始,6 指的是重复串的长度,ABCDEFG(7,6)H 完全可以表示前面的串,并且是没有二义性的。
LZ77 用 滑动窗口(sliding-window compression)来实现这个算法。具体思路是扫描头从串的头部开始扫描串,在扫描头的前面有一个长度为 N 的滑动窗口。如果发现扫描头处的串和窗口里的 最长匹配串 是相同的,则用(两个串之间的距离,串的长度)来代替后一个重复的串,同时还需要添加一个表示是真实串还是替换后的“串”的字节在前面以方便解压(此串需要在 真实串和替换“串” 之前都有存在)。
实际过程中滑动窗口的大小是固定的,匹配的串也有最小长度限制,以方便 标识+两个串之间的距离+串的长度 所占用的字节是固定的 以及 不要约压缩体积越大。更加详细的实现可以参考:Standford Edu. lz77 algorithm、 LZ77 Compression Algorithm、 LZ77压缩算法编码原理详解(结合图片和简单代码)
这里通过这个压缩机制也就能比较容易的解释为啥 CSS BEM 写法 GZIP 压缩之后可以忽略长度以及 JPEG 图片 GZIP 之后可能会变大 的情况了
解压:GZIP 的压缩因为要在窗口里寻找重复串相对来说效率是比较低的(LZ77 还是通过 Hash 等系列方法提高了很多),那解压又是怎么个情况呢?观察压缩后的整个串,每个小串前都有一个标识要标记是原始串还是替换“串”,通过这个标识就能以 O(1)的复杂度直接读完并且替换完替换“串”,整体上效率是非常可观的。
Huffman Coding
Huffman Coding 是大学课本中一般都会提到的算法。核心思路是通过构造 Huffman Tree 的方式给字符重新编码(核心是避免一个叶子的路径是另外一个叶子路径的前缀),以保证出现频路越高的字符占用的字节越少。关于 Huffman Tree 的构造这里不再细说,不太清楚的可以参考:Huffman Coding。
解压:Huffman Coding 之后需要维护一张 Huffman Map 表,来记录重新编码后的字符串,根据这张表,还原原始串也是非常高效的。
Deflate 综合使用了 LZ77 和 Huffman Coding 来压缩文件,相对而言又提升了很多。详细可以参考 gzip原理与实现
问题
是否所有文件都需要开启 gzip
如果压缩文件太小,那不使用;但是如果具有一定规模的项目文件,比如10K以上,可以开启 gzip。
媒体文件无需开启: 图片、音乐、视频大多数已经压缩过了。一般只需要压缩html, css, javascript
gzip 原理
gzip 并不是万能的,它的原理是在一个文本文件中找一些重复出现的字符串、临时替换它们,从而使整个文件变小,所以对于图片等会处理不了。
核心算法是哈夫曼算法和LZ77
为什么在本地进行gzip
服务器压缩也需要时间开销和 CPU 开销,所以有时候可以用 Webpack 来进行 gzip 压缩,从而为服务器分压。
服务器查找到有与源文件同名的.gz文件就会直接读取,不会主动压缩,降低cpu负载,优化了服务器性能
gzip_comp_level 的合理值
从图中可以看出 gzip_comp_level 大于2时效果并不是很明显。所以可以将值设置为1或者2。