“Tomcat最大支持多少个请求”
作者:mmseoamin日期:2024-02-22

Tomcat支持的并发请求数量

  • 前置说明
  • 源码走读
    • SpringBoot启动
    • Tomcat容器启动
    • Tomcat线程池处理请求
    • 总结

      前置说明

      这里的Tomcat,指的是默认SpringBoot内嵌Tomcat(不修改任何配置)。

      对于“Tomcat默认可以处理多少个请求”的解读,按照时间颗粒度不同,有两种理解角度,一种是并行处理,另一种是并发处理(QPS)。

      • 并行处理

        Tomcat的请求处理线程池,默认设置的“最大线程数”(默认maximumPoolSize为200),即Tomcat支持并行处理的请求数。

        org.apache.tomcat.util.net.AbstractEndpoint

        “Tomcat最大支持多少个请求”,在这里插入图片描述,第1张

      • 并发处理(QPS)

        单位时间1s内,基于Tomcat最大线程数(200),能接受处理的请求数。

        假如一个请求响应时间(RT)为200ms,则默认Tomcat的理想QPS为1000。

        源码走读

        SpringBoot启动

        Tomcat容器的启动位于上下文刷新的finishRefresh()方法中。

        执行栈帧

        “Tomcat最大支持多少个请求”,在这里插入图片描述,第2张

        finishRefresh()方法

        “Tomcat最大支持多少个请求”,在这里插入图片描述,第3张

        “Tomcat最大支持多少个请求”,在这里插入图片描述,第4张

        WebServer接口为服务容器根接口,SpringBoot默认容器为Tomcat。

        “Tomcat最大支持多少个请求”,在这里插入图片描述,第5张

        Tomcat容器启动

        执行堆栈

        “Tomcat最大支持多少个请求”,在这里插入图片描述,第6张

        直接跟踪到线程池创建部分

        “Tomcat最大支持多少个请求”,在这里插入图片描述,第7张

            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);
            }
        

        这便是Tomcat请求处理线程池的创建,debug该线程池的属性

        “Tomcat最大支持多少个请求”,在这里插入图片描述,第8张

        可以看得出来,线程池核心线程数为10,最大线程数为200。

        对于该线程池的等待队列,是Tomcat自定义的TaskQueue(上面的org.apache.tomcat.util.net.AbstractEndpoint#createExecutor方法中创建)。

        自定义等待队列TaskQueue中有一个属性parent,通过重写队列的org.apache.tomcat.util.threads.TaskQueue#offer方法,实现请求拒绝策略。

        public class TaskQueue extends LinkedBlockingQueue {
            private volatile ThreadPoolExecutor parent = null;
            @Override
            public boolean offer(Runnable o) {
              	//we can't do any checks
              	// 如果未设置线程池,则直接入队
                if (parent==null) return super.offer(o);
                //we are maxed out on threads, simply queue the object
                // 如果线程数 == 最大线程数,则直接入队
                if (parent.getPoolSize() == parent.getMaximumPoolSize()) return super.offer(o);
                //we have idle threads, just add it to the queue
                // 如果已提交任务数(提交但未完成) <= 线程数,直接入队
                if (parent.getSubmittedCount()<=(parent.getPoolSize())) return super.offer(o);
                //if we have less threads than maximum force creation of a new thread
                // -----------------------------------------------------------------
                // 如果线程数 < 最大线程数,不入队,返回false,强制创建新的工作线程
                // -----------------------------------------------------------------
                if (parent.getPoolSize() 
        

        这里可以看到,Tomcat的请求线程池的任务提交和常规的线程池任务提交有一定区别,

        常规线程池的等待队列,基本只负责按照队列属性进行入队,直接返回入队成功或者失败。

        而Tomcat的线程池任务等待队列,在常规线程池任务提交逻辑上,加入了线程池本身的运行状态作为入队的判断条件,最主要的一个差别是:

        Tomcat中,如果线程数小于最大核心线程数,不管等待队列是否可以入队,会直接创建新的工作线程执行任务。

        常规线程池的做法是,等待队列满了才会创建新的工作线程去执行任务。

        Tomcat线程池处理请求

        public abstract class AbstractEndpoint {
            public boolean processSocket(SocketWrapperBase socketWrapper,
                    SocketEvent event, boolean dispatch) {
                try {
                    if (socketWrapper == null) {
                        return false;
                    }
                    // 获取缓存中的socket任务
                    SocketProcessorBase 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);
                    return false;
                } 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 full
                    getLog().error(sm.getString("endpoint.process.fail"), t);
                    return false;
                }
                return true;
            }
        }
        

        SocketProcessorBase为Socket处理任务基础抽象类。

        “Tomcat最大支持多少个请求”,在这里插入图片描述,第9张

        通过继承该类,实现doRun()抽象方法,实现不同的线程模型。

        debug可知,Tomcat默认使用的线程模型为NIO。

        “Tomcat最大支持多少个请求”,在这里插入图片描述,第10张

        官网线程模型

        链接:https://tomcat.apache.org/tomcat-8.0-doc/config/http.html

        “Tomcat最大支持多少个请求”,在这里插入图片描述,第11张

        注意,这里的阻塞非阻塞,是针对请求的读取和响应,而非请求本身的逻辑(Controller方法)。

        总结

        Tomcat默认最大并行处理数为200,并发处理数 QPS = 200 * (1000ms / 平均RT)。