Tomcat支持的并发请求数量
前置说明
这里的Tomcat,指的是默认SpringBoot内嵌Tomcat(不修改任何配置)。
对于“Tomcat默认可以处理多少个请求”的解读,按照时间颗粒度不同,有两种理解角度,一种是并行处理,另一种是并发处理(QPS)。
- 并行处理 Tomcat的请求处理线程池,默认设置的“最大线程数”(默认maximumPoolSize为200),即Tomcat支持并行处理的请求数。 org.apache.tomcat.util.net.AbstractEndpoint
- 并发处理(QPS) 单位时间1s内,基于Tomcat最大线程数(200),能接受处理的请求数。 假如一个请求响应时间(RT)为200ms,则默认Tomcat的理想QPS为1000。
源码走读
SpringBoot启动
Tomcat容器的启动位于上下文刷新的
finishRefresh()
方法中。
执行栈帧
finishRefresh()方法
WebServer接口为服务容器根接口,SpringBoot默认容器为Tomcat。
Tomcat容器启动
执行堆栈
直接跟踪到线程池创建部分
publicvoidcreateExecutor(){
internalExecutor =true;// 创建自定义等待队列TaskQueue taskqueue =newTaskQueue();// 创建 线程生成工程TaskThreadFactory tf =newTaskThreadFactory(getName()+"-exec-", daemon,getThreadPriority());// 创建请求处理线程池
executor =newThreadPoolExecutor(getMinSpareThreads(),getMaxThreads(),60,TimeUnit.SECONDS,taskqueue, tf);// 保存线程池到队列属性中
taskqueue.setParent((ThreadPoolExecutor) executor);}
这便是Tomcat请求处理线程池的创建,debug该线程池的属性
可以看得出来,线程池核心线程数为10,最大线程数为200。
对于该线程池的等待队列,是Tomcat自定义的TaskQueue(上面的org.apache.tomcat.util.net.AbstractEndpoint#createExecutor方法中创建)。
自定义等待队列
TaskQueue
中有一个属性
parent
,通过重写队列的
org.apache.tomcat.util.threads.TaskQueue#offer
方法,实现请求拒绝策略。
publicclassTaskQueueextendsLinkedBlockingQueue<Runnable>{privatevolatileThreadPoolExecutor parent =null;@Overridepublicbooleanoffer(Runnable o){//we can't do any checks// 如果未设置线程池,则直接入队if(parent==null)returnsuper.offer(o);//we are maxed out on threads, simply queue the object// 如果线程数 == 最大线程数,则直接入队if(parent.getPoolSize()== parent.getMaximumPoolSize())returnsuper.offer(o);//we have idle threads, just add it to the queue// 如果已提交任务数(提交但未完成) <= 线程数,直接入队if(parent.getSubmittedCount()<=(parent.getPoolSize()))returnsuper.offer(o);//if we have less threads than maximum force creation of a new thread// -----------------------------------------------------------------// 如果线程数 < 最大线程数,不入队,返回false,强制创建新的工作线程// -----------------------------------------------------------------if(parent.getPoolSize()<parent.getMaximumPoolSize())returnfalse;//if we reached here, we need to add it to the queue// 以上条件不符合,则直接入队returnsuper.offer(o);}}
这里可以看到,Tomcat的请求线程池的任务提交和常规的线程池任务提交有一定区别,
常规线程池的等待队列,基本只负责按照队列属性进行入队,直接返回入队成功或者失败。
而Tomcat的线程池任务等待队列,在常规线程池任务提交逻辑上,加入了线程池本身的运行状态作为入队的判断条件,最主要的一个差别是:
Tomcat中,如果线程数小于最大核心线程数,不管等待队列是否可以入队,会直接创建新的工作线程执行任务。
常规线程池的做法是,等待队列满了才会创建新的工作线程去执行任务。
Tomcat线程池处理请求
publicabstractclassAbstractEndpoint<S>{publicbooleanprocessSocket(SocketWrapperBase<S> socketWrapper,SocketEvent event,boolean dispatch){try{if(socketWrapper ==null){returnfalse;}// 获取缓存中的socket任务SocketProcessorBase<S> sc = processorCache.pop();if(sc ==null){// 缓存为空,则创建(拉取)新的socket任务
sc =createSocketProcessor(socketWrapper, event);}else{// 缓存不为空,则重置socket事件
sc.reset(socketWrapper, event);}// 获取线程池Executor executor =getExecutor();if(dispatch && executor !=null){// 提交任务
executor.execute(sc);}else{
sc.run();}}catch(RejectedExecutionException ree){getLog().warn(sm.getString("endpoint.executor.fail", socketWrapper), ree);returnfalse;}catch(Throwable t){ExceptionUtils.handleThrowable(t);// This means we got an OOM or similar creating a thread, or that// the pool and its queue are fullgetLog().error(sm.getString("endpoint.process.fail"), t);returnfalse;}returntrue;}}
SocketProcessorBase
为Socket处理任务基础抽象类。
通过继承该类,实现
doRun()
抽象方法,实现不同的线程模型。
debug可知,Tomcat默认使用的线程模型为NIO。
官网线程模型
链接:https://tomcat.apache.org/tomcat-8.0-doc/config/http.html
注意,这里的阻塞非阻塞,是针对请求的读取和响应,而非请求本身的逻辑(Controller方法)。
总结
Tomcat默认最大并行处理数为200,并发处理数 QPS = 200 * (1000ms / 平均RT)。
版权归原作者 二生三 所有, 如有侵权,请联系我们删除。