详解 Tomcat 的连接数与线程池

本贴最后更新于 2216 天前,其中的信息可能已经时移世改

前言

在使用 tomcat 时,经常会遇到连接数、线程数之类的配置问题,要真正理解这些概念,必须先了解 Tomcat 的连接器(Connector)。

在前面的文章 详解 Tomcat 配置文件 server.xml 中写到过:Connector 的主要功能,是接收连接请求,创建 Request 和 Response 对象用于和请求端交换数据;然后分配线程让 Engine(也就是 Servlet 容器)来处理这个请求,并把产生的 Request 和 Response 对象传给 Engine。当 Engine 处理完请求后,也会通过 Connector 将响应返回给客户端。

可以说,Servlet 容器处理请求,是需要 Connector 进行调度和控制的,Connector 是 Tomcat 处理请求的主干,因此 Connector 的配置和使用对 Tomcat 的性能有着重要的影响。这篇文章将从 Connector 入手,讨论一些与 Connector 有关的重要问题,包括 NIO/BIO 模式、线程池、连接数等。

根据协议的不同,Connector 可以分为 HTTP Connector、AJP Connector 等,本文只讨论 HTTP Connector。

一、Nio、Bio、APR

1、Connector 的 protocol

Connector 在处理 HTTP 请求时,会使用不同的 protocol。不同的 Tomcat 版本支持的 protocol 不同,其中最典型的 protocol 包括 BIO、NIO 和 APR(Tomcat7 中支持这 3 种,Tomcat8 增加了对 NIO2 的支持,而到了 Tomcat8.5 和 Tomcat9.0,则去掉了对 BIO 的支持)。

BIO 是 Blocking IO,顾名思义是阻塞的 IO;NIO 是 Non-blocking IO,则是非阻塞的 IO。而 APR 是 Apache Portable Runtime,是 Apache 可移植运行库,利用本地库可以实现高可扩展性、高性能;Apr 是在 Tomcat 上运行高并发应用的首选模式,但是需要安装 apr、apr-utils、tomcat-native 等包。

2、如何指定 protocol

Connector 使用哪种 protocol,可以通过元素中的 protocol 属性进行指定,也可以使用默认值。

指定的 protocol 取值及对应的协议如下:

  • HTTP/1.1:默认值,使用的协议与 Tomcat 版本有关
  • org.apache.coyote.http11.Http11Protocol:BIO
  • org.apache.coyote.http11.Http11NioProtocol:NIO
  • org.apache.coyote.http11.Http11Nio2Protocol:NIO2
  • org.apache.coyote.http11.Http11AprProtocol:APR

如果没有指定 protocol,则使用默认值 HTTP/1.1,其含义如下:在 Tomcat7 中,自动选取使用 BIO 或 APR(如果找到 APR 需要的本地库,则使用 APR,否则使用 BIO);在 Tomcat8 中,自动选取使用 NIO 或 APR(如果找到 APR 需要的本地库,则使用 APR,否则使用 NIO)。

3、BIO/NIO 有何不同

无论是 BIO,还是 NIO,Connector 处理请求的大致流程是一样的:

在 accept 队列中接收连接(当客户端向服务器发送请求时,如果客户端与 OS 完成三次握手建立了连接,则 OS 将该连接放入 accept 队列);在连接中获取请求的数据,生成 request;调用 servlet 容器处理请求;返回 response。为了便于后面的说明,首先明确一下连接与请求的关系:连接是 TCP 层面的(传输层),对应 socket;请求是 HTTP 层面的(应用层),必须依赖于 TCP 的连接实现;一个 TCP 连接中可能传输多个 HTTP 请求。

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

