前言
笔者在解决业务的问题中发现,如果Tomcat在并发延迟突变高后,很容易线程数量飙高,而且短时间是降不下去的,除非重启,但是不能线程飙高就重启吧,实际上是线程池的逻辑,然而Tomcat开放的配置是限制的。
1. Tomcat运行的线程配置
Tomcat运行过程一般都是在server.xml中配置catalina-exec-xxx和http-nio-8080-xxx
另外的配置
2. Tomcat线程飙高很久的原因
线程池的线程数量要超过core数量必须要正在执行的任务线程数达到core且队列打满,才会启动max-core的线程做最后的挣扎😄。
根据这个原理: Tomcat线程数超过core必须是持续并发超过core,但是又是max线程数能接受的范畴。
根据Tomcat 8.5.73 Tomcat启动创建的源码,创建catalina-exec线程池的代码:
org.apache.catalina.core.StandardThreadExecutor
startInternal,根据注释是
org.apache.catalina.util.LifecycleBase#startInternal()
/**
* Start the component and implement the requirements
* of {@link org.apache.catalina.util.LifecycleBase#startInternal()}.
*
* @exception LifecycleException if this component detects a fatal error
* that prevents this component from being used
*/
@Override
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);
}
可以看到可以调节core max max超时时间,以及队列数,均可以调节。
创建http-nio-8080-xxx的代码,实际上Tomcat的http处理就是这个线程池最终执行的
org.apache.tomcat.util.net.AbstractEndpoint
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);
}
可以看到队列是不可以调节的无界队列,只能设置core和max线程数,其他参数都不可更改。
public class TaskQueue extends LinkedBlockingQueue<Runnable> {
private static final long serialVersionUID = 1L;
protected static final StringManager sm = StringManager.getManager(TaskQueue.class);
private static final int DEFAULT_FORCED_REMAINING_CAPACITY = -1;
private transient volatile ThreadPoolExecutor parent = null;
// No need to be volatile. This is written and read in a single thread
// (when stopping a context and firing the listeners)
private int forcedRemainingCapacity = -1;
public TaskQueue() {
super();
}
-----------------------------------
/**
* Creates a {@code LinkedBlockingQueue} with a capacity of
* {@link Integer#MAX_VALUE}.
*/
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
前缀的来源
org.apache.coyote.AbstractProtocol.
getNamePrefix
选择一个NIO
protected String getNamePrefix() {
if (isSSLEnabled()) {
return ("https-" + getSslImplementationShortName()+ "-nio");
} else {
return ("http-nio");
}
}
3. Tomcat的调节
实际上知道源码就可以很简单的处理问题,调节参数就可以了,一般的调节core的数量,调大一些就可以提高并发量,另外catalina-exec-xxx可以调节max超时时间和队列数,来达到尽量不把队列打满的情况。
总结
实际上Tomcat在演进中,为了提高并发量,IO设计经过了几次迭代BIO NIO NIO2 APR
BIO
阻塞式IO,通过输入输出流的方式,通过线程并发,IO能力弱,已经被Tomcat默认不选择。
NIO/NIO2
NIO是多路复用,一个线程复用多个socket连接,NIO2增加了AIO的支持,Tomcat分别采用Future方式实现阻塞读写,采用CompletionHandler方式实现非阻塞读写。
Tomcat8默认NIO方式,也是通用的配置,根据实际可以配置NIO2.
APR
采用底层JNI的模式IO,性能最强,但是依赖native的包:Apache Tomcat Native Library - Documentation Index