前言
这一章介绍连接器和连接池的相关配置,关于线程池在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()
使用线程池
在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);
}