在 NIO 实现的 Connector 中,处理请求的主要实体是 NIoEndpoint 对象。NIoEndpoint 中除了包含 Acceptor 和 Worker 外,还是用了 Poller,处理流程如下图所示(图片来源:http://gearever.iteye.com/blog/1844203)。

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 效率的显著提升:

目前大多数 HTTP 请求使用的是长连接(HTTP/1.1 默认 keep-alive 为 true),而长连接意味着,一个 TCP 的 socket 在当前请求结束后,如果没有新的请求到来,socket 不会立马释放,而是等 timeout 后再释放。如果使用 BIO,“读取 socket 并交给 Worker 中的线程”这个过程是阻塞的,也就意味着在 socket 等待下一个请求或等待释放的过程中,处理这个 socket 的工作线程会一直被占用,无法释放;因此 Tomcat 可以同时处理的 socket 数目不能超过最大线程数,性能受到了极大限制。而使用 NIO,“读取 socket 并交给 Worker 中的线程”这个过程是非阻塞的,当 socket 在等待下一个请求或等待释放时,并不会占用工作线程,因此 Tomcat 可以同时处理的 socket 数目远大于最大线程数,并发性能大大提高。

二、3 个参数:acceptCount、maxConnections、maxThreads

再回顾一下 Tomcat 处理请求的过程:在 accept 队列中接收连接(当客户端向服务器发送请求时,如果客户端与 OS 完成三次握手建立了连接,则 OS 将该连接放入 accept 队列);在连接中获取请求的数据,生成 request;调用 servlet 容器处理请求;返回 response。

相对应的,Connector 中的几个参数功能如下:

1、acceptCount

accept 队列的长度;当 accept 队列中连接的个数达到 acceptCount 时,队列满,进来的请求一律被拒绝。默认值是 100。

2、maxConnections

Tomcat 在任意时刻接收和处理的最大连接数。当 Tomcat 接收的连接数达到 maxConnections 时,Acceptor 线程不会读取 accept 队列中的连接;这时 accept 队列中的线程会一直阻塞着,直到 Tomcat 接收的连接数小于 maxConnections。如果设置为-1,则连接数不受限制。

默认值与连接器使用的协议有关:NIO 的默认值是 10000,APR/native 的默认值是 8192,而 BIO 的默认值为 maxThreads(如果配置了 Executor,则默认值是 Executor 的 maxThreads)。

在 windows 下,APR/native 的 maxConnections 值会自动调整为设置值以下最大的 1024 的整数倍;如设置为 2000,则最大值实际是 1024。

3、maxThreads

请求处理线程的最大数量。默认值是 200(Tomcat7 和 8 都是的)。如果该 Connector 绑定了 Executor,这个值会被忽略,因为该 Connector 将使用绑定的 Executor,而不是内置的线程池来执行任务。

maxThreads 规定的是最大的线程数目,并不是实际 running 的 CPU 数量;实际上,maxThreads 的大小比 CPU 核心数量要大得多。这是因为,处理请求的线程真正用于计算的时间可能很少,大多数时间可能在阻塞,如等待数据库返回数据、等待硬盘读写数据等。因此,在某一时刻,只有少数的线程真正的在使用物理 CPU,大多数线程都在等待;因此线程数远大于物理核心数才是合理的。

换句话说,Tomcat 通过使用比 CPU 核心数量多得多的线程数,可以使 CPU 忙碌起来,大大提高 CPU 的利用率。

4、参数设置

(1)maxThreads 的设置既与应用的特点有关,也与服务器的 CPU 核心数量有关。通过前面介绍可以知道,maxThreads 数量应该远大于 CPU 核心数量;而且 CPU 核心数越大,maxThreads 应该越大;应用中 CPU 越不密集(IO 越密集),maxThreads 应该越大,以便能够充分利用 CPU。当然,maxThreads 的值并不是越大越好,如果 maxThreads 过大,那么 CPU 会花费大量的时间用于线程的切换,整体效率会降低。

(2)maxConnections 的设置与 Tomcat 的运行模式有关。如果 tomcat 使用的是 BIO,那么 maxConnections 的值应该与 maxThreads 一致;如果 tomcat 使用的是 NIO,那么类似于 Tomcat 的默认值,maxConnections 值应该远大于 maxThreads。

(3)通过前面的介绍可以知道,虽然 tomcat 同时可以处理的连接数目是 maxConnections,但服务器中可以同时接收的连接数为 maxConnections+acceptCount 。acceptCount 的设置,与应用在连接过高情况下希望做出什么反应有关系。如果设置过大,后面进入的请求等待时间会很长;如果设置过小,后面进入的请求立马返回 connection refused。

三、线程池 Executor

Executor 元素代表 Tomcat 中的线程池,可以由其他组件共享使用;要使用该线程池,组件需要通过 executor 属性指定该线程池。

Executor 是 Service 元素的内嵌元素。一般来说,使用线程池的是 Connector 组件;为了使 Connector 能使用线程池,Executor 元素应该放在 Connector 前面。Executor 与 Connector 的配置举例如下:

Executor 的主要属性包括:

  • name:该线程池的标记
  • maxThreads:线程池中最大活跃线程数,默认值 200(Tomcat7 和 8 都是)
  • minSpareThreads:线程池中保持的最小线程数,最小值是 25
  • maxIdleTime:线程空闲的最大时间,当空闲超过该值时关闭线程(除非线程数小于 minSpareThreads),单位是 ms,默认值 60000(1 分钟)
  • daemon:是否后台线程,默认值 true
  • threadPriority:线程优先级,默认值 5
  • namePrefix:线程名字的前缀,线程池中线程名字为:namePrefix+ 线程编号

四、查看当前状态

上面介绍了 Tomcat 连接数、线程数的概念以及如何设置,下面说明如何查看服务器中的连接数和线程数。

查看服务器的状态,大致分为两种方案:(1)使用现成的工具,(2)直接使用 Linux 的命令查看。

现成的工具,如 JDK 自带的 jconsole 工具可以方便的查看线程信息(此外还可以查看 CPU、内存、类、JVM 基本信息等),Tomcat 自带的 manager,收费工具 New Relic 等。下图是 jconsole 查看线程信息的界面:

下面说一下如何通过 Linux 命令行,查看服务器中的连接数和线程数。

1、连接数

假设 Tomcat 接收 http 请求的端口是 8083,则可以使用如下语句查看连接情况:

netstat –nat | grep 8083

结果如下所示:

可以看出,有一个连接处于 listen 状态,监听请求;除此之外,还有 4 个已经建立的连接(ESTABLISHED)和 2 个等待关闭的连接(CLOSE_WAIT)。

2、线程

ps 命令可以查看进程状态,如执行如下命令:

ps –e | grep java

结果如下图:

可以看到,只打印了一个进程的信息;27989 是线程 id,java 是指执行的 java 命令。这是因为启动一个 tomcat,内部所有的工作都在这一个进程里完成,包括主线程、垃圾回收线程、Acceptor 线程、请求处理线程等等。

通过如下命令,可以看到该进程内有多少个线程;其中,nlwp 含义是 number of light-weight process。

ps –o nlwp 27989

可以看到,该进程内部有 73 个线程;但是 73 并没有排除处于 idle 状态的线程。要想获得真正在 running 的线程数量,可以通过以下语句完成:

ps -eLo pid ,stat | grep 27989 | grep running | wc -l

其中 ps -eLo pid ,stat 可以找出所有线程,并打印其所在的进程号和线程当前的状态;两个 grep 命令分别筛选进程号和线程状态;wc 统计个数。其中,ps -eLo pid ,stat | grep 27989 输出的结果如下:

图中只截图了部分结果;Sl 表示大多数线程都处于空闲状态。

参考文献

  • Tomcat 7.0 官方文档

Apache Tomcat 7 Configuration Reference (7.0.109) - The HTTP Connector

  • Tomcat 8.0 官方文档

Apache Tomcat 8 Configuration Reference (8.0.53) - The HTTP Connector

  • Tomcat 8.5 官方文档

Apache Tomcat 8 Configuration Reference (8.0.53) - The HTTP Connector

  • Tomcat maxThreads maxConnections acceptCount 参数说明

  • tomcat 架构分析(connector BIO 实现)

tomcat架构分析(connector BIO 实现) - Gearever - ITeye博客

  • tomcat 架构分析 (connector NIO 实现)

http://gearever.iteye.com/blog/1844203

  • Why is the tomcat default thread pool size so large?

  • B3log

    B3log 是一个开源组织,名字来源于“Bulletin Board Blog”缩写,目标是将独立博客与论坛结合,形成一种新的网络社区体验,详细请看 B3log 构思。目前 B3log 已经开源了多款产品:SymSoloVditor思源笔记

    1063 引用 • 3454 回帖 • 189 关注
  • Tomcat

    Tomcat 最早是由 Sun Microsystems 开发的一个 Servlet 容器,在 1999 年被捐献给 ASF(Apache Software Foundation),隶属于 Jakarta 项目,现在已经独立为一个顶级项目。Tomcat 主要实现了 JavaEE 中的 Servlet、JSP 规范,同时也提供 HTTP 服务,是市场上非常流行的 Java Web 容器。

    162 引用 • 529 回帖 • 3 关注
  • Java

    Java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由 Sun Microsystems 公司于 1995 年 5 月推出的。Java 技术具有卓越的通用性、高效性、平台移植性和安全性。

    3190 引用 • 8214 回帖 • 1 关注

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...

推荐标签 标签

  • Q&A

    提问之前请先看《提问的智慧》,好的问题比好的答案更有价值。

    8447 引用 • 38484 回帖 • 155 关注
  • LaTeX

    LaTeX(音译“拉泰赫”)是一种基于 ΤΕΧ 的排版系统,由美国计算机学家莱斯利·兰伯特(Leslie Lamport)在 20 世纪 80 年代初期开发,利用这种格式,即使使用者没有排版和程序设计的知识也可以充分发挥由 TeX 所提供的强大功能,能在几天,甚至几小时内生成很多具有书籍质量的印刷品。对于生成复杂表格和数学公式,这一点表现得尤为突出。因此它非常适用于生成高印刷质量的科技和数学类文档。

    12 引用 • 54 回帖 • 49 关注
  • TGIF

    Thank God It's Friday! 感谢老天,总算到星期五啦!

    288 引用 • 4485 回帖 • 663 关注
  • 又拍云

    又拍云是国内领先的 CDN 服务提供商,国家工信部认证通过的“可信云”,乌云众测平台认证的“安全云”,为移动时代的创业者提供新一代的 CDN 加速服务。

    21 引用 • 37 回帖 • 548 关注
  • CloudFoundry

    Cloud Foundry 是 VMware 推出的业界第一个开源 PaaS 云平台,它支持多种框架、语言、运行时环境、云平台及应用服务,使开发人员能够在几秒钟内进行应用程序的部署和扩展,无需担心任何基础架构的问题。

    5 引用 • 18 回帖 • 172 关注
  • JRebel

    JRebel 是一款 Java 虚拟机插件,它使得 Java 程序员能在不进行重部署的情况下,即时看到代码的改变对一个应用程序带来的影响。

    26 引用 • 78 回帖 • 672 关注
  • CongSec

    本标签主要用于分享网络空间安全专业的学习笔记

    1 引用 • 1 回帖 • 15 关注
  • 学习

    “梦想从学习开始,事业从实践起步” —— 习近平

    171 引用 • 512 回帖
  • TensorFlow

    TensorFlow 是一个采用数据流图(data flow graphs),用于数值计算的开源软件库。节点(Nodes)在图中表示数学操作,图中的线(edges)则表示在节点间相互联系的多维数据数组,即张量(tensor)。

    20 引用 • 19 回帖 • 1 关注
  • Logseq

    Logseq 是一个隐私优先、开源的知识库工具。

    Logseq is a joyful, open-source outliner that works on top of local plain-text Markdown and Org-mode files. Use it to write, organize and share your thoughts, keep your to-do list, and build your own digital garden.

    6 引用 • 63 回帖 • 5 关注
  • 分享

    有什么新发现就分享给大家吧!

    248 引用 • 1795 回帖 • 1 关注
  • 运维

    互联网运维工作,以服务为中心,以稳定、安全、高效为三个基本点,确保公司的互联网业务能够 7×24 小时为用户提供高质量的服务。

    149 引用 • 257 回帖
  • WiFiDog

    WiFiDog 是一套开源的无线热点认证管理工具,主要功能包括:位置相关的内容递送;用户认证和授权;集中式网络监控。

    1 引用 • 7 回帖 • 592 关注
  • WebSocket

    WebSocket 是 HTML5 中定义的一种新协议,它实现了浏览器与服务器之间的全双工通信(full-duplex)。

    48 引用 • 206 回帖 • 318 关注
  • 工具

    子曰:“工欲善其事,必先利其器。”

    288 引用 • 734 回帖 • 2 关注
  • 程序员

    程序员是从事程序开发、程序维护的专业人员。

    574 引用 • 3533 回帖
  • Linux

    Linux 是一套免费使用和自由传播的类 Unix 操作系统,是一个基于 POSIX 和 Unix 的多用户、多任务、支持多线程和多 CPU 的操作系统。它能运行主要的 Unix 工具软件、应用程序和网络协议,并支持 32 位和 64 位硬件。Linux 继承了 Unix 以网络为核心的设计思想,是一个性能稳定的多用户网络操作系统。

    946 引用 • 943 回帖
  • SpaceVim

    SpaceVim 是一个社区驱动的模块化 vim/neovim 配置集合,以模块的方式组织管理插件以
    及相关配置,为不同的语言开发量身定制了相关的开发模块,该模块提供代码自动补全,
    语法检查、格式化、调试、REPL 等特性。用户仅需载入相关语言的模块即可得到一个开箱
    即用的 Vim-IDE。

    3 引用 • 31 回帖 • 104 关注
  • 数据库

    据说 99% 的性能瓶颈都在数据库。

    343 引用 • 723 回帖
  • Postman

    Postman 是一款简单好用的 HTTP API 调试工具。

    4 引用 • 3 回帖 • 7 关注
  • 链滴

    链滴是一个记录生活的地方。

    记录生活,连接点滴

    156 引用 • 3792 回帖
  • Webswing

    Webswing 是一个能将任何 Swing 应用通过纯 HTML5 运行在浏览器中的 Web 服务器,详细介绍请看 将 Java Swing 应用变成 Web 应用

    1 引用 • 15 回帖 • 637 关注
  • HHKB

    HHKB 是富士通的 Happy Hacking 系列电容键盘。电容键盘即无接点静电电容式键盘(Capacitive Keyboard)。

    5 引用 • 74 回帖 • 478 关注
  • jQuery

    jQuery 是一套跨浏览器的 JavaScript 库,强化 HTML 与 JavaScript 之间的操作。由 John Resig 在 2006 年 1 月的 BarCamp NYC 上释出第一个版本。全球约有 28% 的网站使用 jQuery,是非常受欢迎的 JavaScript 库。

    63 引用 • 134 回帖 • 724 关注
  • 快应用

    快应用 是基于手机硬件平台的新型应用形态;标准是由主流手机厂商组成的快应用联盟联合制定;快应用标准的诞生将在研发接口、能力接入、开发者服务等层面建设标准平台;以平台化的生态模式对个人开发者和企业开发者全品类开放。

    15 引用 • 127 回帖
  • Solidity

    Solidity 是一种智能合约高级语言,运行在 [以太坊] 虚拟机(EVM)之上。它的语法接近于 JavaScript,是一种面向对象的语言。

    3 引用 • 18 回帖 • 401 关注
  • 域名

    域名(Domain Name),简称域名、网域,是由一串用点分隔的名字组成的 Internet 上某一台计算机或计算机组的名称,用于在数据传输时标识计算机的电子方位(有时也指地理位置)。

    43 引用 • 208 回帖