SpringBoot内嵌Tomcat连接池分析

文章目录

  • 1 Tomcat连接池
    • 1.1 简介
    • 1.2 架构图
      • 1.2.1 JDK线程池架构图
      • 1.2.2 Tomcat线程架构
    • 1.3 核心参数
      • 1.3.1 AcceptCount
      • 1.3.2 MaxConnections
      • 1.3.3 MinSpareThread/MaxThread
      • 1.3.4 MaxKeepAliveRequests
      • 1.3.5 ConnectionTimeout
      • 1.3.6 KeepAliveTimeout
    • 1.4 核心内部线程
      • 1.4.1 Acceptor
      • 1.4.2 Poller
      • 1.4.3 TomcatThreadPoolExecutor
    • 1.5 测试

1 Tomcat连接池

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

1.1 简介

Spring Boot 2.6.11版本中内置Tomcat版本是 9.0.65SpringBoot 内置Tomcat 的默认设置如下:

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

在这里插入图片描述

相关配置及默认值如下

server:tomcat:# 当所有可能的请求处理线程都在使用中时,传入连接请求的最大队列长度accept-count: 100# 服务器在任何给定时间接受和处理的最大连接数。一旦达到限制,操作系统仍然可以接受基于“acceptCount”属性的连接。max-connections: 8192threads:# 工作线程的最小数量,初始化时创建的线程数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

1.2 架构图

在这里插入图片描述

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

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

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

点击此处了解JDK线程池

1.2.1 JDK线程池架构图

在这里插入图片描述

1.2.2 Tomcat线程架构

在这里插入图片描述

1.3 核心参数

1.3.1 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());

1.3.2 MaxConnections

最大连接数
Acccptor.java

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

1.3.3 MinSpareThread/MaxThread

工作线程最小/最大线程数
AbstractEndpoint.java

// tomcat 启动时
public void createExecutor() {internalExecutor = true;// 容量为Integer.MAX_VALUETaskQueue 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);
}

1.3.4 MaxKeepAliveRequests

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

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

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

NioEndpoint.setSocketOptions socketWrapper.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;}

1.3.5 ConnectionTimeout

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

Tomcat 9中,ConnectionTimeout的默认值是20000毫秒,也就是20秒

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

NioEndpoint.Poller#run()// Check for read timeoutif ((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 timeoutif (!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;}}

1.3.6 KeepAliveTimeout

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

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

1.4 核心内部线程

1.4.1 Acceptor

Acceptor:接收器,作用是接受scoket网络请求,并调用setSocketOptions()封装成为NioSocketWrapper,并注册到Pollerevents中。注意查看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))  

1.4.2 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线程池执行处理请求keyif (socketWrapper != null) {processKey(sk, socketWrapper);- processSocket(socketWrapper, SocketEvent.OPEN_READ/SocketEvent.OPEN_WRITE)- executor.execute((Runnable)new SocketProcessor(socketWrapper,SocketEvent))}}

1.4.3 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,将配置一个默认的,并且该处理程序将始终抛出 RejectedExecutionException
public class ThreadPoolExecutor extends java.util.concurrent.ThreadPoolExecutor {// 已提交但尚未完成的任务数。这包括队列中的任务和已交给工作线程但后者尚未开始执行任务的任务。// 这个数字总是大于或等于getActiveCount() 。private final AtomicInteger submittedCount = new AtomicInteger(0);@Overrideprotected void afterExecute(Runnable r, Throwable t) {if (!(t instanceof StopPooledThreadException)) {submittedCount.decrementAndGet();}@Overridepublic void execute(Runnable command){// 提交任务的数量+1submittedCount.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;}}}
/*** 实现Tomcat特有逻辑的自定义队列*/
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;}/*** put:向阻塞队列填充元素,当阻塞队列满了之后,put时会被阻塞。* offer:向阻塞队列填充元素,当阻塞队列满了之后,offer会返回false。** @param o 当任务被拒绝后,继续强制的放入到线程池中* @return 向阻塞队列塞任务,当阻塞队列满了之后,offer会返回false。*/public boolean force(Runnable o) {if (parent == null || parent.isShutdown()) {throw new RejectedExecutionException("taskQueue.notRunning");}return super.offer(o);}/*** 带有阻塞时间的塞任务*/@Deprecatedpublic 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}/*** 当线程真正不够用时,优先是开启线程(直至最大线程),其次才是向队列填充任务。** @param runnable 任务* @return false 表示向队列中添加任务失败,*/@Overridepublic 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);}/*** 获取任务*/@Overridepublic Runnable poll(long timeout, TimeUnit unit) throws InterruptedException {Runnable runnable = super.poll(timeout, unit);//取任务超时,会停止当前线程,来避免内存泄露if (runnable == null && parent != null) {parent.stopCurrentThreadIfNeeded();}return runnable;}/*** 阻塞式的获取任务,可能返回null。*/@Overridepublic Runnable take() throws InterruptedException {//当前线程应当被终止的情况下:if (parent != null && parent.currentThreadShouldBeStopped()) {long keepAliveTime = parent.getKeepAliveTime(TimeUnit.MILLISECONDS);return poll(keepAliveTime, TimeUnit.MILLISECONDS);}return super.take();}/*** 返回队列的剩余容量*/@Overridepublic 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;}
} 

