Tomcat的Connector组件

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来决定的,这一部分代码是这个样子。

/**
 * Set the Coyote protocol which will be used by the connector.
 *
 * @param protocol The Coyote protocol name
 */
public void setProtocol(String protocol) {

    if (AprLifecycleListener.isAprAvailable()) {
        if ("HTTP/1.1".equals(protocol)) {
            setProtocolHandlerClassName
                ("org.apache.coyote.http11.Http11AprProtocol");
        } else if ("AJP/1.3".equals(protocol)) {
            setProtocolHandlerClassName
                ("org.apache.coyote.ajp.AjpAprProtocol");
        } else if (protocol != null) {
            setProtocolHandlerClassName(protocol);
        } else {
            setProtocolHandlerClassName
                ("org.apache.coyote.http11.Http11AprProtocol");
        }
    } else {
        if ("HTTP/1.1".equals(protocol)) {
            setProtocolHandlerClassName
                ("org.apache.coyote.http11.Http11NioProtocol");
        } else if ("AJP/1.3".equals(protocol)) {
            setProtocolHandlerClassName
                ("org.apache.coyote.ajp.AjpNioProtocol");
        } else if (protocol != null) {
            setProtocolHandlerClassName(protocol);
        }
    }
}

大致概括一下,先判断是否启用APR,再根据Connector的protocol决定Handler的class。对于是否使用APR的判断,可以通过显式指定,而默认是通过Tomcat自动检测的,检测方式是通过AprListener判断指定的Library是否加载。

而在设置了对应的ProtocolHandler后,对于Connector的停用与启用,都是对Handler进行操作的。

/**
 * Pause the connector.
 */
public void pause() {
    try {
        protocolHandler.pause();
    } catch (Exception e) {}
}
/**
 * Pause the connector.
 */
public void resume() {
    try {
        protocolHandler.resume();
    } catch (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,否则创建自己的。

/**
 * Start the NIO endpoint, creating acceptor, poller threads.
 */
@Override
public void startInternal() throws Exception {

    if (!running) {
        running = true;
        paused = false;
        // Create worker collection  重点看这里,会判断是否存在Executor配置
        if ( getExecutor() == null ) {
            createExecutor();
        }
        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自己维护的线程池基本类似。

protected void startInternal() throws LifecycleException {
    taskqueue = new TaskQueue(maxQueueSize);
    TaskThreadFactory tf = new TaskThreadFactory(namePrefix,daemon,getThreadPriority());
    executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), maxIdleTime, TimeUnit.MILLISECONDS,taskqueue, tf);
    executor.setThreadRenewalDelay(threadRenewalDelay);
    if (prestartminSpareThreads) {
        executor.prestartAllCoreThreads();
    }
    taskqueue.setParent(executor);

    setState(LifecycleState.STARTING);
}

而请求处理时,会调用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的线程池相关内容,其它信息后续再谈。

扫描二维码,关注我!


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值