请求在 tomcat 服务器的处理过程(BIO 模式)
客户端与服务端完成三次握手建立了连接,连接信息会存放在 ServerSocket 连接请求的队列中(队列长度为 acceptCount)
如果提交到线程池的任务数没有超过 maxConnections,那么就 ServerSocket.accept() 返回 socket 对象,封装为任务提交到线程池;
如果提交的任务数超过了 maxConnections,则阻塞
任务提交到线程池后,分三种情况:
线程数 <= minSpareThreads:不管有没有空闲线程,都新建线程来处理任务
minSpareThreads < 线程数 < maxThreads:新任务会优先使用空闲线程,如果没有空闲线程就新建线程
线程数 == maxThreads:新任务就会在 Connector 创建的 ServerSocket 队列中堆积起来,直到到达最大的配置值(acceptCount 属性值)
若队列已满,任何再来的请求将会收到 connection refused 错误,直到有可用的资源来处理它们
当任务被处理完后,则销毁任务以及任务中的 socket 对象,该连接被释放
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 等包。
BIO
在 BIO 实现的 Connector 中,处理请求的主要实体是 JIoEndpoint 对象。
JIoEndpoint 维护了 Acceptor 和 Worker:
Acceptor 接收 socket,然后从 Worker 线程池中找出空闲的线程处理 socket,如果 worker 线程池没有空闲线程,则 Acceptor 将阻塞。
Worker 是 Tomcat 自带的线程池,如果通过配置了其他线程池,原理与 Worker 类似。
NIO
在 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 参数
maxConnections :Tomcat 在任意时刻接收和处理的最大连接数(可以提交给线程池的最大任务数)
当 Tomcat 接收的连接数达到 maxConnections 时,Acceptor 线程不会读取 accept 队列中的连接(socket);这时 accept 队列中的线程会一直阻塞着,直到 Tomcat 接收的连接数小于 maxConnections。
如果设置为 -1,则连接数不受限制。
默认值与连接器使用的协议有关:
NIO 的默认值是 10000
APR/native 的默认值是 8192
在windows下,APR/native 的 maxConnections 值会自动调整为设置值以下最大的 1024 的整数倍
如设置为 2000,则最大值实际是 1024
BIO 的默认值为 maxThreads(如果配置了 Executor,则默认值是 Executor 的 maxThreads)
acceptCount :允许的最大并发连接数(瞬时连接、瞬时并发数),为 ServerSocket 连接请求的队列长度,默认值为 100
请求连接在任务队列中时,客户端显示为浏览器显示“转圈”
当 accept 队列中连接的个数达到 acceptCount 时,队列满,进来的请求一律被拒绝。
实际场景中,常见的表象是 nginx 响应 502,Tomcat 中没有任何 access 日志,此时应该调大该值。
minProcessors:服务器启动时,线程池创建的最少线程数
maxProcessors(maxThreads ):线程池最大连接线程数。默认值为 200
线程数小于此数时,每次来任务若有空闲线程,使用空闲线程处理,如果没有空闲线程则新建线程处理
如果该 Connector 绑定了 Executor,这个值会被忽略,因为该 Connector 将使用绑定的 Executor,而不是内置的线程池来执行任务。
注:
maxThreads 规定的是最大的线程数目,并不是实际 running 的 CPU 数量;
实际上,maxThreads 的大小比 CPU 核心数量要大得多。
因为处理请求的线程真正用于计算的时间可能很少,大多数时间可能在阻塞,如等待数据库返回数据、等待硬盘读写数据等。
因此,在某一时刻,只有少数的线程真正的在使用物理 CPU,大多数线程都在等待;
故线程数远大于物理核心数才是合理的。
换句话说,Tomcat 通过使用比 CPU 核心数量多得多的线程数,可以使 CPU 忙碌起来,大大提高 CPU 的利用率
minSpareThreads :线程池最小空闲线程数(多余的空闲线程都将杀死)。默认值为 25
线程数小于此数时,每次来任务都新建线程处理
maxSpareThreads :线程池最大空闲线程数
一旦创建的线程超过这个值,Tomcat 就会关闭不再需要的 socket 线程
maxIdLeTime:一个线程空闲多久算是一个空闲线程,单位:毫秒
connectionTimeout :网络连接超时。单位:毫秒。默认值为 60000ms(60秒)
设置为 0 表示永不超时,但这样设置有隐患的。通常设置为 30000 毫秒或使用默认值
disableUploadTimeout :禁用上传超时,主要用于大数据上传时,允许 Servlet 容器正在执行使用一个较长的连接超时值,以使 Servlet 有较长的时间来完成它的执行,默认值为 false
enableLookups :是否反查域名,取值为:true 或 false
若为 true,则可以通过调用 request.getRemoteHost() 进行 DNS 查询来得到远程客户端的实际主机名
若为 false,则不进行DNS查询,而是返回其 ip 地址
为了提高处理能力,应设置为 false