Tomcat 线程模型详解性能调优

article/2025/8/4 12:42:42

1. Tomcat I/O模型详解**(了解)**
1.1 Linux I/O模型详解
I/O要解决什么问题
I/O:在计算机内存与外部设备之间拷贝数据的过程。
程序通过CPU向外部设备发出读指令,数据从外部设备拷贝至内存需要一段时间,这段时间CPU就没
事情做了,程序就会两种选择:

  1. 让出CPU资源,让其干其他事情
  2. 继续让CPU不停地查询数据是否拷贝完成
    到底采取何种选择就是I/O模型需要解决的事情了。
    以网络数据读取为例来分析,会涉及两个对象,一个是调用这个I/O操作的用户线程,另一个是操作系
    统内核。一个进程的地址空间分为用户空间和内核空间,基于安全上的考虑,用户程序只能访问用户
    空间,内核程序可以访问整个进程空间,只有内核可以直接访问各种硬件资源,比如磁盘和网卡。
    在这里插入图片描述
    当用户线程发起 I/O 调用后,网络数据读取操作会经历两个步骤:
    数据准备阶段: 用户线程等待内核将数据从网卡拷贝到内核空间。
    数据拷贝阶段: 内核将数据从内核空间拷贝到用户空间(应用进程的缓冲区)。

不同的I/O模型对于这2个步骤有着不同的实现步骤。

Linux的I/O模型分类
Linux 系统下的 I/O 模型有 5 种:
同步阻塞I/O(bloking I/O)
同步非阻塞I/O(non-blocking I/O)
I/O多路复用(multiplexing I/O)
信号驱动式I/O(signal-driven I/O)
异步I/O(asynchronous I/O)

其中信号驱动式IO在实际中并不常用
阻塞或非阻塞是指应用程序在发起 I/O 操作时,是立即返回还是等待。
同步或异步是指应用程序在与内核通信时,数据从内核空间到应用空间的拷贝,是由内核主动发起还是由应用程序来触发。
1.2 Tomcat支持的 I/O 模型
Tomcat 支持的 I/O 模型有:
在这里插入图片描述
注意: Linux 内核没有很完善地支持异步 I/O 模型,因此 JVM 并没有采用原生的 Linux 异步 I/O,而
是在应用层面通过 epoll 模拟了异步 I/O 模型。因此在 Linux 平台上,Java NIO 和 Java NIO2 底层
都是通过 epoll 来实现的,但是 Java NIO 更加简单高效。

Tomcat I/O 模型如何选型
I/O 调优实际上是连接器类型的选择,一般情况下默认都是 NIO,在绝大多数情况下都是够用的,
除非你的 Web 应用用到了 TLS 加密传输,而且对性能要求极高,这个时候可以考虑 APR,因为 APR
通过 OpenSSL 来处理 TLS 握手和加密 / 解密。OpenSSL 本身用 C 语言实现,它还对 TLS 通信做了
优化,所以性能比 Java 要高。如果你的 Tomcat 跑在 Windows 平台上,并且 HTTP 请求的数据量比
较大,可以考虑 NIO2,这是因为 Windows 从操作系统层面实现了真正意义上的异步 I/O,如果传输
的数据量比较大,异步 I/O 的效果就能显现出来。如果你的 Tomcat 跑在 Linux 平台上,建议使用
NIO。因为在 Linux 平台上,Java NIO 和 Java NIO2 底层都是通过 epoll 来实现的,但是 Java NIO
更加简单高效。
指定IO模型只需修改protocol配置

1 <!-- 修改protocol属性, 使用NIO2 -->
2 <Connector port="8080" protocol="org.apache.coyote.http11.Http11Nio2Protocol"
3 connectionTimeout="20000"
4 redirectPort="8443" />

