水煮Tomcat(五) – 线程池相关

143 阅读3分钟

前言

这一章介绍连接器和连接池的相关配置,关于线程池在Tomcat中的定位和作用,通过前几个章节,已经有说明过,这里不再赘述。

配置说明

Tomcat连接池配置方式有两种,在server.xml配置文件中可以很直观的看出来。
配置文件中默认被注释掉的< Executor >元素,就是用来注册外部线程池的;而我们常用的线程池配置,是在< Connector >元素中设置。

  • maxThreads:最大线程数;
  • minSpareThreads:闲置线程数;
<Server port="8005" shutdown="SHUTDOWN">
  <Service name="Catalina">

    <--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 port="8080" protocol="HTTP/1.1" redirectPort="8443" connectionTimeout="20000" maxThreads="500" minSpareThreads="10" 
               maxPostSize="52428800" URIEncoding="UTF-8" />
    <Engine name="Catalina" defaultHost="localhost">
      <-- ... 忽略一些配置 -->
    </Engine>
  </Service>
</Server>

完整配置

连接器的配置是决定 Tomcat 性能的关键,在一般情况下使用默认的就可以了,但是在程序比较吃力时,就需要手动配置它来提高效率,完整的配置如下所示,这里列举的都是SpringBoot中的默认配置

<Connector port="8080" 
    protocol="HTTP/1.1" 
    executor="tomcatThreadPool" 
    maxThreads="200" 
    minSpareThreads="10" 
    acceptCount="100" 
    maxConnections="8192" 
    connectionTimeout="20000" 
    compression="off" 
    compressionMinSize="2048" 
    disableUploadTimeout="true" 
    redirectPort="8443" 
    URIEncoding="UTF-8" />

参数说明:

  • maxThreads:表示Tomcat可创建的最大的线程数;
  • minSpareThreads:最小空闲线程数,Tomcat初始化时创建的线程数,该值应该少于maxThreads,缺省值为4;
  • acceptCount:指定当所有可以使用的处理请求的线程数都被使用时,可以放到处理队列中的请求数,超过这个数的请求将不予处理,默认为10个;
  • maxConnections:服务器在任何给定时间接受和处理的最大连接数。
  • connectionTimeout:网络连接超时时间,单位为毫秒,如果设置为“0”则表示永不超时,不建议这样设置;
  • compression:默认为 off,开启是连接器在试图节省服务器的带宽使用 HTTP/1.1 GZIP 压缩。关闭会自动在压缩和传输之间进行权衡。
  • compressionMinSize:在 compression 开启时,可以通过这个来配置进行压缩的最小数据量。默认为 "2048"。
  • disableUploadTimeout:上传文件时是否使用超时机制,默认开启,由 ConnectionTimeout 决定,如果为 false,那么只会在设置的 connectionUploadTimeout 设置的时间后才会断开。
  • redirectPort:如果此连接器支持非 SSL 请求,并且收到匹配需要 SSL 传输的请求,Catalina 将自动将请求重定向到此处指定的端口号。

SpringBoot默认值

代码位置:ServerProperties.getTomcat()
image.png

使用线程池

在AbstractEndpoint代码的processSocket方法,可以看到tomcat监听到请求之后,会获取连接池来处理网络请求。
当然也可以不分发请求,那样就是新开线程处理了,一般不会使用。
来看看dispatch的注释说明
Should the processing be performed on a new container thread

public boolean processSocket(SocketWrapperBase<S> socketWrapper,
            SocketEvent event, boolean dispatch) {
    try {
        SocketProcessorBase<S> sc = createSocketProcessor(socketWrapper, event);
        // 获取连接池
        Executor executor = getExecutor();
        if (dispatch && executor != null) {
            // 用连接池处理网络请求
            executor.execute(sc);
        } else {
            // 新开线程执行,一般不会执行到此分支
            sc.run();
        }
    } catch (Throwable t) {
        ExceptionUtils.handleThrowable(t);
        return false;
    }
    return true;
}

外部线程池

SpringBoot

在SpringBoot代码中也可以用注册bean的方式来设置外部线程池。

    @Bean
    public TomcatProtocolHandlerCustomizer<?> tomcatProtocolHandlerCustomizer() {

        return protocolHandler -> {
            protocolHandler.setExecutor(
                    Executors.newFixedThreadPool(19,
                            new NamedThreadFactory("my-test-pool")));
        };
    }

怎么生效

如果在应用中注册了这个bean,会在tomcat启动时,将此外部线程池设置进入服务
AbstractProtocol.setExecutor -> AbstractEndpoint.setExecutor

/**
 * External Executor based thread pool.
 */
private Executor executor = null;
public void setExecutor(Executor executor) {
    this.executor = executor;
    // 是否用的内部线程池,这里设置为false
    this.internalExecutor = (executor == null);
}
public Executor getExecutor() { return executor; }

内部线程池

SpringBoot

在application.yml中配置线程池数量

server:
  port: 8080
  tomcat:
    max-threads: 64
    min-spare-threads: 16

怎么生效

在启动NioPoint时,在startInternal方法内,如果没有设置外部线程池,会读取配置文件中的maxThreads和minSpareThreads属性,初始化内部线程池。

/**
 * Start the NIO endpoint, creating acceptor, poller threads.
 */
@Override
public void startInternal() throws Exception {
	// ... 忽略
	// 如果没有设置外部线程池,此处会创建内部线程池
	if (getExecutor() == null) {
		// 创建内部线程池
	    createExecutor();
	}
	// ... 忽略
	// 启动Acceptor线程【单线程】,前文介绍过,此处就不多说明了
	startAcceptorThread();
}

// 创建内部线程池
public void createExecutor() {
    internalExecutor = true;
    TaskQueue taskqueue = new TaskQueue();
    TaskThreadFactory tf = new TaskThreadFactory(getName() + "-exec-", daemon, getThreadPriority());
    // 读取配置文件中的maxThreads和minSpareThreads属性,进行初始化线程池
    executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), 60, TimeUnit.SECONDS,taskqueue, tf);
    taskqueue.setParent( (ThreadPoolExecutor) executor);
}