Tomcat容器的组件,可以归结为两大类,一类是Container,一类则是Connector。
Connector,也称为通道或连接器,说的都是Tomcat中用于处理请求与响应的组件。
该组件在Tomcat中的作用可以说是至关重要的。所有的请求与响应,都是经过Connector,才转到对应的容器中进行处理的。就像老毛形容武汉长江大桥建成时说
一桥飞架南北,大堑变通途。
Connector也可以用这句来形容。
我们在Tomcat的配置文件server.xml中一定见到过以下配置
<!-- Define an AJP 1.3 Connector on port 8009 -->
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
其定义了两个Connector。分别是AJP通道和HTTP通道。
A "Connector" represents an endpoint by which requests are received
and responses are returned.
而对于Connector,我们又可以设置是否使用加密,超时时间等,就像通往一个地方的道路很多,可以通过高速、国道、铁路分别到达目的地。Connector就像我们的道路一样,让请求和响应运行在其上。
而我们经常遇到到乱码一类的问题,除自身应用的编码之外,也是和Connector的配置紧密相关的。(可以看深度揭秘乱码问题背后的原因及解决方式 和 乱码问题补充了解。)
Tomcat内部提供的Connector有多种,各有各的特点,如下图(图片来自官方文档)
我们看到除了是否阻塞之外,还有对于SSL的实现不同等,每种Connector都有不同的特性,本次我们来分析下常用Connector都支持的线程池。
对于Connector组件, 我们在server.xml中的配置,是以Connector这个类来表示的,而具体对于请求的接收等,是由每个通道配置的protocol来决定的,这一部分代码是这个样子。
setProtocol(String protocol) { (AprLifecycleListener.()) { (.equals(protocol)) { setProtocolHandlerClassName (); } (.equals(protocol)) { setProtocolHandlerClassName (); } (protocol != ) { setProtocolHandlerClassName(protocol); } { setProtocolHandlerClassName (); } } { (.equals(protocol)) { setProtocolHandlerClassName (); } (.equals(protocol)) { setProtocolHandlerClassName (); } (protocol != ) { setProtocolHandlerClassName(protocol); } } }
大致概括一下,先判断是否启用APR,再根据Connector的protocol决定Handler的class。对于是否使用APR的判断,可以通过显式指定,而默认是通过Tomcat自动检测的,检测方式是通过AprListener判断指定的Library是否加载。
而在设置了对应的ProtocolHandler后,对于Connector的停用与启用,都是对Handler进行操作的。
pause() { { .pause(); } (Exception e) {} } resume() { { .resume(); } (Exception e) {} }
上面注释说Connector代表了一种Endpoint,所以Handler内部是抽象了一个Endpoint类进行请求接收与处理的。每种Connector对应不同的Endpoint,以Tomcat7及以后默认的非阻塞Connector 为例,其对应的Handler是这个class
Http11NioProtocol
而Endpoint是NioEndpoint。
如果采用默认的配置使用Tomcat,此时,我们会观察到,每次启动时,每个Connector都会启动多个线程用于请求的处理。这些线程默认也都使用了线程池,每个Connector维护了自己的threadpool。
而在server.xml中,我们可能会发现这样一段注释掉的配置:
<!--The connectors can use a shared executor, you can define one or more named thread pools-->
<!--
<Executor name="tomcatThreadPool" namePrefix="catalina-exec-"
maxThreads="150" minSpareThreads="4"/>
-->
说明也写的很明白,即这些Connector可以共用一个线程池。
而在具体的Connector启动的时候,会判断当前配置是否存在Executor,如果有就使用公共的threadpool,否则创建自己的。
startInternal() Exception { (!) { = ; = ; ( getExecutor() == ) { } initializeConnectionLatch(); }
线程池的创建方式如下:
public void createExecutor() {
internalExecutor = true;
TaskQueue taskqueue = new TaskQueue();
TaskThreadFactory tf = new TaskThreadFactory(getName() + "-exec-", daemon, getThreadPriority());
executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), 60, TimeUnit.SECONDS,taskqueue, tf);
taskqueue.setParent( (ThreadPoolExecutor) executor);
}
而对于使用共享线程池的配置,是使用StandardThreadExecutor这个类来表示,在解析配置时,根据对应的配置项,来决定是否初始化并启动之。其启动代码如下,我们看到和各个Connector自己维护的线程池基本类似。
startInternal() LifecycleException { = TaskQueue(); TaskThreadFactory tf = TaskThreadFactory(,,getThreadPriority()); = ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), , TimeUnit.,, tf); .setThreadRenewalDelay(); () { .prestartAllCoreThreads(); } .setParent(); setState(LifecycleState.); }
而请求处理时,会调用Executor进行处理,此时如果配置了共享的Executor,就会使用共享的threadpool处理,否则使用自己的去处理。
我们看到,在使用共享线程池时,对应的线程栈如下,
而独立线程池是,是这样的
而对于使用Executor,也非常容易,只需要声明Executor,同时将需要使用它的Connector加上对应的threadpool属性即可。
<Executor name="executor" namePrefix="catalina-exec-"
maxThreads="150" minSpareThreads="4"/>
<Connector port="8080" protocol="HTTP/1.1"threadpool="executor"/>
以上为Connector的线程池相关内容,其它信息后续再谈。