- Tomcat线程模型分析及其性能调优
- Tomcat的IO模型
- tomcat设计精髓点
- Tomcat调优
Tomcat 支持的多种 I/O 模型和应用层协议。Tomcat 支持的 I/O 模型有:
| IO模型 | 描述 |
|---|---|
| BIO (JIoEndpoint) | 同步阻塞式IO,即Tomcat使用传统的java.io进行操作。该模式下每个请求都会创建一个线程,对性能开销大,不适合高并发场景。优点是稳定,适合连接数目小且固定架构。 |
| NIO(NioEndpoint) | 同步非阻塞式IO,jdk1.4 之后实现的新IO 该模式基于多路复用选择器监测连接状态再同步通知线程处理,从而达到非阻塞的目的。比传统BIO能更好的支持并发性能。Tomcat 8.0之后默认采用该模式。NIO方式适用于连接数目多且连接比较短(轻操作) 的架构, 比如聊天服务器, 弹幕系统, 服务器间通讯,编程比较复杂 |
| AIO (Nio2Endpoint) | 异步非阻塞式IO,jdk1.7后之支持 。与nio不同在于不需要多路复用选择器,而是请求处理线程执行完成进行回调通知,继续执行后续操作。Tomcat 8之后支持。一般适用于连接数较多且连接时间较长的应用 |
| APR(AprEndpoint) | 全称是 Apache Portable Runtime/Apache可移植运行库),是Apache HTTP服务器的支持库。AprEndpoint 是通过 JNI 调用 APR 本地库而实现非阻塞 I/O 的。使用需要编译安装APR 库 |
注意: Linux 内核没有很完善地支持异步 I/O 模型,因此 JVM 并没有采用原生的 Linux 异步 I/O,而是在应用层面通过 epoll 模拟了异步 I/O 模型。因此在 Linux 平台上,Java NIO 和 Java NIO.2 底层都是通过 epoll 来实现的,但是 Java NIO 更加简单高效。
Tomcat I/O 模型的选择
I/O 调优实际上是连接器类型的选择,一般情况下默认都是 NIO,在绝大多数情况下都是够用的,除非你的 Web 应用用到了 TLS 加密传输,而且对性能要求极高,这个时候可以考虑 APR,因为 APR 通过 OpenSSL 来处理 TLS 握手和加密 / 解密。OpenSSL 本身用 C 语言实现,它还对 TLS 通信做了优化,所以性能比 Java 要高。如果你的 Tomcat 跑在 Windows 平台上,并且 HTTP 请求的数据量比较大,可以考虑 NIO.2,这是因为 Windows 从操作系统层面实现了真正意义上的异步 I/O,如果传输的数据量比较大,异步 I/O 的效果就能显现出来。
指定IO模型只需修改protocol配置:
**NioEndpoint **
上篇博客说过在 Tomcat 中,EndPoint 组件的主要工作就是处理 I/O,而 NioEndpoint 利用 Java NIO API 实现了多路复用 I/O 模型。Tomcat的NioEndpoint 是基于主从Reactor多线程模型设计的
NioEndpoint的设计思路
LimitLatch 是连接控制器,它负责控制最大连接数,NIO 模式下默认是 10000(tomcat9中8192),当连接数到达最大时阻塞线程,直到后续组件处理完一个连接后将连接数减 1。注意到达最大连接数后操作系统底层还是会接收客户端连接,但用户层已经不再接收。
Acceptor 跑在一个单独的线程里,它在一个死循环里调用 accept 方法来接收新连接,一旦有新的连接请求到来,accept 方法返回一个 Channel 对象,接着把 Channel 对象交给 Poller 去处理。
Tomcat线程池扩展
Tomcat线程池默认实现StandardThreadExecutor。Tomcat 线程池和 Java 原生线程池的区别:
自定义了拒绝策略,Tomcat 在线程总数达到最大数时,不是立即执行拒绝策略,而是再尝试向任务队列添加任务,添加失败后再执行拒绝策略。
TaskQueue 重写了 LinkedBlockingQueue 的 offer 方法。只有当前线程数大于核心线程数、小于最大线程数,并且已提交的任务个数大于当前线程数时,也就是说线程不够用了,但是线程数又没达到极限,才会去创建新的线程。目的:在任务队列的长度无限制的情况下,让线程池有机会创建新的线程。
NIO中涉及的对象池技术
Java 对象,特别是一个比较大、比较复杂的 Java 对象,它们的创建、初始化和 GC 都需要耗费 CPU 和内存资源,为了减少这些开销,Tomcat 使用了对象池技术。对象池技术可以减少频繁创建和销毁对象带来的成本,实现对象的缓存和复用,是典型的以空间换时间的设计思路。
SynchronizedStack 内部维护了一个对象数组,并且用数组来实现栈的接口:push 和 pop 方法,这两个方法分别用来归还对象和获取对象。SynchronizedStack 用数组而不是链表来维护对象,可以减少结点维护的内存开销,并且它本身只支持扩容不支持缩容,也就是说数组对象在使用过程中不会被重新赋值,也就不会被 GC。这样设计的目的是用最低的内存和 GC 的代价来实现无界容器,同时 Tomcat 的最大同时请求数是有限制的,因此不需要担心对象的数量会无限膨胀。
Tomcat 的关键指标
Tomcat 的关键指标有吞吐量、响应时间、错误数、线程池、CPU 以及 JVM 内存。前三个指标是我们最关心的业务指标,Tomcat 作为服务器,就是要能够又快有好地处理请求,因此吞吐量要大、响应时间要短,并且错误数要少。后面三个指标是跟系统资源有关的,当某个资源出现瓶颈就会影响前面的业务指标,比如线程池中的线程数量不足会影响吞吐量和响应时间;但是线程数太多会耗费大量 CPU,也会影响吞吐量;当内存不足时会触发频繁地 GC,耗费 CPU,最后也会反映到业务指标上来。
通过 JConsole 监控 Tomcat
连接远程tomcat
linux上,在tomcat安装目录的bin下,新建setenv.sh,重启tomcat
export JAVA_OPTS="-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=9999 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false"
命令行查看 Tomcat 指标
#通过 ps 命令找到 Tomcat 进程,拿到进程 ID ps -ef|grep tomcat #查看进程状态的大致信息 cat/proc//status #监控进程的 CPU 和内存资源使用情况 top -p pid # 查看 Tomcat 的网络连接,比如 Tomcat 在 8080 端口上监听连接请求 netstat -na|grep 8080
线程池的并发调优
线程池调优指的是给 Tomcat 的线程池设置合适的参数,使得 Tomcat 能够又快又好地处理请求。
sever.xml中配置线程池
@Override
public void customize(TomcatServletWebServerFactory factory) {
factory.setPort(8090);
factory.setProtocol("org.apache.coyote.http11.Http11NioProtocol");
factory.addConnectorCustomizers(connectorCustomizer());
}
@Bean
public TomcatConnectorCustomizer connectorCustomizer(){
return new TomcatConnectorCustomizer() {
@Override
public void customize(Connector connector) {
Http11NioProtocol protocol = (Http11NioProtocol) connector.getProtocolHandler();
protocol.setMaxThreads(500);
protocol.setMinSpareThreads(20);
protocol.setConnectionTimeout(5000);
}
};
}
}



