Tomcat High Concurrency and Performance Optimization - tenji/ks GitHub Wiki

Tomcat 高并发及性能调优

一、Tomcat 的连接器(Connector)

1.1 BIO (Blocking IO)

在 BIO 实现的 Connector 中,处理请求的主要实体是 JioEndpoint 对象。JioEndpoint 维护了 Acceptor 和 Worker:Acceptor 接收 Socket,然后从 Worker 线程池中找出空闲的线程处理 Socket,如果 Worker 线程池没有空闲线程,则 Acceptor 将阻塞。其中 Worker 是 Tomcat 自带的线程池,如果通过 配置了其他线程池,原理与 Worker 类似。

1.2 NIO (Non-Blocking IO)

NioEndpoint

在 NIO 实现的 Connector 中,处理请求的主要实体是 NioEndpoint 对象。NioEndpoint 中除了包含 Acceptor 和 Worker 外,还是用了 Poller,但是 Acceptor 接收 Socket 后,不是直接使用 Worker 中的线程处理请求,而是先将请求发送给了 Poller,而 Poller 是实现 NIO 的关键。Acceptor 向 Poller 发送请求通过队列实现,使用了典型的订阅模式。在 Poller 中,维护了一个 Selector 对象;当 Poller 从队列中取出 Socket 后,注册到该 Selector 中;然后通过遍历 Selector,找出其中可读的 Socket,并使用 Worker 中的线程处理相应请求。与 BIO 类似,Worker 也可以被自定义的线程池代替。

通过上述过程可以看出,在 NioEndpoint 处理请求的过程中,无论是 Acceptor 接收 Socket,还是线程处理请求,使用的仍然是阻塞方式。但在“读取 Socket 并交给 Worker 中的线程”的这个过程中,使用非阻塞的 NIO 实现,这是 NIO 模式与 BIO 模式的最主要区别(其他区别对性能影响较小,暂时略去不提)。而这个区别,在并发量较大的情形下可以带来 Tomcat 效率的显著提升:

Tomcat 的 NioEndpoit 组件实际上就是实现了 I/O 多路复用模型,正式因为这个并发能力才足够优秀。让我们一起窥探下 Tomcat NioEndpoint 的设计原理。

对于 Java 的多路复用器的使用,无非是两步:

  1. 创建一个 Seletor,在它身上注册各种感兴趣的事件,然后调用 select 方法,等待感兴趣的事情发生。
  2. 感兴趣的事情发生了,比如可以读了,这时便创建一个新的线程从 Channel 中读数据。

Tomcat 的 NioEndpoint 组件虽然实现比较复杂,但基本原理就是上面两步。我们先来看看它有哪些组件,它一共包含 LimitLatch、Acceptor、Poller、SocketProcessor 和 Executor 共 5 个组件,它们的工作过程如下图所示:

正是由于使用了 I/O 多路复用,Poller 内部本质就是持有 Java Selector 检测 channel 的 I/O 事件,当数据可读写的时候创建 SocketProcessor 任务丢到线程池执行,也就是少量线程监听读写事件,接着专属的线程池执行读写,提高性能。

1.3 NIO2 (AIO, Asynchronous IO)

Nio2Endpoint

Nio2Acceptor 扩展了 Acceptor,用异步 I/O 的方式来接收连接,跑在一个单独的线程里,也是一个线程组。Nio2Acceptor 接收新的连接后,得到一个 AsynchronousSocketChannel,Nio2Acceptor 把 AsynchronousSocketChannel 封装成一个 Nio2SocketWrapper,并创建一个 SocketProcessor 任务类交给线程池处理,并且 SocketProcessor 持有 Nio2SocketWrapper 对象。

Executor 在执行 SocketProcessor 时,SocketProcessor 的 run 方法会调用 Http11Processor 来处理请求,Http11Processor 会通过 Nio2SocketWrapper 读取和解析请求数据,请求经过容器处理后,再把响应通过 Nio2SocketWrapper 写出。

Nio2Endpoint 中没有 Poller 组件,也就是没有 Selector。因为在异步 I/O 模式下,Selector 的工作交给内核来做了。

1.4 APR (Apache Portable Runtime)

AprEndpoint

安装起来最困难,但是从操作系统级别来解决异步的 IO 问题,大幅度的提高性能。此种模式下,必须要安装 apr 和 native,直接启动就支持 apr。APR 是在 Tomcat 上运行高并发应用的首选模式。

APR(Apache Portable Runtime Libraries)是 Apache 可移植运行时库,它是用 C 语言实现的,其目的是向上层应用程序提供一个跨平台的操作系统接口库。Tomcat 可以用它来处理包括文件和网络 I/O,从而提升性能。

跟 NioEndpoint 一样,AprEndpoint 也实现了非阻塞 I/O,它们的区别是:NioEndpoint 通过调用 Java 的 NIO API 来实现非阻塞 I/O,而 AprEndpoint 是通过 JNI 调用 APR 本地库而实现非阻塞 I/O 的。

更多关于 JAVA IO 的内容,请查看:JAVA IO

二、Tomcat 调优

2.1 JVM 调优

2.2 关闭 DNS 反向查询

当 WEB 应用程序想要记录客户端的信息时,它也会记录客户端的 IP 地址或者通过域名服务器查找机器名转换为 IP 地址。DNS 查询需要占用网络,并且包括可能从很多很远的服务器或者不起作用的服务器上去获取对应的 IP 的过程,这样会消耗一定的时间。为了消除 DNS 查询对性能的影响我们可以关闭 DNS 查询,所以在connector port="8080"中加入:

enableLookups=“false”

2.3 Tomcat 参数调优

参考链接

⚠️ **GitHub.com Fallback** ⚠️