Spring Boot内嵌Tomcat处理请求的链接数和线程数

Spring Boot内嵌Tomcat处理请求的连接数和线程数

处理请求的连接数和线程数配置

Spring Boot的配置项

properties 复制代码
#等待连接数
server.tomcat.accept-count=100
#最大链连接数
server.tomcat.max-connections=8192
properties 复制代码
#最小备用线程数
server.tomcat.threads.min-spare=10
#最大工作线程数
server.tomcat.threads.max=200

TomCat中的NIO模式的工作流程,主要分为Acceptor、Poller、Processor

Acceptor监听网络连接,有连接进来后注册在Poller中,Poller通过轮询检测连接中的读写事件,有事件发生时调用Processor进行请求处理。如下图:

源码说明(Spring Boot-2.7.18内嵌Tomcat-9.0.83)

线程数

线程数对应的是NIO模式图中的Executor,默认最大线程是200,核心线程数10。

  • server.tomcat.threads.min-spare 对应ThreadPoolExecutor的核心线程数
  • server.tomcat.threads.max 对应ThreadPoolExecutor的最大线程数
org.apache.tomcat.util.net.AbstractEndpoint
java 复制代码
public void createExecutor() {
    internalExecutor = true;
    if (getUseVirtualThreads()) {
        executor = new VirtualThreadExecutor(getName() + "-virt-");
    } else {
    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);
    }
}

ThreadPoolExecutor就是Tomcat的NIO模式下创建的线程池,其中getMinSpareThreads() getMaxThreads()分别获取的是核心线程数和最大线程数,对应的就是server.tomcat.threads.min-spareserver.tomcat.threads.max

org.apache.tomcat.util.threads.TaskQueue

创建线程池的时候创建了一个TaskQueue ,任务队列的处理逻辑主要在这里,下面分析一下TaskQueue的入队方法:

java 复制代码
public boolean offer(Runnable o) {
    //1
    if (parent==null) {
        return super.offer(o);
    }
    //2
    if (parent.getPoolSizeNoLock() == parent.getMaximumPoolSize()) {
        return super.offer(o);
    }
    //3
    if (parent.getSubmittedCount() <= parent.getPoolSizeNoLock()) {
        return super.offer(o);
    }
    //4
    if (parent.getPoolSizeNoLock() < parent.getMaximumPoolSize()) {
        return false;
    }
    //5
    return super.offer(o);
}
  1. 如果parent(由 taskqueue.setParent( (ThreadPoolExecutor) executor)设置)属性为null,直接将任务添加到队列中等待执行
  2. 如果当前线程池中的活动线程数等于最大线程数,直接将任务添加到队列中等待执行
  3. 如果已提交但未开始执行的任务数小于或等于当前线程数,说明有足够的空闲线程来处理新任务,直接将任务添加到队列中等待执行
  4. 如果当前线程数大于最小线程数且小于最大线程数,需要创建新的线程来处理任务。返回false表示入队失败,ThreadPoolExecutor在检测到这种情况时会尝试创建一个新的线程来执行任务
  5. 线程池中有足够的资源或者队列尚未满时,直接将任务添加到队列中等待执行
org.apache.tomcat.util.threads.ThreadPoolExecutor

下面分析一下返回false的情况

