Tomcat高效响应的秘密 keep alive

Posted Tomcat那些事儿

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Tomcat高效响应的秘密 keep alive相关的知识,希望对你有一定的参考价值。

众所周知,HTTP是TCP/IP的上层协议。而基于TCP/IP的协议,都会涉及到三次握手建立连接,四次挥手结束连接。当然,在请求数量不多的情况下,这些还勉强可以接受。但在互联网上,各种HTTP的请求,动辄成百上千的连接请求,如果每个都握手、挥手,基本都干这些了,正常的数据传输反而不能及时送达了。


为此,在HTTP1.1的时候,将keep alive设置为默认的,以提高HTTP的效率。下图是维基百科上关于keep alive的一个配图,对比打开和关闭keep alive情况下的传输效果。


keep alive,又称为persistent connection,或Http connection reuse。主要作用是用一个TCP连接来处理多个HTTP的请求和响应。好处自然是相当明显的,节省了创建和结束多个连接的时间,可以更专注于数据的传输,页面可以更快的渲染,同时也降低了服务器的负载。


以下几张图来自图解HTTP一书,也很好的说明keep alive的原理


单个请求的情况:

Tomcat高效响应的秘密(二) keep alive

多个请求的情况:


Tomcat高效响应的秘密(二) keep alive


使用keep alive后的情况:


Tomcat高效响应的秘密(二) keep alive



基本原理就是上面这些,而我们在发送一个HTTP请求时,HTTP1.1的请求头中会自动包含connection: keep alive这样的内容。Tomcat高效响应的秘密(二) keep alive

那是不是keep alive一直开启着就一直有优势呢? 


事情都是有两面性的,如果一直开启,那在大量请求到来时,势必会创建大量的连接,而这些连接一直keep alive,而请求量下降的时候,服务器上会存在不少不工作的连接,依然会影响性能。



下面我们来看,在Tomcat中,对于keep alive是怎么实现的。


前面的文章里写过关于Tomcat的几类Connector,可以看这里



几类Connector包含一个抽象基类AbstractHttp11Processor

在该类中会进行keep alive逻辑的判断处理。


上面内容说keep alive默认是开启的,当然也是可以显式的关闭的,只要在请求头中设置connection:close就可以了。


代码中先是判断请求头中是否包含关闭的配置

MimeHeaders headers = request.getMimeHeaders();

        // Check connection header

        MessageBytes connectionValueMB = headers.getValue(Constants.CONNECTION);

        if (connectionValueMB != null) {

            ByteChunk connectionValueBC = connectionValueMB.getByteChunk();

            if (findBytes(connectionValueBC, Constants.CLOSE_BYTES) != -1) {// 判断是否有显式的关闭keep-alive

                keepAlive = false;

            } else if (findBytes(connectionValueBC,

                                 Constants.KEEPALIVE_BYTES) != -1) {

                keepAlive = true;

            }

        }


同时,在Connector中还可以配置,允许keep alive的最大连接数,

   if (maxKeepAliveRequests == 1) {

                keepAlive = false;

            } else if (maxKeepAliveRequests > 0 &&

                    socketWrapper.decrementKeepAlive() <= 0) {  // 判断允许keep alive的请求数还有多少

                keepAlive = false;

            }



同时在特定条件下,已经开启的keep alive 连接也是要关闭的

// Connection: close header.

        keepAlive = keepAlive && !statusDropsConnection(statusCode); // 根据status code判断是否要把当前连接丢掉

        if (!keepAlive) {

            // Avoid adding the close header twice

            if (!connectionClosePresent) {

            headers.addValue(Constants.CONNECTION).setString(  // 设置响应头中的connection为close

                        Constants.CLOSE);

            }

        } else if (!http11 && !getErrorState().isError()) {

            headers.addValue(Constants.CONNECTION).setString(Constants.KEEPALIVE);

        }


对应的以下几类响应状态,keep alive会关闭

/**

     * Determine if we must drop the connection because of the HTTP status

     * code.  Use the same list of codes as Apache/httpd.

     */

    protected boolean statusDropsConnection(int status) {

        return status == 400 /* SC_BAD_REQUEST */ ||

               status == 408 /* SC_REQUEST_TIMEOUT */ ||

               status == 411 /* SC_LENGTH_REQUIRED */ ||

               status == 413 /* SC_REQUEST_ENTITY_TOO_LARGE */ ||

               status == 414 /* SC_REQUEST_URI_TOO_LONG */ ||

               status == 500 /* SC_INTERNAL_SERVER_ERROR */ ||

               status == 503 /* SC_SERVICE_UNAVAILABLE */ ||

               status == 501 /* SC_NOT_IMPLEMENTED */;

    }


在服务端传回响应头中包含close,则该连接会关闭。

Tomcat高效响应的秘密(二) keep alive



而实质上,每次的keep alive,是在一个循环里跑,直到到达配置的超时时间后,退出循环。退出时的根据最终Socket的状态,判断是否要保持该keep alive的连接。如果要保持,则会重新添加到线程池中继续执行,例如NIO的处理时,在循环跳出后,finally中最终会执行以下代码,将SocketProcessor添加到cache中。


if (running && !paused) {

                    processorCache.push(this); // 重点是这里。

           }


而Poller在处理特定的事件时,会直接从cache中pop出来


  SocketProcessor sc = processorCache.pop(); // 看这里

            if ( sc == null ) sc = new SocketProcessor(attachment, status);

            else sc.reset(attachment, status);

            Executor executor = getExecutor();

            if (dispatch && executor != null) {

                executor.execute(sc);

            } else {

                sc.run();

          }


BIO中,则是直接将SocketProcessor传到线程池的Executor中。

 getExecutor().execute(new SocketProcessor(socket, SocketStatus.OPEN_READ));



相关阅读













Tomcat那些事儿

扫描或长按下方二维码,即可关注!





以上是关于Tomcat高效响应的秘密 keep alive的主要内容,如果未能解决你的问题,请参考以下文章

nginx与tomcat之间的keep-alive配置

Tomcat、HTTP Keep-Alive 和 Java 的 HttpsUrlConnection

高效管理项目的秘密武器:累积流图

团队高效率协作开发的秘密武器-APIDOC

高效的秘密让k8s运维更高效-日志搜索脚本

高效的秘密让k8s运维更高效-日志搜索脚本