当前位置: 首页 > 科技观察

解释Tomcat的连接数和线程池

时间:2023-03-22 10:36:23 科技观察

在使用tomcat的时候,经常会遇到连接数、线程数等配置问题。要真正理解这些概念,首先要理解Tomcat的连接器(Connector)。在上一篇文章中,我在Tomcat的配置文件server.xml中写到:Connector的主要作用是接收连接请求,创建Request和Response对象,用于与请求端交换数据;然后分配线程让Engine(也就是Servlet容器)来处理这个请求,并将生成的Request和Response对象传递给Engine。Engine处理完请求后,也会通过Connector将response返回给客户端。可以说Servlet容器对请求的处理需要Connector的调度和控制。连接器是Tomcat请求处理的支柱。因此,Connector的配置和使用对Tomcat的性能有着重要的影响。本文将从Connector入手,讨论与Connector相关的一些重要问题,包括NIO/BIO模式、线程池、连接数等。根据协议不同,Connector可分为HTTPConnector、AJPConnector等。本文只讨论HTTP连接器。1、Nio、Bio、APR1和Connector的协议Connectors在处理HTTP请求时使用不同的协议。不同的Tomcat版本支持不同的协议。最典型的协议有BIO、NIO、APR(Tomcat7支持这三种,Tomcat8增加了对NIO2的支持,Tomcat8.5和Tomcat9.0去掉了对BIO的支持)。BIO即BlockingIO,顾名思义,就是阻塞IO;NIO即Non-blockingIO,就是非阻塞IO。而APR是ApachePortableRuntime,即Apache的可移植运行时库。使用本地库可以实现高扩展性和高性能;Apr是在Tomcat上运行高并发应用的最佳模式,但是需要安装apr和apr-utils、tomcat-native等包。2.如何指定protocolConnector使用哪种协议,可以通过元素中的protocol属性指定,也可以使用默认值。指定协议值及对应的协议如下:HTTP/1.1:默认值,使用的协议与Tomcat版本有关org.apache.coyote.http11.Http11Nio2Protocol:NIO2org.apache.coyote.http11.Http11AprProtocol:APR如果没有指定协议,则使用默认值HTTP/1.1,其含义如下:在Tomcat7中,BIO或APR是自动选择的(如果找到APR如果找到APR需要的本地库,则使用APR,否则使用BIO);在Tomcat8中,会自动选择NIO或APR(如果找到APR需要的本地库,则使用APR,否则使用NIO)。3.BIO/NIO有什么区别无论是BIO还是NIO,Connector处理请求的大致过程都是一样的:在accept队列中接收连接(当客户端向服务器发送请求时,如果客户端和OS完成三次握手建立如果连接建立,OS将连接放入accept队列);获取连接中请求的数据并生成请求;调用servlet容器处理请求;返回响应。为了后面的描述方便,先明确一下connection和request的关系:connection在TCP层(传输层),对应socket;请求在HTTP层面(应用层),必须依赖TCP连接实现;一个TCP连接可能会传输多个HTTP请求。在BIO实现的Connector中,处理请求的主要实体是JIoEndpoint对象。JIoEndpoint维护了Acceptor和Worker:Acceptor接收到socket,然后从Worker线程池中找一个空闲线程来处理socket。如果工作线程池中没有空闲线程,Acceptor就会阻塞。其中Worker是Tomcat自带的线程池。如果通过配置其他线程池,原理和Worker类似。在NIO实现的Connector中,处理请求的主要实体是NIoEndpoint对象。除了Acceptor和Worker,NIoEndpoint还使用了Poller。处理流程如下图所示(图片来源:http://gearever.iteye.com/blog/1844203)。Acceptor接收到socket后,并不直接使用Worker中的线程来处理请求,而是先将请求发送给Poller,而Poller是实现NIO的关键。Acceptor通过队列向Poller发送请求,使用典型的生产者-消费者模型。在Poller中,维护了一个Selector对象;当Poller从队列中取出一个socket时,注册到Selector中;然后通过遍历Selector,找到可读socket,使用Worker中的线程来处理相应的请求。和BIO类似,Worker也可以换成自定义的线程池。通过上面的过程可以看出,在NIoEndpoint处理请求的过程中,无论是Acceptor接收socket还是线程处理请求,仍然采用了阻塞的方式;但是在“读取socket并交给Worker中的线程”的过程中,使用了非阻塞的NIO实现,这是NIO模式和BIO模式的主要区别(其他区别较少对性能有影响,暂时不提)。而这种差异可以带来Tomcat在并发量大的情况下效率的显着提升:目前大多数HTTP请求都使用长连接(HTTP/1.1默认keep-alive为true),长连接意味着一个TCPsocket当前请求结束后,如果没有新的请求到来,socket不会立即释放,而是超时后释放。如果使用BIO,“读取socket并交给Worker中的线程”的过程是阻塞的,也就是说处理socket的worker线程在socket等待下一次请求或者等待的时候会一直被占用释放。无法释放;因此,Tomcat同时处理的socket数不能超过最大线程数,性能受到很大限制。使用NIO,“读取socket并交给Worker中的线程”的过程是非阻塞的。当socket在等待下一个请求或者等待释放时,不会占用工作线程,所以Tomcat可以同时处理大量的socket。最大线程数,并发性能大幅提升。二、3个参数:acceptCount,maxConnections,maxThreads回顾一下Tomcat处理请求的过程:在accept队列中接收连接(客户端向服务器发送请求时,如果客户端与OS完成三次握手建立一个连接,OS将该连接放入接受队列);获取连接中请求的数据并生成请求;调用servlet容器处理请求;返回响应。相应的,Connector中的几个参数的作用如下:1、acceptCount队列的长度;当accept队列中的连接数达到acceptCount时,队列满了,所有进来的请求都会被拒绝。默认值为100。2.maxConnectionsTomcat在任何时候接收和处理的最大连接数。当Tomcat接收到的连接数达到maxConnections时,Acceptor线程将不会读取accept队列中的连接;此时accept队列中的线程会被阻塞,直到Tomcat接收到的连接数小于maxConnections。如果设置为-1,则连接数不受限制。默认值与connector使用的协议有关:NIO默认值为10000,APR/native默认值为8192,BIO默认值为maxThreads(如果配置了Executor,默认值为Executor的最大线程数)。Windows下,APR/native的maxConnections值会自动调整为低于设定值1024的整数倍;如果设置为2000,则最大值实际为1024。3.maxThreads请求处理线程数的最大值。默认值为200(Tomcat7和8)。如果Connector绑定了一个Executor,这个值会被忽略,因为Connector会使用绑定的Executor而不是内置的线程池来执行任务。maxThreads指定最大线程数,不是实际运行的CPU数;事实上,maxThreads的大小远大于CPU内核的数量。这是因为处理请求的线程实际花在计算上的时间可能很少,大部分时间可能是阻塞的,比如等待数据库返回数据,等待硬盘读写数据等.因此,在某个时刻,只有少数线程真正在使用物理CPU,大部分线程都在等待;因此,线程数远大于物理内核数是合理的。也就是说,Tomcat可以通过使用远多于CPU核心数的线程数来保持CPU的忙碌,从而大大提高CPU的利用率。4.参数设置(1)maxThreads的设置不仅与应用的特性有关,还与服务器的CPU核数有关。从前面的介绍我们可以知道,maxThreads的数量应该远大于CPU核心的数量;CPU核心数越大,maxThreads应该越大;应用中CPU越不密集(IO越密集),maxThreads就应该越大,这样才能充分利用CPU。当然,maxThreads的值并不是越大越好。如果maxThreads太大,CPU会花费大量时间切换线程,整体效率会下降。(2)maxConnections的设置与Tomcat的运行模式有关。如果tomcat使用BIO,那么maxConnections的值要和maxThreads保持一致;如果tomcat使用NIO,那么类似于Tomcat的默认值,maxConnections的值应该比maxThreads大很多。(3)通过前面的介绍我们可以知道,虽然tomcat同时可以处理的连接数是maxConnections,但是服务器同时可以接收的连接数是maxConnections+acceptCount。acceptCount的设置与连接过高时应用程序想要如何响应有关。如果设置过大,后续传入请求的等待时间会很长;如果设置太小,后续传入的请求将立即返回连接被拒绝。3、线程池ExecutorExecutor元素代表Tomcat中的线程池,可以被其他组件共享;要使用线程池,组件需要通过executor属性指定线程池。Executor是Service元素的内联元素。一般来说,使用线程池的是Connector组件;为了让Connector使用线程池,Executor元素应该放在Connector的前面。Executor和Connector的配置如下:Executor的主要属性包括:name:线程池的标记maxThreads:线程池中的最大活动线程数,默认值为200(Tomcat7和8都是)minSpareThreads:线程池中保留的最小线程数,最小值为25maxIdleTime:线程空闲的最长时间,当空闲时间超过该值时,线程会被关闭(除非线程数小于minSpareThreads),单位是ms,默认Value60000(1分钟)daemon:后台线程与否,默认值truethreadPriority:线程优先级,默认值5namePrefix:prefixof线程名称,线程池中的线程名称为:namePrefix+threadnumber4.查看当前状态Tomcat在上面介绍了连接数和线程数的概念以及如何设置。下面介绍如何查看服务器中的连接数和线程数。查看服务器状态,大致分为两种方案:(1)使用现成的工具,(2)直接使用Linux命令查看。现成的工具,比如JDK自带的jconsole工具可以方便的查看线程信息(另外还可以查看CPU、内存、类、JVM基础信息等),Tomcat自带的manager,以及收费工具NewRelic等。下图是jconsole查看线程信息的界面:下面说说如何通过Linux命令行查看服务器中的连接数和线程数。1.连接数假设Tomcat在8083端口收到http请求,可以使用如下语句查看连接状态:netstat–nat|grep8083结果如下:监听状态,监听请求;否则另外还有4个已建立的连接(ESTABLISHED)和2个等待关闭的连接(CLOSE_WAIT)。2、线程ps命令可以查看进程状态。例如执行以下命令:ps–e|grepjava结果如下:可以看到只打印了一个进程信息;27989是线程id,java指的是执行的java命令。这是因为启动一个tomcat,所有的内部工作都是在这个进程中完成的,包括主线程、垃圾回收线程、Acceptor线程、请求处理线程等等。通过下面的命令,可以看到进程中有多少个线程;其中,nlwp表示轻量级进程数。ps–onlwp27989可以看到进程内部有73个线程;但73不排除处于空闲状态的线程。要获取实际运行的线程数,可以使用如下语句:ps-eLopid,stat|grep27989|greprunning|wc-l其中ps-eLopid,stat可以找出所有线程,并打印进程号和线程的当前状态;两个grep命令分别过滤进程号和线程状态;wc数数。其中,ps-eLopid,stat|的输出grep27989如下:图中只展示了部分结果;Sl表示大多数线程处于空闲状态。参考资料Tomcat7.0官方文档http://tomcat.apache.org/tomcat-7.0-doc/config/http.htmlTomcat8.0官方文档http://tomcat.apache.org/tomcat-8.0-doc/config/http。htmlTomcat8.5官方文档http://tomcat.apache.org/tomcat-8.0-doc/config/http.htmlTomcatmaxThreadsmaxConnectionsacceptCount参数说明http://www.jianshu.com/p/2bc3fca12623tomcat架构分析(connectorBIO实现)http://gearever.iteye.com/blog/1841586tomcat架构分析(connectorNIO实现)http://gearever.iteye.com/blog/1844203为什么tomcat默认线程池大小这么大?https://stackoverflow.com/questions/14249824/why-is-the-tomcat-default-thread-pool-size-so-large如何查找Tomcat当前线程数https://serverfault.com/questions/594877/howto-查找tomcat当前线程数