服务器之家:专注于服务器技术及软件下载分享
分类导航

云服务器|WEB服务器|FTP服务器|邮件服务器|虚拟主机|服务器安全|DNS服务器|服务器知识|Nginx|IIS|Tomcat|

服务器之家 - 服务器技术 - Tomcat - 基于tomcat的连接数与线程池详解

基于tomcat的连接数与线程池详解

2021-08-26 16:03编程迷思 Tomcat

下面小编就为大家带来一篇基于tomcat的连接数与线程池详解。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧

前言

在使用tomcat时,经常会遇到连接数、线程数之类的配置问题,要真正理解这些概念,必须先了解tomcat的连接器(connector)。

在前面的文章 详解tomcat配置文件server.xml 中写到过:connector的主要功能,是接收连接请求,创建request和response对象用于和请求端交换数据;然后分配线程让engine(也就是servlet容器)来处理这个请求,并把产生的request和response对象传给engine。当engine处理完请求后,也会通过connector将响应返回给客户端。

可以说,servlet容器处理请求,是需要connector进行调度和控制的,connector是tomcat处理请求的主干,因此connector的配置和使用对tomcat的性能有着重要的影响。这篇文章将从connector入手,讨论一些与connector有关的重要问题,包括nio/bio模式、线程池、连接数等。

根据协议的不同,connector可以分为http connector、ajp connector等,本文只讨论http connector。

一、nio、bio、apr

1、connector的protocol

connector在处理http请求时,会使用不同的protocol。不同的tomcat版本支持的protocol不同,其中最典型的protocol包括bio、nio和apr(tomcat7中支持这3种,tomcat8增加了对nio2的支持,而到了tomcat8.5和tomcat9.0,则去掉了对bio的支持)。

bio是blocking io,顾名思义是阻塞的io;nio是non-blocking io,则是非阻塞的io。而apr是apache portable runtime,是apache可移植运行库,利用本地库可以实现高可扩展性、高性能;apr是在tomcat上运行高并发应用的首选模式,但是需要安装apr、apr-utils、tomcat-native等包。

2、如何指定protocol

connector使用哪种protocol,可以通过<connector>元素中的protocol属性进行指定,也可以使用默认值。

指定的protocol取值及对应的协议如下:

http/1.1:默认值,使用的协议与tomcat版本有关

org.apache.coyote.http11.http11protocol:bio
org.apache.coyote.http11.http11nioprotocol:nio
org.apache.coyote.http11.http11nio2protocol:nio2
org.apache.coyote.http11.http11aprprotocol:apr

如果没有指定protocol,则使用默认值http/1.1,其含义如下:在tomcat7中,自动选取使用bio或apr(如果找到apr需要的本地库,则使用apr,否则使用bio);在tomcat8中,自动选取使用nio或apr(如果找到apr需要的本地库,则使用apr,否则使用nio)。

3、bio/nio有何不同

无论是bio,还是nio,connector处理请求的大致流程是一样的:

在accept队列中接收连接(当客户端向服务器发送请求时,如果客户端与os完成三次握手建立了连接,则os将该连接放入accept队列);在连接中获取请求的数据,生成request;调用servlet容器处理请求;返回response。为了便于后面的说明,首先明确一下连接与请求的关系:连接是tcp层面的(传输层),对应socket;请求是http层面的(应用层),必须依赖于tcp的连接实现;一个tcp连接中可能传输多个http请求。

在bio实现的connector中,处理请求的主要实体是jioendpoint对象。jioendpoint维护了acceptor和worker:acceptor接收socket,然后从worker线程池中找出空闲的线程处理socket,如果worker线程池没有空闲线程,则acceptor将阻塞。其中worker是tomcat自带的线程池,如果通过<executor>配置了其他线程池,原理与worker类似。

在nio实现的connector中,处理请求的主要实体是nioendpoint对象。nioendpoint中除了包含acceptor和worker外,还是用了poller,处理流程如下图所示(图片来源:http://gearever.iteye.com/blog/1844203)。

基于tomcat的连接数与线程池详解

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),而长连接意味着,一个tcp的socket在当前请求结束后,如果没有新的请求到来,socket不会立马释放,而是等timeout后再释放。如果使用bio,“读取socket并交给worker中的线程”这个过程是阻塞的,也就意味着在socket等待下一个请求或等待释放的过程中,处理这个socket的工作线程会一直被占用,无法释放;因此tomcat可以同时处理的socket数目不能超过最大线程数,性能受到了极大限制。而使用nio,“读取socket并交给worker中的线程”这个过程是非阻塞的,当socket在等待下一个请求或等待释放时,并不会占用工作线程,因此tomcat可以同时处理的socket数目远大于最大线程数,并发性能大大提高。

二、3个参数:acceptcount、maxconnections、maxthreads

再回顾一下tomcat处理请求的过程:在accept队列中接收连接(当客户端向服务器发送请求时,如果客户端与os完成三次握手建立了连接,则os将该连接放入accept队列);在连接中获取请求的数据,生成request;调用servlet容器处理请求;返回response

