美文网首页tomcat
keepalive连接复用对tomcat线程池的影响

keepalive连接复用对tomcat线程池的影响

作者: 肥兔子爱豆畜子 | 来源:发表于2021-08-02 21:58 被阅读0次

本文来源于看到的一篇文章: tomcat的acceptCount、maxThreads、connectionTimeout参数调整

  • 这篇文章中对acceptCount的分析其实很到位,在理解了tcp的握手过程、syn队列和accept队列的知识之后,就比较容易理解这个参数的含义以及在对tomcat的缓冲和保护作用了。
  • 在看了tomcat的源码之后,也容易理解文章中说的关于connectionTimeout其实就是SO_TIMEOUT也是对的,作为服务端的connectionTimeout这个参数很容易被名字误导跟一些客户端连接工具(比如HttpClient、数据库连接池之类)的connectionTimeout参数搞混,客户端这个参数代表英文直接翻译过来的意思“建立连接超时”,尝试跟对端建立连接然后尝试了这个时间之后还没连上就抛异常,而tomcat作为服务端的这个参数跟“建立连接”没关系,事实上它应该是建立连接之后,如果超过connectionTimeout这个时间还没收到客户端的请求,则抛异常。tomcat会默认用它来设置socket的readTimeout和writeTimeout、从这也可以看出tomcat中这个参数的含义。另外,这个参数跟maxKeepaliveTimeout的区别是maxKeepaliveTimeout强调是请求处理完了之后等待下一次请求的超时时间。
  • 文章中关于maxThreads的理解笔者也是认同的,关于tomcat工作线程池的大小如何调整其实取决于task的性质和当前系统运行状态CPU利用率,task阻塞比较多、可以适当调多一些线程个数增加并行处理和吞吐能力,如果task阻塞很少、cpu利用比较充分,那调多线程个数其实会起到相反效果、需知CPU在线程间切换的成本也是比较高的,当CPU花在线程切换上的开销甚至高于实际处理业务逻辑上的开销时,显然这样的调优是误入了歧途。

但是,文章中的一段关于keepalive的观点让我产生了疑惑:

“当开启http keep alive的时候,client端可能没有那么及时地关闭连接,那么server端的worker线程会一直被这些实际上可能不活跃的连接给占用了,导致worker线程没能重复利用起来。”

如果按照上面的描述,当client没有请求发送但维持住长连接不及时关闭,tomcat的worker线程会被占用,那不就是相当于:

  1. 在tomcat使用nio模式时,client与tomcat之间维持连接,复用于多个request,这时候worker线程是专门为这个连接服务的吗?连接断开之前能否服务其他连接?
  2. 如果不能,那么是不是就跟bio一样了,相当于有多少个worker线程就能服务多少个连接了?


直觉感觉这不可能,事实上,tomcat NIO模式下,worker线程在一次request的读写过程中是blocking的,但是一次request读写完成之后,等待下次request是unblocking的。这在tomcat的官网上关于其几种IO模式的设计思路上可以查到。见下图:

另外,关于tomcat的IO方式和线程模型笔者也在Tomcat NIO线程模型与IO方式分析 这篇文章中分析过:等待下一次从socket连接过来的请求的时候是把socket注册到Poller的selector上的,这个时候它是非阻塞的,而读一个具体的request body的时候,如果body未读完则使用CountDownLatch阻塞当前线程等待BlockPoller通知继续读。

Poller每次提交给worker线程池的task类是SocketProcessor,名字起的有些误导人,事实上提交给线程池处理的不是“一个连接”而应该理解为“一次请求”,同一个连接上可以有多个请求,每个请求可能会分配给不同的worker线程去处理,但是每个请求的body是始终由一个线程处理的。这就是tomcat的请求处理的线程模型。

程序验证

服务端使用springboot2内嵌的tomcat9运行一个servlet,为了验证,对tomcat做如下配置:

server.port=8080
server.servlet.context-path=/prototype
server.tomcat.max-threads=1
server.tomcat.max-connections=6
server.tomcat.accept-count=2

然后启动两个客户端程序,代码基本一样,就是不断的以keepalive的方式往服务端发请求:

import org.apache.http.client.fluent.Request;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
 * 使用HttpClient fluent api客户端工具建立连接并发送http POST请求,
 * 客户端有连接池,能够以keepalive方式与服务端进行连接复用,一个连接可发送多个请求。
 * */
public class TestHttpKeepAlive1 {
    private static Logger logger = LoggerFactory.getLogger(TestHttpKeepAlive1.class);

    public static void main(String[] args) {
        StringEntity entity = new StringEntity("{\"name\":\"AAA\"}", ContentType.APPLICATION_JSON);
        try {
            while (true) {
                String reponseContent = Request.Post("http://localhost:8080/prototype/testRequestServlet").body(entity)
                        .execute().returnContent().toString();
                logger.info(reponseContent);
                TimeUnit.SECONDS.sleep(2);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

tomcat的默认maxKeepaliveTimeout是60s,另外一个控制长连接能保持多久的参数是maxKeepAliveRequests、默认是100,可以通过调试客户端程序配合netstat命令查看tcp连接,当启动一个客户端向服务端发送100次请求之前或发送完一个请求之后的60秒内,客户端与服务端之间的连接一直是同一个,这可以通过客户端的端口号确认。


而当我们启动两个客户端的时候,可以看到服务端也是可以用1个工作线程来同时服务两个客户端连接的,且2个连接一直保持(执行了100次请求之后会打开新连接)。



连接复用可以较少频繁的连接建立与关闭带来的开销,特别是对于传输报文本身比较小的情况,短链接的建立与关闭开销在整个通信过程占比十分可观。但有一点要注意就是长连接对客户端出站端口的占用和服务端tomcat连接数的占用(通过LimitLatch控制),当tomcat仅接收来自其他内部系统的调用的时候、客户端连接的数量相对是可控的,开启长连接可以显著提高性能。

进一步参考

关于tomcat的各个配置参数的含义和默认值,可以参考tomcat官网doc: https://tomcat.apache.org/tomcat-9.0-doc/config/http.html
通过合理的使用keepalive来提高client与tomcat之间的http请求性能,可以参考配置TOMCAT及httpClient的keepalive以高效利用长连接

相关文章

  • keepalive连接复用对tomcat线程池的影响

    本文来源于看到的一篇文章: tomcat的acceptCount、maxThreads、connectionTim...

  • 享元模式

    简介 缓存共享对象,复用共享对象,比如各种池化操作,数据库连接池缓存连接,线程池缓存线程等等。 享元模式样...

  • tomcat jdbc连接池配置

    1.连接池本质作用是为客户端提供连接复用,提升连接效率,降低系统开销。Tomcat的连接池提供了maxActive...

  • Java线程池

    为什么需要线程池 对象复用思想在编程中有很多应用,不论是线程池还是连接池都是一种对象复用的思想。今天来谈谈Java...

  • DruidDataSource详解(一)

    资源复用是系统性能优化中的一种常用手段,如单例,数据库连接池,线程池等都是资源复用的常用技巧。 数据库连接池的基本...

  • 数据库连接池、线程池等管理的关键点,你知道吗?

    在Java应用开发中经常会用到连接池、线程池等池化技术。池化(pool)技术的本质是通过复用对象、连接等资源,减少...

  • Java并发JUC——ThreadPoolExecutor 深入

    线程池的作用 ● 利用线程池管理并复用线程、控制最大并发数等既然使用了线程池就需要确保线程池是在复用的,每次new...

  • Java笔记-Thread Pool 线程池

    1、线程池作用 线程池作用就是限制系统中执行线程的数量,且复用以前工作线程。根据当前的环境,手动配置线程池,减少对...

  • 源码剖析ThreadPoolExecutor线程池及阻塞队列

    本文章对ThreadPoolExecutor线程池的底层源码进行分析,线程池如何起到了线程复用、又是如何进行维护我...

  • JVM调优3-tomcat8优化

    1. tomcat配置优化 1.1 禁用AJP连接 在server.xml中关闭AJP连接 1.2 执行器(线程池...

网友评论

    本文标题:keepalive连接复用对tomcat线程池的影响

    本文链接:https://www.haomeiwen.com/subject/cbojvltx.html