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);
    }
一个类比图
相关推荐
中国lanwp14 分钟前
springboot logback 默认加载配置文件顺序
java·spring boot·logback
cherishSpring25 分钟前
在windows使用docker打包springboot项目镜像并上传到阿里云
spring boot·docker·容器
苹果酱05671 小时前
【Azure Redis 缓存】在Azure Redis中,如何限制只允许Azure App Service访问?
java·vue.js·spring boot·mysql·课程设计
沫夕残雪1 小时前
Tomcat的安装与配置
java·tomcat
慧一居士2 小时前
Kafka HA集群配置搭建与SpringBoot使用示例总结
spring boot·后端·kafka
uncofish3 小时前
springboot不连接数据库启动(原先连接了mysql数据库)
数据库·spring boot·mysql
_BugMan3 小时前
Spring Boot集成RocketMQ
spring boot·rocketmq·java-rocketmq
bing_1583 小时前
Spring Boot 应用中如何避免常见的 SQL 性能陷阱 (例如:SELECT *, NOT IN, 隐式类型转换)?
spring boot·sql·性能优化
xbhog4 小时前
Java大厂面试突击:从Spring Boot自动配置到Kafka分区策略实战解析
spring boot·kafka·mybatis·java面试·分布式架构
bug菌4 小时前
面十年开发候选人被反问:当类被标注为@Service后,会有什么好处?我...🫨
spring boot·后端·spring