1.3 网络编程模型Reactor线程模型
Reactor 模型是网络服务器端用来处理高并发网络 IO 请求的一种编程模型。
该模型主要有三类处理事件:即连接事件、写事件、读事件;三个关键角色:即 reactor、acceptor、
handler。acceptor负责连接事件,handler负责读写事件,reactor负责事件监听和事件分发。
单 Reactor 单线程
在这里插入图片描述
由上图可以看出,单Reactor单线程模型中的 reactor、acceptor 和 handler以及后续业务处理逻辑的
功能都是由一个线程来执行的。reactor 负责监听客户端事件和事件分发,一旦有连接事件发生,它会
分发给 acceptor,由 acceptor 负责建立连接,然后创建一个 handler。如果是读写事件,reactor 将
事件分发给 handler 进行处理。handler 负责读取客户端请求,进行业务处理,并最终给客户端返回
结果。
单 Reactor 多线程
在这里插入图片描述
该模型中,reactor、acceptor 和 handler 的功能由一个线程来执行,与此同时,会有一个线程池,
由若干 worker 线程组成。在监听客户端事件、连接事件处理方面,这个类型和单 rector 单线程是相
同的,但是不同之处在于,在单 reactor 多线程类型中,handler 只负责读取请求和写回结果,而具
体的业务处理由 worker 线程来完成。
主从 Reactor 多线程
在这里插入图片描述
在这个类型中,会有一个主 reactor 线程、多个子 reactor 线程和多个 worker 线程组成的一个线程
池。其中,主 reactor 负责监听客户端事件,并在同一个线程中让 acceptor 处理连接事件。一旦连接
建立后,主 reactor 会把连接分发给子 reactor 线程,由子 reactor 负责这个连接上的后续事件处
理。那么,子 reactor 会监听客户端连接上的后续事件,有读写事件发生时,它会让在同一个线程中
的 handler 读取请求和返回结果,而和单 reactor 多线程类似,具体业务处理,它还是会让线程池中
的 worker 线程处理。
1.4 Tomcat NIO实现
在 Tomcat 中,EndPoint 组件的主要工作就是处理 I/O,而 NioEndpoint 利用 Java NIO API 实现了
多路复用 I/O 模型。Tomcat的NioEndpoint 是基于主从Reactor多线程模型设计的。
在这里插入图片描述
LimitLatch 是连接控制器,它负责控制最大连接数,NIO 模式下默认是 10000(tomcat9中8192),当连接数到达最大时阻塞线程,直到后续组件处理完一个连接后将连接数减 1。注意到达最大连接数后操作系统底层还是会接收客户端连接,但用户层已经不再接收。

Acceptor 跑在一个单独的线程里,它在一个死循环里调用 accept 方法来接收新连接,一旦有新的连接请求到来,accept 方法返回一个 Channel 对象,接着把 Channel 对象交给 Poller 去处理。

1 #NioEndpoint#initServerSocket
2
3 serverSock = ServerSocketChannel.open();
4 //第2个参数表示操作系统的等待队列长度,默认100
5 //当应用层面的连接数到达最大值时,操作系统可以继续接收的最大连接数
6 serverSock.bind(addr, getAcceptCount());
7 //ServerSocketChannel 被设置成阻塞模式
8 serverSock.configureBlocking(true);

ServerSocketChannel 通过 accept() 接受新的连接,accept() 方法返回获得 SocketChannel 对
象,然后将 SocketChannel 对象封装在一个 PollerEvent 对象中,并将 PollerEvent 对象压入 Poller
的 SynchronizedQueue 里,这是个典型的生产者 - 消费者模式,Acceptor 与 Poller 线程之间通过
SynchronizedQueue 通信。

Poller 的本质是一个 Selector,也跑在单独线程里。Poller 在内部维护一个 Channel 数组,它在一个死循环里不断检测 Channel 的数据就绪状态,一旦有 Channel 可读,就生成一个 SocketProcessor 任务对象扔给Executor 去处理。
在这里插入图片描述
Executor 就是线程池,负责运行 SocketProcessor 任务类,SocketProcessor 的 run 方法会调用
Http11Processor 来读取和解析请求数据。Http11Processor 是应用层协议的封装,它会调用容器获得响应,再把响应通过 Channel 写出。
1.5 Tomcat 异步IO实现
NIO 和 NIO2 最大的区别是,一个是同步一个是异步。异步最大的特点是,应用程序不需要自己去触
发数据从内核空间到用户空间的拷贝。
在这里插入图片描述
Nio2Endpoint 中没有 Poller 组件,也就是没有 Selector。在异步 I/O 模式下,Selector 的工作交给
内核来做了。
2. Tomcat性能调优(重点)
2.1 如何监控Tomcat的性能
Tomcat 的关键指标