相对应的,connector中的几个参数功能如下:

1、acceptcount

accept队列的长度;当accept队列中连接的个数达到acceptcount时,队列满,进来的请求一律被拒绝。默认值是100。

2、maxconnections

tomcat在任意时刻接收和处理的最大连接数。当tomcat接收的连接数达到maxconnections时,acceptor线程不会读取accept队列中的连接;这时accept队列中的线程会一直阻塞着,直到tomcat接收的连接数小于maxconnections。如果设置为-1,则连接数不受限制。

默认值与连接器使用的协议有关:nio的默认值是10000,apr/native的默认值是8192,而bio的默认值为maxthreads(如果配置了executor,则默认值是executor的maxthreads)。

在windows下,apr/native的maxconnections值会自动调整为设置值以下最大的1024的整数倍;如设置为2000,则最大值实际是1024。

3、maxthreads

请求处理线程的最大数量。默认值是200(tomcat7和8都是的)。如果该connector绑定了executor,这个值会被忽略,因为该connector将使用绑定的executor,而不是内置的线程池来执行任务。

maxthreads规定的是最大的线程数目,并不是实际running的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的设置,与应用在连接过高情况下希望做出什么反应有关系。如果设置过大,后面进入的请求等待时间会很长;如果设置过小,后面进入的请求立马返回connection refused。

三、线程池executor

executor元素代表tomcat中的线程池,可以由其他组件共享使用;要使用该线程池,组件需要通过executor属性指定该线程池。

executor是service元素的内嵌元素。一般来说,使用线程池的是connector组件;为了使connector能使用线程池,executor元素应该放在connector前面。executor与connector的配置举例如下:

?
1
2
<executor name="tomcatthreadpool" nameprefix ="catalina-exec-" maxthreads="150" minsparethreads="4" />
<connector executor="tomcatthreadpool" port="8080" protocol="http/1.1" connectiontimeout="20000" redirectport="8443" acceptcount="1000" />

executor的主要属性包括:

name:该线程池的标记

maxthreads:线程池中最大活跃线程数,默认值200(tomcat7和8都是)

minsparethreads:线程池中保持的最小线程数,最小值是25

maxidletime:线程空闲的最大时间,当空闲超过该值时关闭线程(除非线程数小于minsparethreads),单位是ms,默认值60000(1分钟)

daemon:是否后台线程,默认值true

threadpriority:线程优先级,默认值5

nameprefix:线程名字的前缀,线程池中线程名字为:nameprefix+线程编号

四、查看当前状态

上面介绍了tomcat连接数、线程数的概念以及如何设置,下面说明如何查看服务器中的连接数和线程数。

查看服务器的状态,大致分为两种方案:(1)使用现成的工具,(2)直接使用linux的命令查看。

现成的工具,如jdk自带的jconsole工具可以方便的查看线程信息(此外还可以查看cpu、内存、类、jvm基本信息等),tomcat自带的manager,收费工具new relic等。下图是jconsole查看线程信息的界面:

基于tomcat的连接数与线程池详解

下面说一下如何通过linux命令行,查看服务器中的连接数和线程数。

1、连接数

假设tomcat接收http请求的端口是8083,则可以使用如下语句查看连接情况:

?
1
netstat –nat | grep 8083

结果如下所示:

基于tomcat的连接数与线程池详解

可以看出,有一个连接处于listen状态,监听请求;除此之外,还有4个已经建立的连接(established)和2个等待关闭的连接(close_wait)。

2、线程

ps命令可以查看进程状态,如执行如下命令:

?
1
ps –e | grep java

结果如下图:

基于tomcat的连接数与线程池详解

可以看到,只打印了一个进程的信息;27989是线程id,java是指执行的java命令。这是因为启动一个tomcat,内部所有的工作都在这一个进程里完成,包括主线程、垃圾回收线程、acceptor线程、请求处理线程等等。

通过如下命令,可以看到该进程内有多少个线程;其中,nlwp含义是number of light-weight process。

?
1
ps –o nlwp 27989

基于tomcat的连接数与线程池详解

可以看到,该进程内部有73个线程;但是73并没有排除处于idle状态的线程。要想获得真正在running的线程数量,可以通过以下语句完成:

?
1
ps -elo pid ,stat | grep 27989 | grep running | wc -l

其中ps -elo pid ,stat可以找出所有线程,并打印其所在的进程号和线程当前的状态;两个grep命令分别筛选进程号和线程状态;wc统计个数。其中,ps -elo pid ,stat | grep 27989输出的结果如下:

基于tomcat的连接数与线程池详解

图中只截图了部分结果;sl表示大多数线程都处于空闲状态。

以上这篇基于tomcat的连接数与线程池详解就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持服务器之家。

原文链接:http://www.cnblogs.com/kismetv/archive/2017/11/09/7806063.html

延伸 · 阅读

精彩推荐