带宽是比较宝贵的资源,而作为web服务器传输的内容的多少,直接影响到网站的上行和下行带宽,因此对传输的文本进行压缩,这是一个非常明智的选择。
本文将会带您探讨Tomcat中的compression相关的压缩属性,http响应头,和senfile的互斥性。
1.http响应头中压缩相关属性
http的响应头中,已经对压缩进行了定义,是Content-Encoding属性:
传输内容编码:Content-Encoding
内容编码,即整个数据信息是在数据器端经过怎样的编码处理,然后客户端会以怎么的编码来反向处理,以得到原始的内容。这里的内容编码主要是指压缩编码,即服务器端压缩,客户端解压缩。
可以参考的值为:gzip,compress,deflate和identity。
通常压缩方式都是gzip格式的,当选择gzip的时候,整个html文本会被进行一次gzip格式的压缩:
从上图的分析可以看出来,java版本的实现有GZIPOutputstream可以进行gzip的实现了,并且对于servlet可以查看通过查看httprequest来看这个属性是否支持gzip,如果支持的话,那么浏览器端也会进行gzip相应的解压。
除了这个Content-Encoding属性,其实还有两个比较重要的属性,容易和Content-Encoding属性混淆:
传输数据编码:Transfer-Encoding
数据编码,即表示数据在网络传输当中,使用怎么样的保证方式来保证数据是安全成功地传输处理。可以是分段传输,也可以是不分段,直接使用原数据进行传输。
有效的值为:chunked和Identity.
传输内容格式:Content-Type
内容格式,即接收的数据最终是以何种的形式显示在浏览器中。可以是一个图片,还是一段文本,或者是一段html。内容格式额外支持可选参数,charset,即实际内容的字符集。通过字符集,客户端可以对数据进行解编码,以最终显示可以看得懂的文字(而不是一段byte[]或者是乱码)。
上述的属性,可以有下面的图来进行解释:
Content-Type是代表着格式,这个一般不会混淆,
而Content-encoding这个是内容编码格式,实际上就是压不压缩传输,
Trandfer-encoding这个是传输的方式,大白话也就是分不分块,
上述的三个属性就是http响应头中的格式,我们主要关注的是Content-encoding,当然我们在解析Tomcat的代码时,还会看到其余的两个属性的踪影。
2.Tomcat中的压缩实现
对于压缩的处理,是在Tomcat中的响应头中,也就是Response的commit的时候,开始对输出流进行处理,而如果Content-encoding是gzip的话,那么会在Http11Processor中的输出流filter链条中,加上一个GzipOutputFilter:
Http11Processor是Tomcat前端比较重要的处理类,Work工作线程将任务交给Http11Processor开始继续干活,Http11Processor接着会攒出Request和Response,并基于Mapper进行调用,从而进入容器中。
而XXXFilter这里的filter不是容器端的filter,而是在Response进行commit提交的时候,基于响应头的Tomcat的配置,是否执行相关的处理。
以这个compression为例,当在Tomcat中配置了compression的话,GzipOutputFilter就开始自动执行过滤,从上面的代码逻辑可以看到,实际上就是基于流的包装机制,使用GzipOutPutStream来再对当前的流进行一次包装,然后在OutputBuffer最终commit的时候,调用这个GzipOutputFilter,最终执行doWrite方法,让输出流中的字节进行压缩。
从上述的分析可以看出,Tomcat的压缩实现实际上就是GzipOutputStream,只不过采用了GzipOutputFilter责任链的模式,通过流的一层一层的包装,将输出的字节进行了压缩。
3.Tomcat中的压缩配置与实现
我们再看看Tomcat中的关于压缩的配置,主要是三个属性,每一个通道都可以配置:
compression:这个属性是开启压缩属性(默认就是开的),也可以设置关闭和强制压缩(当然你浏览器端要是不支持解压缩会报错);
compressableMimeType:对什么格式的文本执行压缩
compressionMinSize:当发送的文件的大小大于多少时候,才进行压缩,小于这个配置数不进行压缩,默认为2048。
我们来看看这三个属性是怎么实现的,还是回到Http11Processor类中的action方法:
当为commit的时候,一共有两步的操作,第一步是准备一个Response,第二步是调用OutputBuffer进行commit;
对于第二步前面已经讲过,如果是gzip的话,会调用GzipFilter进行GzipOutputStream的包装,这样输出流自动就是压缩的;
第一步,我们在sendfile的实现中也接触过,prepareResponse方法,就是通过Tomcat的配置,对响应头进行解析并设置,我们来仔细分析一下这个prepareResponse方法中涉及compression的部分代码
其中主要有两个标识比较关键,
一个标识是isCompressable,这个标识对应的方法也是isCompressable方法,该方法主要是解析上面的几个配置,判断是否满足当前响应头的压缩的条件;
第二个标识是useCompression,这个标识对应的方法也是useCompression方法,该方法主要是通过Request来查找accept-encoding,use-agent等几个属性,来判断浏览器端是否支不支持压缩;
这两个条件都满足的话,可以看到最后一步,OutputBuffer加入GzipOutputFilter,然后设置响应头的Content-Encoding为gzip。
这个就是整个Tomcat的压缩部分的全部实现。
4.与sendfile的互斥性
sendfile我们了解,实际是一种操作系统级别的优化手段,直接跳过内存转接,直接从内核缓冲区到网卡缓冲区,相当于高效;
但是我们在查询Tomcat文档的时候,发现sendfile和compression是不兼容的,也就是上图中的红色字体部分,这个是为什么呢?
可以这么来理解,对于compression必然需要在内存转接中进行操作,也就是下图中用户空间部分:
而我们了解到senfile实际是将这部分给省略掉了;
因此,就会出现上述的英文了:
There is a tradeoff between using compression (saving your bandwidth) and using the sendfile feature (saving your CPU cycles).
是一个tradeoff(妥协),如果你使用compression,带宽是省了,但是cpu是浪费了,如果你使用sendfile,cpu是省了,但是带宽你是浪费了,
总结下来,就是天下没有完美的午餐。
总结:
当配置Compression为gzip时,在Tomcat中是采用GzipOutputStream来实现的,而更要记住的是,Sendfile和Compression这两个优化选项只能选择其一来使用!