Tomcat 的关键指标有吞吐量、响应时间、错误数、线程池、CPU 以及 JVM 内存。前三个指标是
我们最关心的业务指标,Tomcat 作为服务器,就是要能够又快有好地处理请求,因此吞吐量要大、响
应时间要短,并且错误数要少。后面三个指标是跟系统资源有关的,当某个资源出现瓶颈就会影响前
面的业务指标,比如线程池中的线程数量不足会影响吞吐量和响应时间;但是线程数太多会耗费大量
CPU,也会影响吞吐量;当内存不足时会触发频繁地 GC,耗费 CPU,最后也会反映到业务指标上
来。
通过 JConsole 监控 Tomcat
JConsole是一款基于JMX的可视化监控和管理工具

  1. 开启 JMX 的远程监听端口
    我们可以在 Tomcat 的 bin 目录下新建一个名为setenv.sh的文件(或者setenv.bat,根据你的操作系统类型),然后输入下面的内容:
1 export JAVA_OPTS="${JAVA_OPTS} -Dcom.sun.management.jmxremote"
2 export JAVA_OPTS="${JAVA_OPTS} -Dcom.sun.management.jmxremote.port=8011"
3 export JAVA_OPTS="${JAVA_OPTS} -Djava.rmi.server.hostname=x.x.x.x"
4 export JAVA_OPTS="${JAVA_OPTS} -Dcom.sun.management.jmxremote.ssl=false"
5 export JAVA_OPTS="${JAVA_OPTS} -Dcom.sun.management.jmxremote.authenticate=false"

2)重启 Tomcat,这样 JMX 的监听端口 8011 就开启了,接下来通过 JConsole 来连接这个端口。

1 jconsole x.x.x.x:8011

我们可以看到 JConsole 的主界面:
在这里插入图片描述
吞吐量、响应时间、错误数
在 MBeans 标签页下选择 GlobalRequestProcessor,这里有 Tomcat 请求处理的统计信息。你会看
到 Tomcat 中的各种连接器,展开“http-nio-8080”,你会看到这个连接器上的统计信息,其中
maxTime 表示最长的响应时间,processingTime 表示平均响应时间,requestCount 表示吞吐量,
errorCount 就是错误数。
在这里插入图片描述
线程池
选择“线程”标签页,可以看到当前 Tomcat 进程中有多少线程,如下图所示:
在这里插入图片描述
图的左下方是线程列表,右边是线程的运行栈,这些都是非常有用的信息。如果大量线程阻塞,通过
观察线程栈,能看到线程阻塞在哪个函数,有可能是 I/O 等待,或者是死锁。
CPU
在主界面可以找到 CPU 使用率指标,请注意这里的 CPU 使用率指的是 Tomcat 进程占用的 CPU,不
是主机总的 CPU 使用率。
在这里插入图片描述
JVM 内存
选择“内存”标签页,你能看到 Tomcat 进程的 JVM 内存使用情况。
在这里插入图片描述
命令行查看 Tomcat 指标
极端情况下如果 Web 应用占用过多 CPU 或者内存,又或者程序中发生了死锁,导致 Web 应用对外
没有响应,监控系统上看不到数据,这个时候需要我们登陆到目标机器,通过命令行来查看各种指
标。

  1. 首先我们通过 ps 命令找到 Tomcat 进程,拿到进程 ID。
