Tomcat 配备gzip无效

Tomcat 配置gzip无效

提升Tomcat性能方法有很多种,使用NIO Connector和启用gzip压缩是其中两种。

NIO:Java New IO,使用了多路复用的技术,无疑要比普通的IO socket要高效。

gzip:对需要传输到前台的内容首先在内存中进行gzip压缩,这样可以大大的减少网络带宽占用。前提是前台的Accept-Encoding允许gzip。

但是,当同时配置了这两个时,会发现大于48KB的文件并没有进行压缩。

经查Tomcat源码,发现org.apache.catalina.servlets.DefaultServlet中:

/**
     * Check if sendfile can be used.
     */
    protected boolean checkSendfile(HttpServletRequest request,
                                  HttpServletResponse response,
                                  CacheEntry entry,
                                  long length, Range range) {
        if ((sendfileSize > 0)
            && (entry.resource != null)
            && ((length > sendfileSize) || (entry.resource.getContent() == null))
            && (entry.attributes.getCanonicalPath() != null)
            && (Boolean.TRUE == request.getAttribute("org.apache.tomcat.sendfile.support"))
            && (request.getClass().getName().equals("org.apache.catalina.connector.RequestFacade"))
            && (response.getClass().getName().equals("org.apache.catalina.connector.ResponseFacade"))) {
            request.setAttribute("org.apache.tomcat.sendfile.filename", entry.attributes.getCanonicalPath());
            if (range == null) {
                request.setAttribute("org.apache.tomcat.sendfile.start", new Long(0L));
                request.setAttribute("org.apache.tomcat.sendfile.end", new Long(length));
            } else {
                request.setAttribute("org.apache.tomcat.sendfile.start", new Long(range.start));
                request.setAttribute("org.apache.tomcat.sendfile.end", new Long(range.end + 1));
            }
            return true;
        } else {
            return false;
        }
    }

此处的sendfileSize = 48*1024,默认值为48KB,可以发现,当文件大小大于48KB时,Tomcat并未马上将内容写回到output中,而是把文件的路径记录下来。

/**
     * Serve the specified resource, optionally including the data content.
     *
     * @param request The servlet request we are processing
     * @param response The servlet response we are creating
     * @param content Should the content be included?
     *
     * @exception IOException if an input/output error occurs
     * @exception ServletException if a servlet-specified error occurs
     */
    protected void serveResource(HttpServletRequest request,
                                 HttpServletResponse response,
                                 boolean content)
        throws IOException, ServletException {

    		......

            // Copy the input stream to our output stream (if requested)
            if (content) {
                try {
                    response.setBufferSize(output);
                } catch (IllegalStateException e) {
                    // Silent catch
                }
                if (ostream != null) {
                    if (!checkSendfile(request, response, cacheEntry, contentLength, null))
                        copy(cacheEntry, renderResult, ostream);
                } else {
                    copy(cacheEntry, renderResult, writer);
                }
            }
    		
    		......

    }

并在Http11Processor的process方法的最后一部分,把文件内容以FileChannel的形式写回到前台,不需要先把文件内容先读到用户内存->压缩->写回socket内核内存。


/**
     * Process pipelined HTTP requests using the specified input and output
     * streams.
     *
     * @throws IOException error during an I/O operation
     */
    public SocketState process(NioChannel socket)
        throws IOException {
        
         ......
            // Do sendfile as needed: add socket to sendfile and end
            if (sendfileData != null && !error) {
                KeyAttachment ka = (KeyAttachment)socket.getAttachment(false);
                ka.setSendfileData(sendfileData);
                sendfileData.keepAlive = keepAlive;
                SelectionKey key = socket.getIOChannel().keyFor(socket.getPoller().getSelector());
                //do the first write on this thread, might as well
                openSocket = socket.getPoller().processSendfile(key,ka,true,true);
                break;
            }
        ......

    }

这种NIO底层读写channel的形式避免了读取到用户内存的开销,也可以提升性能。

目前,尚不清楚使用NIO快,还是gzip较快,有待测试。

如果在使用NIO的同时还一定要用gzip,可以关闭NIO Connector的useSendFile选项。

<Connector port="8080" protocol="org.apache.coyote.http11.Http11NioProtocol"
               connectionTimeout="20000" 
               redirectPort="8443"
               	useSendfile="false"
               	compression="on" 
				compressionMinSize="2048" 
				noCompressionUserAgents="gozilla, traviata" 
				compressableMimeType="text/html,text/xml,text/javascript" />


参考:http://tomcat.apache.org/tomcat-6.0-doc/config/http.html