我的编程空间,编程开发者的网络收藏夹
学习永远不晚

图解系列 图解Spring Boot 最大连接数及最大并发数

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

图解系列 图解Spring Boot 最大连接数及最大并发数

文章目录

每个Spring Boot版本和内置容器不同,结果也不同,这里以Spring Boot 2.7.10版本 + 内置Tomcat容器举例。

概序

SpringBoot2.7.10版本中内置Tomcat版本是9.0.73,SpringBoot内置Tomcat的默认设置如下:

  • Tomcat的连接等待队列长度,默认是100
  • Tomcat的最大连接数,默认是8192
  • Tomcat的最小工作线程数,默认是10
  • Tomcat的最大线程数,默认是200
  • Tomcat的连接超时时间,默认是20s

相关配置及默认值如下

server:  tomcat:    # 当所有可能的请求处理线程都在使用中时,传入连接请求的最大队列长度    accept-count: 100    # 服务器在任何给定时间接受和处理的最大连接数。一旦达到限制,操作系统仍然可以接受基于“acceptCount”属性的连接。    max-connections: 8192    threads:      # 工作线程的最小数量,初始化时创建的线程数      min-spare: 10      # 工作线程的最大数量 io密集型建议10倍的cpu数,cpu密集型建议cpu数+1,绝大部分应用都是io密集型      max: 200    # 连接器在接受连接后等待显示请求 URI 行的时间。    connection-timeout: 20000    # 在关闭连接之前等待另一个 HTTP 请求的时间。如果未设置,则使用 connectionTimeout。设置为 -1 时不会超时。    keep-alive-timeout: 20000    # 在连接关闭之前可以进行流水线处理的最大HTTP请求数量。当设置为0或1时,禁用keep-alive和流水线处理。当设置为-1时,允许无限数量的流水线处理或keep-alive请求。     max-keep-alive-requests: 100

架构图

当连接数大于maxConnections+acceptCount + 1时,新来的请求不会收到服务器拒绝连接响应,而是不会和新的请求进行3次握手建立连接,一段时间后(客户端的超时时间或者Tomcat的20s后)会出现请求连接超时。

TCP的3次握手4次挥手

时序图

核心参数

AcceptCount

全连接队列容量,等同于backlog参数,与Linux中的系统参数somaxconn取较小值,Windows中没有系统参数。

NioEndpoint.java

serverSock = ServerSocketChannel.open();socketProperties.setProperties(serverSock.socket());InetSocketAddress addr = new InetSocketAddress(getAddress(), getPortWithOffset());// 这里serverSock.socket().bind(addr,getAcceptCount());

MaxConnections

Acccptor.java

// 线程的run方法。public void run() {    while (!stopCalled) {    // 如果我们已达到最大连接数,等待        connectionLimitLatch.countUpOrAwait();            // 接受来自服务器套接字的下一个传入连接            socket = endpoint.serverSocketAccept()            // socket.close 释放的时候 调用 connectionLimitLatch.countDown();                

MinSpareThread/MaxThread

AbstractEndpoint.java

// tomcat 启动时public void createExecutor() {        internalExecutor = true;    // 容量为Integer.MAX_VALUE        TaskQueue taskqueue = new TaskQueue();        TaskThreadFactory tf = new TaskThreadFactory(getName() + "-exec-", daemon, getThreadPriority());    // Tomcat扩展的线程池        executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), 60, TimeUnit.SECONDS,taskqueue, tf);        taskqueue.setParent( (ThreadPoolExecutor) executor);}

重点重点重点

Tomcat扩展了线程池增强了功能。

  • JDK线程池流程:minThreads --> queue --> maxThreads --> Exception
  • Tomcat增强后: minThreads --> maxThreads --> queue --> Exception

MaxKeepAliveRequests

长连接,在发送了maxKeepAliveRequests个请求后就会被服务器端主动断开连接。