1 ps -ef|grep tomcat
  1. 接着查看进程状态的大致信息,通过cat /proc//status命令:
  2. 监控进程的 CPU 和内存资源使用情况:
  3. 查看 Tomcat 的网络连接,比如 Tomcat 在 8080 端口上监听连接请求,通过下面的命令查看连接
    列表:
    你还可以分别统计处在“已连接”状态和“TIME_WAIT”状态的连接数:
  4. 通过 ifstat 来查看网络流量,大致可以看出 Tomcat 当前的请求数和负载状况。
    2.2 线程池的并发调优(重点)
    线程池调优指的是给 Tomcat 的线程池设置合适的参数,使得 Tomcat 能够又快又好地处理请求。
    sever.xml中配置线程池:
1 <!--
2 namePrefix: 线程前缀
3 maxThreads: 最大线程数,默认设置 200,一般建议在 500 ~ 1000,根据硬件设施和业务来判断
4 minSpareThreads: 核心线程数,默认设置 25
5 prestartminSpareThreads:Tomcat 初始化的时候就初始化核心线程
6 maxQueueSize: 最大的等待队列数,超过则拒绝请求 ,默认 Integer.MAX_VALUE
7 maxIdleTime: 线程空闲时间,超过该时间,线程会被销毁,单位毫秒
8 className: 线程实现类,默认org.apache.catalina.core.StandardThreadExecutor
9 -->
10 <Executor name="tomcatThreadPool" namePrefix="catalina-exec-Fox"
11 prestartminSpareThreads="true"
12 maxThreads="500" minSpareThreads="10" maxIdleTime="10000"/>
13 
14 <Connector port="8080" protocol="HTTP/1.1" executor="tomcatThreadPool"
15 connectionTimeout="20000"
16 redirectPort="8443" URIEncoding="UTF-8"/>

这里面最核心的就是如何确定 maxThreads 的值,如果这个参数设置小了,Tomcat 会发生线程饥
饿,并且请求的处理会在队列中排队等待,导致响应时间变长;如果 maxThreads 参数值过大,同样
也会有问题,因为服务器的 CPU 的核数有限,线程数太多会导致线程在 CPU 上来回切换,耗费大量
的切换开销。
理论上我们可以通过公式 线程数 = CPU 核心数 *(1+平均等待时间/平均工作时间),计算出一个理想值,这个值只具有指导意义,因为它受到各种资源的限制,实际场景中,我们需要在理想值的基础上进行压测,来获得最佳线程数。
SpringBoot中调整Tomcat参数
方式1: yml中配置 (属性配置类:ServerProperties)

1 server:
2 tomcat:
3 threads:
4 min-spare: 20
5 max: 500
6 connection-timeout: 5000ms

SpringBoot中的TomcatConnectorCustomizer类可用于对Connector进行定制化修改。

1 @Configuration
2 public class MyTomcatCustomizer implements
3 WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
4
5 @Override
6 public void customize(TomcatServletWebServerFactory factory) {
7 factory.setPort(8090);
8 factory.setProtocol("org.apache.coyote.http11.Http11NioProtocol");
9 factory.addConnectorCustomizers(connectorCustomizer());
10 }
11
12 @Bean
13 public TomcatConnectorCustomizer connectorCustomizer(){
14 return new TomcatConnectorCustomizer() {
15 @Override
16 public void customize(Connector connector) {
17 Http11NioProtocol protocol = (Http11NioProtocol)
connector.getProtocolHandler();
18 protocol.setMaxThreads(500);
19 protocol.setMinSpareThreads(20);
20 protocol.setConnectionTimeout(5000);
21 }
22 };
23 }
24
25 }

http://www.hkcw.cn/article/QJMZFhMQwC.shtml

相关文章

Mem0: Building Production-Ready AI Agents with Scalable Long-Term Memory

文章目录 摘要Abstract1. 引言2. Mem03. graph-based Mem0参考总结 摘要 Mem0是一种针对AI智能体的长时记忆架构&#xff0c;旨在解决大型语言模型固定上下文窗口导致的跨会话连贯性问题。其基础版本采用两阶段处理范式&#xff1a;提取阶段通过结合对话摘要、近期消息序列和新…

