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

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

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

Spring Boot的配置项

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

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

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

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

源码说明(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
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的入队方法:

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的情况

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;
        }
    }
}
 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连接的队列大小。

    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);
    }
一个类比图

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

  • 8
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

豢龙先生

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值