在连接关闭之前可以进行流水线处理的最大HTTP请求数量。当设置为0或1时,禁用keep-alive和流水线处理。当设置为-1时,允许无限数量的流水线处理或keep-alive请求。

较大的 MaxKeepAliveRequests 值可能会导致服务器上的连接资源被长时间占用。根据您的具体需求,您可以根据服务器的负载和资源配置来调整 MaxKeepAliveRequests 的值,以平衡并发连接和服务器资源的利用率。

NioEndpoint.setSocketOptionssocketWrapper.setKeepAliveLeft(NioEndpoint.this.getMaxKeepAliveRequests());Http11Processor.service(SocketWrapperBase<?> socketWrapper)  keepAlive = true;  while(!getErrorState().isError() && keepAlive && !isAsync() && upgradeToken == null &&                sendfileState == SendfileState.DONE && !protocol.isPaused()) {    // 默认100  int maxKeepAliveRequests = protocol.getMaxKeepAliveRequests();if (maxKeepAliveRequests == 1) {    keepAlive = false;} else if (maxKeepAliveRequests > 0 &&            //            socketWrapper.decrementKeepAlive() <= 0) {    keepAlive = false;}

ConnectionTimeout

连接的生存周期,当已经建立的连接,在connectionTimeout时间内,如果没有请求到来,服务端程序将会主动关闭该连接。

  • 在Tomcat 9中,ConnectionTimeout的默认值是20000毫秒,也就是20秒。
  • 如果该时间过长,服务器将要等待很长时间才会收到客户端的请求结果,从而导致服务效率低下。如果该时间过短,则可能会出现客户端在请求过程中网络慢等问题,而被服务器取消连接的情况。
  • 由于某个交换机或者路由器出现了问题,导致某些post大文件的请求堆积在交换机或者路由器上,tomcat的工作线程一直拿不到完整的文件数据。

NioEndpoint.Poller#run()

 // Check for read timeout if ((socketWrapper.interestOps() & SelectionKey.OP_READ) == SelectionKey.OP_READ) {     long delta = now - socketWrapper.getLastRead();     long timeout = socketWrapper.getReadTimeout();     if (timeout > 0 && delta > timeout) {         readTimeout = true;     } } // Check for write timeout if (!readTimeout && (socketWrapper.interestOps() & SelectionKey.OP_WRITE) == SelectionKey.OP_WRITE) {     long delta = now - socketWrapper.getLastWrite();     long timeout = socketWrapper.getWriteTimeout();     if (timeout > 0 && delta > timeout) {         writeTimeout = true;     } }

KeepAliveTimeout

等待另一个 HTTP 请求的时间,然后关闭连接。当未设置时,将使用 connectionTimeout。当设置为 -1 时,将没有超时。

Http11InputBuffer.parseRequestLine

// Read new bytes if neededif (byteBuffer.position() >= byteBuffer.limit()) {    if (keptAlive) {        // 还没有读取任何请求数据,所以使用保持活动超时        wrapper.setReadTimeout(keepAliveTimeout);    }    if (!fill(false)) {        // A read is pending, so no longer in initial state        parsingRequestLinePhase = 1;        return false;    }    // 至少已收到请求的一个字节 切换到套接字超时。     wrapper.setReadTimeout(connectionTimeout);}

内部线程

Acceptor

Acceptor: 接收器,作用是接受scoket网络请求,并调用setSocketOptions()封装成为NioSocketWrapper,并注册到Poller的events中。注意查看run方法org.apache.tomcat.util.net.Acceptor#run

public void run() {       while (!stopCalled) {           // 等待下一个请求进来           socket = endpoint.serverSocketAccept();            // 注册socket到Poller,生成PollerEvent事件           endpoint.setSocketOptions(socket);           // 向轮询器注册新创建的套接字                    - poller.register(socketWrapper);                        - (SynchronizedQueue(128))events.add(new PollerEvent(socketWrapper))    

Poller

Poller:轮询器,轮询是否有事件达到,有请求事件到达后,以NIO的处理方式,查询Selector取出所有请求,遍历每个请求的需求,分配给Executor线程池执行。查看org.apache.tomcat.util.net.NioEndpoint.Poller#run()

public void run() {       while (true) {               //查询selector取出所有请求事件               Iterator<SelectionKey> iterator =                   keyCount > 0 ? selector.selectedKeys().iterator() : null;               // 遍历就绪键的集合并调度任何活动事件。               while (iterator != null && iterator.hasNext()) {                   SelectionKey sk = iterator.next();                   iterator.remove();                   NioSocketWrapper socketWrapper = (NioSocketWrapper) sk.attachment();                   // 分配给Executor线程池执行处理请求key                   if (socketWrapper != null) {                       processKey(sk, socketWrapper);                       - processSocket(socketWrapper, SocketEvent.OPEN_READ/SocketEvent.OPEN_WRITE)                           - executor.execute((Runnable)new SocketProcessor(socketWrapper,SocketEvent))                   }               }

TomcatThreadPoolExecutor

真正执行连接读写操作的线程池,在JDK线程池的基础上进行了扩展优化。

AbstractEndpoint.java

public void createExecutor() {        internalExecutor = true;        TaskQueue taskqueue = new TaskQueue();        TaskThreadFactory tf = new TaskThreadFactory(getName() + "-exec-", daemon, getThreadPriority());    // tomcat自定义线程池        executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), 60, TimeUnit.SECONDS,taskqueue, tf);        taskqueue.setParent( (ThreadPoolExecutor) executor);    }

TomcatThreadPoolExecutor.java

// 与 java.util.concurrent.ThreadPoolExecutor 相同,但实现了更高效的getSubmittedCount()方法,用于正确处理工作队列。// 如果未指定 RejectedExecutionHandler,将配置一个默认的,并且该处理程序将始终抛出 RejectedExecutionExceptionpublic class ThreadPoolExecutor extends java.util.concurrent.ThreadPoolExecutor {// 已提交但尚未完成的任务数。这包括队列中的任务和已交给工作线程但后者尚未开始执行任务的任务。    // 这个数字总是大于或等于getActiveCount() 。    private final AtomicInteger submittedCount = new AtomicInteger(0);        @Override    protected void afterExecute(Runnable r, Throwable t) {        if (!(t instanceof StopPooledThreadException)) {            submittedCount.decrementAndGet();        }    @Override    public void execute(Runnable command){        // 提交任务的数量+1        submittedCount.incrementAndGet();        try {            //  线程池内部方法,真正执行的方法。就是JDK线程池原生的方法。            super.execute(command);        } catch (RejectedExecutionException rx) {            // 再次把被拒绝的任务放入到队列中。            if (super.getQueue() instanceof TaskQueue) {                final TaskQueue queue = (TaskQueue)super.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;            }        }    }    
public class TaskQueue extends LinkedBlockingQueue<Runnable> {    private static final long serialVersionUID = 1L;    private transient volatile ThreadPoolExecutor parent = null;    private static final int DEFAULT_FORCED_REMAINING_CAPACITY = -1;        private int forcedRemainingCapacity = -1;        public TaskQueue() {    }    public TaskQueue(int capacity) {        super(capacity);    }    public TaskQueue(Collection<? extends Runnable> c) {        super(c);    }        public void setParent(ThreadPoolExecutor parent) {        this.parent = parent;    }        public boolean force(Runnable o) {        if (parent == null || parent.isShutdown()) {            throw new RejectedExecutionException("taskQueue.notRunning");        }        return super.offer(o);    }        @Deprecated    public boolean force(Runnable o, long timeout, TimeUnit unit) throws InterruptedException {        if (parent == null || parent.isShutdown()) {            throw new RejectedExecutionException("taskQueue.notRunning");        }        return super.offer(o, timeout, unit); //forces the item onto the queue, to be used if the task is rejected    }        @Override    public boolean offer(Runnable runnable) {        if (parent == null) {            return super.offer(runnable);        }        //若是达到最大线程数,进队列。        if (parent.getPoolSize() == parent.getMaximumPoolSize()) {            return super.offer(runnable);        }        //当前活跃线程为10个,但是只有8个任务在执行,于是,直接进队列。        if (parent.getSubmittedCount() < (parent.getPoolSize())) {            return super.offer(runnable);        }        //当前线程数小于最大线程数,那么直接返回false,去创建最大线程        if (parent.getPoolSize() < parent.getMaximumPoolSize()) {            return false;        }        //否则的话,将任务放入到队列中        return super.offer(runnable);    }        @Override    public Runnable poll(long timeout, TimeUnit unit) throws InterruptedException {        Runnable runnable = super.poll(timeout, unit);        //取任务超时,会停止当前线程,来避免内存泄露        if (runnable == null && parent != null) {            parent.stopCurrentThreadIfNeeded();        }        return runnable;    }        @Override    public Runnable take() throws InterruptedException {        //当前线程应当被终止的情况下:        if (parent != null && parent.currentThreadShouldBeStopped()) {            long keepAliveTime = parent.getKeepAliveTime(TimeUnit.MILLISECONDS);            return poll(keepAliveTime, TimeUnit.MILLISECONDS);        }        return super.take();    }        @Override    public int remainingCapacity() {        if (forcedRemainingCapacity > DEFAULT_FORCED_REMAINING_CAPACITY) {            return forcedRemainingCapacity;        }        return super.remainingCapacity();    }        public void setForcedRemainingCapacity(int forcedRemainingCapacity) {        this.forcedRemainingCapacity = forcedRemainingCapacity;    }        void resetForcedRemainingCapacity() {        this.forcedRemainingCapacity = DEFAULT_FORCED_REMAINING_CAPACITY;    }}

JDK线程池架构图

在这里插入图片描述

Tomcat线程架构

测试

如下配置举例

server:  port: 8080  tomcat:    accept-count: 3    max-connections: 6    threads:      min-spare: 2      max: 3

使用ss -nlt查看全连接队列容量。

ss -nltpss -nlt|grep 8080- Recv-Q表示(acceptCount)全连接队列目前长度- Send-Q表示(acceptCount)全连接队列的容量。

静默状态

6个并发连接

结果同上

9个并发连接

10个并发连接

11个并发连接

结果同上

使用ss -nt查看连接状态。

ss -ntpss -nt|grep 8080- Recv-Q表示客户端有多少个字节发送但还没有被服务端接收- Send-Q就表示为有多少个字节未被客户端接收。

静默状态

6个并发连接

9个并发连接

补充个netstat

10个并发连接

结果同上,队列中多加了个

11个并发连接

超出连接后,会有个连接一直停留在SYN_RECV状态,不会完成3次握手了。

超出连接后客户端一直就停留在SYN-SENT状态,服务端不会再发送SYN+ACK,直到客户端超时(20s内核控制)断开。

客户端请求超时(需要等待一定时间(20s))。

这里如果客户端设置了超时时间,要和服务端3次握手超时时间对比小的为准。

12个并发连接

参考

  • https://www.zhangbj.com/p/1105.html
  • https://www.eginnovations.com/blog/tomcat-monitoring-metrics/

来源地址:https://blog.csdn.net/abu935009066/article/details/130957301

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

图解系列 图解Spring Boot 最大连接数及最大并发数

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

如何理解IIS最大并发连接数

本篇文章为大家展示了如何理解IIS最大并发连接数,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。最大并发连接数=队列长度+工作线程数【工作线程数】IIS实际可以第一时间处理的请求数。比如,工作线程数=
2023-06-05

超出MySQL最大连接数问题及解决

目录超出mysql最大连接数问题查看最大连接数修改最大连接数MySQL最大连接数,TimeOut配置MySQL连接数配置Mysql的连接线程池Mysql TimeOut配置总结超出MySQL最大连接数问题如果遇到MySQ连接数超出最大限制
2023-05-15

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录