C++构造函数详解:从基础到高级

文章目录 1. 摘要2. 默认构造函数3. 无参构造函数4. 带参构造函数5. 拷贝构造函数6. 移动构造函数 (C11)7. 委托构造函数 (C11)8. 转换构造函数9. 默认拷贝构造函数10. 总结 1. 摘要 构造函数是C类的重要组成部分&#xff0c;负责对象的初始化工作。本文将详细讲解8种构造函数…

day16 leetcode-hot100-32(链表11)

138. 随机链表的复制 - 力扣&#xff08;LeetCode&#xff09; 1.哈希表 思路 第一次遍历创建新节点并将原节点与新节点同时放入哈希表中 第二次遍历为新节点加入next与random 具体代码 /* // Definition for a Node. class Node {int val;Node next;Node random;public N…

52. N-Queens II

题目描述 52. N-Queens II 回溯法 这道题与第51题是一样的。51. N-Queens-CSDN博客 class Solution {int columns; //从低位到高位起算&#xff0c;第i位为0表示棋盘第i列可以放置皇后&#xff0c;第i位为1表示棋盘第i列不能放置皇后//边长为n的棋盘分别有2n-1条正斜线和反…

关于adb devices无法找到设备:error: device not found 的解决办法

一、此类问题出现的原因&#xff0c;一般是设备所使用的端口被占用&#xff0c;需要找到被占用的端口。 二、操作步骤 1、打开命令窗口。 2、输入adb shell 会发现提示error:device not found&#xff01; 3、输入adb kill-server ,然后启动adb start-server 4、输入 net…

关于不同平台微信多开的解决方案(WIN/MAC/IOS/Andriod)

日常生活跟工作中需要用到多开微信&#xff0c;本次分享下在不同平台上解决微信多开的方法。这些方法我都用了超过1年以上&#xff0c;都比较稳定。中间遇到的一些问题我也有说明&#xff0c;包括每个方法的优缺点&#xff0c;每个平台的操作等。 先说下我自己体验下来的结论&a…

Flutter项目兼容鸿蒙Next系统

一、环境搭建&#xff1a; 1.1 下载鸿蒙DevEco Studio开发工具。 下载地址&#xff1a;下载中心 | 华为开发者联盟-HarmonyOS开发者官网&#xff0c;共建鸿蒙生态 下载之前需要先登录&#xff0c;后面的模拟器创建还要开发者验证、审核啥的&#xff0c;好在审核进度还可以&am…

无需密码强制移除Apple ID 工具! iOS 11.3以下可用 ipad和iphone通用!还不快快收藏起来

无需密码强制移除Apple ID 工具&#xff01; iOS 11.3以下可用 ipad和iphone通用&#xff01;还不快快收藏起来 ✅ 支持的设备范围注意事项使用方法如何下载&#xff1f; 很多朋友在不再使用某个Apple 帐户或是转卖旧的iPhone&#xff0c;为了防止 ID 被盗导致个人信息泄露&…

Mac 散热救星:Macs Fan Control,让你的苹果电脑“冷静”又安静!

各位果粉们&#xff0c;是不是经常遇到这样的烦恼&#xff1a;用着用着电脑&#xff0c;突然就发热卡顿&#xff0c;风扇狂转噪音大得跟拖拉机似的&#xff1f;别担心&#xff0c;今天给大家安利一款超实用的软件 —— Macs Fan Control&#xff0c;它可是让苹果电脑“冷静”又…

mac 安装idea,碰到“已损坏,无法打开,您应该推出磁盘映像”的解决办法

1.打开终端&#xff0c;先输入如下命令&#xff0c;之后回车操作&#xff0c;碰到输入密码时&#xff0c;正常输入密码即可 sudo spctl --master-disable 2.打开系统设置-安全与隐私性-找到‘允许以下来源的应用程序’ &#xff0c;更改为任何来源&#xff0c;碰到输入密码时&…

k8s 四种Service类型(ClusterIP、NodePort、LoadBalancer、ExternalName)详解

🐇明明跟你说过:个人主页 🏅个人专栏:《Kubernetes航线图:从船长到K8s掌舵者》 🏅 🔖行路有良友,便是天堂🔖 目录 一、引言 1、k8s概述 2、Service在Kubernetes中的作用 二、ClusterIP类型 1、ClusterIP 类型的特点和用途 2、ClusterIP 的工作机制 3、…

Android studio 模拟器运行时弹窗显示The emulator process for AVD 模拟器的名字 has terminated 的解决方法

Android studio 问题解决方案 一&#xff1a;.运行模拟器报错展示二.解决步骤第一步:找到.android的文件夹第二步&#xff1a;剪切该文件到SDK的下载路径第三步&#xff1a;点开该文件第四步&#xff1a;找到配置文件第五步&#xff1a;编辑配置文件 三&#xff1a;设置环境变量…

Android Studio快速配置国内镜像源和HTTP代理

最近开始学习Android Studio开发&#xff0c;发现老是连接超时&#xff0c;下面配置下国内镜像源和HTTP代理&#xff0c;而且通过尝试发现最快下载速度方法 一.配置 SDK 镜像源 打开 Android Studio。 进入 File > Settings 然后点击 System Settings,点击 Android SDK。 …

mac/Windows安装python+pycharm【小白操作】

macOS通常自带Python&#xff0c;但建议安装最新版本的Python。你可以通过Homebrew来安装Python。 方法一&#xff1a;通过Homebrew安装Python 安装Homebrew&#xff08;如果尚未安装&#xff09;&#xff1a; 打开终端&#xff0c;输入以下命令并按回车&#xff1a; /bin/bas…

Mac电脑安装Postman教程

Postman 是一款非常常用的 API 调试工具&#xff0c;支持接口测试、自动化测试等功能&#xff0c;本文将分享在 Mac 电脑上安装 Postman 的完整步骤。 1. 下载 Postman 安装包 方法一&#xff1a;官网下载 打开 Postman 官方网站&#xff1a;https://www.postman.com/downlo…

MySQL连接报错处理:1130-host ... is not allowed to connect to this MySql server

在MySQL安装完成后&#xff0c;很多开发者会遇到这样一个问题&#xff1a; 错误代码 1130&#xff1a;host xxx.xxx.xxx.xxx is not allowed to connect to this MySql server 这个错误通常出现在你尝试通过远程工具&#xff08;如 Navicat、DBeaver 等&#xff09;连接 MySQL …

五、单元测试-概述入门

目录 main方法测试缺点&#xff1a; 在pom.xm中&#xff0c;引入junit的依赖。,在test/java目录下&#xff0c;创建测试类&#xff0c;并编写对应的测试方法&#xff0c;并在方法上声明test注解。 练习&#xff1a;验证身份证合法性 测试成功 测试失败 main方法测试缺点&am…

Python中的None值是什么?——浅析“无”的哲学与编程智慧

在Python编程世界中&#xff0c;None是一个极其特殊且基础的概念。它既是一个对象&#xff0c;又是表达“无”与“空”的唯一标识符。它的设计和应用不仅反映了Python对“无”这一哲学问题的简洁处理方式&#xff0c;也蕴含着深刻的编程理念和实践智慧。 本文将深入剖析Python…

Linux正则三剑客篇

一、历史命令 history 命令 &#xff1a;用于输出历史上使用过的命令行数量及具体命令。通过 history 可以快速查看并回顾之前执行过的命令&#xff0c;方便重复操作或追溯执行过程。 !行号 &#xff1a;通过指定历史命令的行号来重新执行该行号对应的命令。例如&#xff0c;若…

配网导线自取电式视频监测装置

在现代社会&#xff0c;电力如同城市的血脉&#xff0c;支撑着生活与生产的正常运转。而电力配网系统&#xff0c;作为电力传输和分配的“最后一公里”&#xff0c;其稳定运行尤为关键。然而&#xff0c;复杂的配电设备、暴露的线路以及多变的外部环境&#xff08;如人为施工破…