1.5 测试

如下配置举例

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

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

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

静默状态
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/49156.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

设计模式——开闭原则

文章目录 基本介绍看下面一段代码方式 1 的优缺点改进的思路分析 基本介绍 开闭原则&#xff08;Open Closed Principle&#xff09;是编程中最基础、最重要的设计原则 一个软件实体如类&#xff0c;模块和函数应该对扩展开放(对提供方)&#xff0c;对修改关闭(对使用方)。用抽…

SpringBoot+WebSocket搭建多人在线聊天环境

一、WebSocket是什么&#xff1f; WebSocket是在单个TCP连接上进行全双工通信的协议&#xff0c;可以在服务器和客户端之间建立双向通信通道。 WebSocket 首先与服务器建立常规 HTTP 连接&#xff0c;然后通过发送Upgrade标头将其升级为双向 WebSocket 连接。 WebSocket使得…

设计模式(3)抽象工厂模式

一、概述&#xff1a; 1、提供一个创建一系列相关或相互依赖对象的接口&#xff0c;而无须指定它们具体的类。 2、结构图&#xff1a; 3、举例代码&#xff1a; &#xff08;1&#xff09; 实体&#xff1a; public interface IUser {public void insert(User user);public…

亚马逊云科技 云技能孵化营 初识机器学习

目录 前言 一、课程介绍 二、什么是机器学习 三、机器学习算法进阶过程 四、亚马逊云科技能给我们什么 总结 前言 近期参加了“亚马逊云科技 云技能孵化营”&#xff0c;该孵化营的亚马逊云科技培训与认证团队为开发者准备了云从业者的精要知识及入门课程&#xff0c;帮助…

typora的样式的修改

typora首先是一个浏览器&#xff0c; 当我们在typora的设置里面勾选开启调试模式之后&#xff0c; 我们在typora里面右键就会有“检查元素” 这个选项 首先右键 ----》检查元素 将普通字体变颜色 关于Typora修改样式 破解版的typora样式太单调&#xff1f;想让笔记可读性更高…

会计资料基础

会计资料 1.会计要素及确认与计量 1.1 会计基础 1.2 六项会计要素小结 1.3 利润的确认条件 1.3.1 利润的定义和确认条件 1.4 会计要素及确认条件 2.六项会计要素 2.1 资产的特征及其确认条件 这部分资产可以给企业带来经济收益&#xff0c;但是如果不能带来经济利益&#xff…

【jsthreeJS】入门three,并实现3D汽车展示厅,附带全码

首先放个最终效果图&#xff1a; 三维&#xff08;3D&#xff09;概念&#xff1a; 三维&#xff08;3D&#xff09;是一个描述物体在三个空间坐标轴上的位置和形态的概念。相比于二维&#xff08;2D&#xff09;只有长度和宽度的平面&#xff0c;三维增加了高度或深度这一维度…

09 数据库开发-MySQL

文章目录 1 数据库概述2 MySQL概述2.1 MySQL安装2.1.1 解压&添加环境变量2.1.2 初始化MySQL2.1.3 注册MySQL服务2.1.4 启动MySQL服务2.1.5 修改默认账户密码2.1.6 登录MySQL 2.2 卸载MySQL2.3 连接服务器上部署的数据库2.4 数据模型2.5 SQL简介2.5.1 SQL通用语法2.3.2 分类…

excel文本函数篇2

本期主要介绍LEN、FIND、SEARCH以及后面加B的情况&#xff1a; &#xff08;1&#xff09;后缀没有B&#xff1a;一个字节代表一个中文字符 &#xff08;2&#xff09;后缀有B&#xff1a;两个字节代表一个中文字符 1、LEN(text)&#xff1a;返回文本字符串中的字符个数 2、…