java 复制代码
public void execute(Runnable command, long timeout, TimeUnit unit) {
    submittedCount.incrementAndGet();
    try {
        //这个方法
        executeInternal(command);
    } catch (RejectedExecutionException rx) {
        if (getQueue() instanceof TaskQueue) {
            final TaskQueue queue = (TaskQueue) getQueue();
            try {
                if (!queue.force(command, timeout, unit)) {
                    submittedCount.decrementAndGet();
                    throw new RejectedExecutionException(sm.getString("threadPoolExecutor.queueFull"));
                }
            } catch (InterruptedException x) {
                submittedCount.decrementAndGet();
                throw new RejectedExecutionException(x);
            }
        } else {
            submittedCount.decrementAndGet();
            throw rx;
        }
    }
}
java 复制代码
 private void executeInternal(Runnable command) {
     if (command == null) {
         throw new NullPointerException();
     }
     int c = ctl.get();
     //1
     if (workerCountOf(c) < corePoolSize) {
         if (addWorker(command, true)) {
             return;
         }
         c = ctl.get();
     }
     //2
     if (isRunning(c) && workQueue.offer(command)) {
         int recheck = ctl.get();
         if (! isRunning(recheck) && remove(command)) {
             reject(command);
         } else if (workerCountOf(recheck) == 0) {
             addWorker(null, false);
         }
     }
     else if (!addWorker(command, false)) {
         reject(command);
     }
 }
  1. 运行中的线程少于核心线程池大小(corePoolSize),尝试启动一个新线程,并将给定的任务作为其首个任务,如果成功则方法返回。
  2. 运行中的线程大于核心线程池大小(corePoolSize),检测线程池状态并执行入队操作。注意这里的workQueue就是在AbstractEndpoint.createExecutor()中构建ThreadPoolExecutor时注入的,如果 workQueue.offer(command) 入队成功,再次检查线程池的状态,确认是否需要撤销任务入队操作(如果线程池已经停止),或者启动一个新的非核心线程(如果当前没有工作线程)。
  3. 如果任务无法入队,即workQueue.offer(command)返回false的时候,则尝试添加一个新的非核心线程。如果失败,我们知道线程池要么已关闭,要么已饱和,因此拒绝该任务。
连接数

最大连接数对应的是NIO模式图中的Poller,用户请求进来后会注册到Poller中去,Poller可以处理的最大连接数就是server.tomcat.max-connections 等待连接数对应的是NIO模式图中的Acceptor,在Acceptor中可以等待被accept方法调用返回的最大连接数就是server.tomcat.accept-count。如果accept方法执行的比较慢,短时间内大量请求的建立的TCP连接会被放在acceptCount定义大小的等待队列中,如果Poller可以处理的请求已达到最大值,并且不能及时处理完成,等待队列满了之后就会拒绝新的请求,并且没被及时处理的等待队列的TCP连接也会发出超时响应(connect timeout)。下面的方法一层层点击下去便会发现acceptCount影响的是C++层的TCP连接的队列大小。

java 复制代码
    protected void initServerSocket() throws Exception {
        if (getUseInheritedChannel()) {
            //...省略
        } else if (getUnixDomainSocketPath() != null) {
            SocketAddress sa = JreCompat.getInstance().getUnixDomainSocketAddress(getUnixDomainSocketPath());
            serverSock = JreCompat.getInstance().openUnixDomainServerSocketChannel();
            serverSock.bind(sa, getAcceptCount());//这里获取
            //...省略
        } else {
            serverSock = ServerSocketChannel.open();
            socketProperties.setProperties(serverSock.socket());
            InetSocketAddress addr = new InetSocketAddress(getAddress(), getPortWithOffset());
            serverSock.bind(addr, getAcceptCount());//这里获取
        }
        serverSock.configureBlocking(true);
    }
一个类比图
相关推荐
打工的小王30 分钟前
Spring Boot(三)Spring Boot整合SpringMVC
java·spring boot·后端
毕设源码-赖学姐31 分钟前
【开题答辩全过程】以 高校体育场馆管理系统为例,包含答辩的问题和答案
java·spring boot
我真会写代码32 分钟前
SSM(指南一)---Maven项目管理从入门到精通|高质量实操指南
java·spring·tomcat·maven·ssm
vx_Biye_Design33 分钟前
【关注可免费领取源码】房屋出租系统的设计与实现--毕设附源码40805
java·spring boot·spring·spring cloud·servlet·eclipse·课程设计
DN金猿39 分钟前
接口路径正确,请求接口却提示404
java·tomcat
翱翔-蓝天1 小时前
为什么“看起来很规范”的后端项目反而臃肿且性能下降
spring boot
80530单词突击赢2 小时前
JavaWeb进阶:SpringBoot核心与Bean管理
java·spring boot·后端
long3163 小时前
Aho-Corasick 模式搜索算法
java·数据结构·spring boot·后端·算法·排序算法
独断万古他化3 小时前
【SSM开发实战:博客系统】(三)核心业务功能开发与安全加密实现
spring boot·spring·mybatis·博客系统·加密
rannn_1113 小时前
【苍穹外卖|Day4】套餐页面开发(新增套餐、分页查询、删除套餐、修改套餐、起售停售)
java·spring boot·后端·学习