vue3——递归组件的使用

该文章是在学习 小满vue3 课程的随堂记录示例均采用 <script setup>&#xff0c;且包含 typescript 的基础用法 一、使用场景 递归组件 的使用场景&#xff0c;如 无限级的菜单 &#xff0c;接下来就用菜单的例子来学习 二、具体使用 先把菜单的基础内容写出来再说 父…

【中危】 Apache NiFi 连接 URL 验证绕过漏洞 (CVE-2023-40037)

漏洞描述 Apache NiFi 是一个开源的数据流处理和自动化工具。 在受影响版本中&#xff0c;由于多个Processors和Controller Services在配置JDBC和JNDI JMS连接时对URL参数过滤不完全。使用startsWith方法过滤用户输入URL&#xff0c;导致过滤可以被绕过。攻击者可以通过构造特…

亚信科技AntDB数据库连年入选《中国DBMS市场指南》代表厂商

近日&#xff0c;全球权威ICT研究与顾问咨询公司Gartner发布了2023年《Market Guide for DBMS, China》&#xff08;即“中国DBMS市场指南”&#xff09;&#xff0c;该指南从市场份额、技术创新、研发投入等维度对DBMS供应商进行了调研。亚信科技是领先的数智化全栈能力提供商…

学习平台助力职场发展与提升

近年来&#xff0c;随着互联网技术的发展&#xff0c;学习平台逐渐成为了职场发展和提升的必备工具。学习平台通过提供丰富的课程内容、灵活的学习时间和个性化的学习路径&#xff0c;帮助职场人士更好地提升自己的技能和知识储备&#xff0c;为职场发展打下坚实的基础。 学习…

redis基本介绍以及在node中使用

文章目录 引言一、什么是redis1. redis简介2. redis的特点3. redis的应用场景 二、redis在windows下安装1. 下载安装2.验证是否安装成功3. 配置环境变量 三、redis-cli常用命令介绍1. redis-cli2. keys *3. set key value4. get key5. exists key6. del key7. info8. flushdb9.…

数据结构与算法:通往编程高地的必修课(文末送书)

&#x1f935;‍♂️ 个人主页&#xff1a;艾派森的个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f4…

电商平台按关键字搜索商品淘宝京东拼多多api接口PHP示例

关键词搜索商品接口的作用是通过调用接口来实现在电商平台中进行商品搜索。具体而言&#xff0c;该接口可以提供以下功能和作用&#xff1a; 商品搜索&#xff1a;用户可以通过输入关键词&#xff0c;在电商平台上进行商品搜索。接口可以根据关键词对商品的名称、描述、标签等…

解决OpenFOAM颗粒计算输出文件Paraview无法打开问题(二)

第二个方案的源是在CFD中文网上看到的一篇帖子&#xff0c;其具体链接忘了。这个帖子给了一个github的链接&#xff0c;就是将OpenFOAM输出的颗粒位置信息转变为真实的位置信息的脚本。其链接在此。 1. 背景 我们知道&#xff0c;paraview之所以打不开OF输出的颗粒文件&#…

Python 有趣的模块之pynupt——通过pynput控制鼠标和键盘

写在前面 Python中有许多有趣和强大的模块&#xff0c;其中一个非常有趣的模块就是pynupt。pynupt是基于pynput模块的一个封装&#xff0c;用于控制鼠标和键盘。它可以实现自动化操作和游戏外挂等功能。 本文将详细介绍pynupt模块的使用方法和常见的功能。 1. 安装pynput模块…

Android 14新增复制粘贴方式,解析工作原理

安卓14为用户提供了一种更简单的方式来在应用程序之间复制和粘贴内容&#xff0c;这肯定是你现在想在安卓14测试版或未来几个月该软件在你的安卓手机上推出时尝试的。 一旦更新在你的手机上&#xff08;无论是测试版还是其他版本&#xff09;&#xff0c;你只需点击并按住你想…

【Python机器学习】实验14 手写体卷积神经网络

文章目录 LeNet-5网络结构&#xff08;1&#xff09;卷积层C1&#xff08;2&#xff09;池化层S1&#xff08;3&#xff09;卷积层C2&#xff08;4&#xff09;池化层S2&#xff08;5&#xff09;卷积层C3&#xff08;6&#xff09;线性层F1&#xff08;7&#xff09;线性层F2 …