【tomcat】3、预备知识与参数解析

  • 系列文章目录:https://blog.csdn.net/hancoder/category_10106944.html
  • 0 sevlet的知识,从上面目录中找
  • 1 tomcat的安装与目录结构:https://blog.csdn.net/hancoder/article/details/106765035
  • 2 tomcat源码环境搭建: https://blog.csdn.net/hancoder/article/details/113064325
  • 3 tomcat架构与参数:https://blog.csdn.net/hancoder/article/details/113065917
  • 4 tomcat源码分析:https://blog.csdn.net/hancoder/article/details/113062146
  • 5 tomcat调优:https://blog.csdn.net/hancoder/article/details/113065948

一、socket和http

1 tcp和http

socket是tcp层上面封装的协议,http是应用层封装的协议

计算机网络的知识告诉我们,

  • tcp需要先三次握手后才能通信,udp无需握手。
  • 应用层是软件之间的通信,就是我们说的浏览器和服务器
在这里插入图片描述

流程:

  • TCP连接三次握手。可以把这里认为是socket,但是socket是tcp之上的封装
  • 发送HTTP请求
  • 响应HTTP请求

1.1 TCP

要想明白Socket连接,先要明白TCP连接。手机能够使用联网功能是因为手机底层实现了TCP/IP协议,可以使手机终端通过无线网络建立TCP连接。TCP协议可以对上层网络提供接口,使上层网络数据的传输建立在“无差别”的网络之上

建立起一个TCP连接需要经过“三次握手”:

  • 第一次握手:客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认;
  • 第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;
  • 第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。

握手过程中传送的包里不包含数据,三次握手完毕后,客户端与服务器才正式开始传送数据。理想状态下,TCP连接一旦建立,在通信双方中的任何一方主动关闭连接之前,TCP 连接都将被一直保持下去。断开连接时服务器和客户端均可以主动发起断开TCP连接的请求,断开过程需要经过“四次握手”(过程就不细写了,就是服务器和客户端交互,最终确定断开)

1.2 HTTP

HTTP协议即超文本传送协议(Hypertext Transfer Protocol ),是Web联网的基础,也是手机联网常用的协议之一,HTTP协议是建立在TCP协议之上的一种应用。作为应用层协议,HTTP是基于TCP/IP协议来传递数据的(HTML文件、图片、查询结果等),HTTP协议不涉及数据包(Packet)传输,主要规定了客户端和服务器之间的通信格式。

HTTP连接最显著的特点是客户端发送的每次请求都需要服务器回送响应,在请求结束后,会主动释放连接。从建立连接到关闭连接的过程称为“一次连接”。

  • 1)在HTTP 1.0中,客户端的每次请求都要求建立一次单独的连接,在处理完本次请求后,就自动释放连接。也就是不支持长连接
  • 2)在HTTP 1.1中,可以在一次连接中处理多个请求,并且多个请求可以重叠进行,不需要等待一个请求结束后再发送下一个请求。尽管HTTP1.1开始支持持久连接,但仍无法保证始终连接。而Socket连接一旦建立TCP三次握手,除非一方主动断开,否则连接状态一直保持

由于HTTP在每次请求结束后都会主动释放连接,因此HTTP连接是一种“短连接”,要保持客户端程序的在线状态,需要不断地向服务器发起连接请求。通常的做法是即时不需要获得任何数据,客户端也保持每隔一段固定的时间向服务器发送一次“保持连接”的请求,服务器在收到该请求后对客户端进行回复,表明知道客户端“在线”。若服务器长时间无法收到客户端的请求,则认为客户端“下线”,若客户端长时间无法收到服务器的回复,则认为网络已经断开。

一个socket里发送了多个http请求,每个http请求会判断请求头里的connection是否close,是的话处理完关闭socket。

如果是connection:keep-alive,则不会断开。另外tomcat里也有限制同时能同时存在几个活跃的socket,还有一个socket可以处理几个http请求。

对方revcbuff满了就阻塞了

1.3 TCP和HTTP总结

  • HTTP协议:简单对象访问协议,对应于应用层 ,HTTP协议是基于TCP连接的
  • TCP协议: 对应于传输层
  • IP协议: 对应于网络层 TCP/IP是传输层协议,主要解决数据如何在网络中传输;而HTTP是应用层协议,主要解决如何包装数据。

1.4 请求与连接

  • 连接TCP层面的(传输层),对应socket;
  • 请求HTTP层面的(应用层),必须依赖于TCP的连接实现;
  • 一个TCP连接中可能传输多个HTTP请求。

2 socket

Socket是对TCP/IP协议的封装,Socket本身并不是协议,而是一个调用接口(API),通过Socket,我们才能使用TCP/IP协议。

  • HTTP连接:http连接就是所谓的短连接,即客户端向服务器端发送一次请求,服务器端响应后连接即会断掉;
  • socket连接:socket连接就是所谓的长连接,理论上客户端和服务器端一旦建立起连接将不会主动断掉;但是由于各种环境因素可能会是连接断开,比如说:服务器端或客户端主机down了,网络故障,或者两者之间长时间没有数据传输,网络防火墙可能会断开该连接以释放网络资源。所以当一个socket连接中没有数据的传输,那么为了维持连接需要发送心跳消息~~具体心跳消息格式是开发者自己定义的

2.1 socket的步骤

一个socket的步骤:

  • serverSocket.bind(addr,backlog)
  • socket = serverSocket.accept()
  • socket.read()
  • socket.write()

在Netty等文章中说了太多回了,我懒得说了,复制点内容吧

socket是”打开—读/写—关闭”模式的实现,以使用TCP协议通讯的socket为例,其交互流程大概是这样子的

  • 服务器根据地址类型(ipv4,ipv6)、socket类型、协议创建socket
  • 服务器为socket绑定ip地址和端口号
  • 服务器socket监听端口号请求,随时准备接收客户端发来的连接,这时候服务器的socket并没有被打开
  • 客户端创建socket
  • 客户端打开socket,根据服务器ip地址和端口号试图连接服务器socket
  • 服务器socket接收到客户端socket请求,被动打开,开始接收客户端请求,直到客户端返回连接信息。这时候socket进入阻塞状态,所谓阻塞即accept()方法一直到客户端返回连接信息后才返回,开始接收下一个客户端谅解请求
  • 客户端连接成功,向服务器发送连接状态信息
  • 服务器accept方法返回,连接成功
  • 客户端向socket写入信息
  • 服务器读取信息
  • 客户端关闭
  • 服务器端关闭

2.2 bind()

看不懂这没关系,我这里是为了解释,只是为了给源码铺垫,后面我会再指引回来

对我而言,我需要注意bind()那个函数,原来在学习中老师都是交绑定一个本地的接口就可以了,但在tomcat源码阅读中,出现了acceptor这个东西,而且还是个数组,据我观察,他就是bind()里的backlog,怎么理解呢?

在查阅手册里,有如下的内容

public void bind(SocketAddress endpoint,//绑定到的IP地址和端口号。 
                 int backlog)//请求进入连接队列的最大长度。

将ServerSocket绑定到特定地址(IP地址和端口号)。 如果地址为null ,则系统将接收临时端口和有效的本地地址来绑定套接字。 

backlog参数是套接字上请求的最大挂起连接数。 其确切语义是实现具体的。 特别地,实现可以施加最大长度,或者可以选择忽略参数altogther。 提供的价值应大于0 。 如果小于或等于0 ,则将使用实现特定的默认值。 

再结合tomcat里acceptor的知识,那么我就总结如下,

  • accept()、read()、write()都是阻塞的
  • accept()后交给BIO的线程池或者NIO的selector,但也得去交啊,如果不去交呢,岂不是只有一个连接在阻塞等待交给selector?另一个线程连阻塞的机会都没有,得在网卡外面等着。
  • 如果指定了backlog参数,就可以认为即使没有提交给后面的selector等read(),但也可以接收多个连接,只是还没提交而已

从我这个想法出发,也就解释了tomcat的最大连接数为什么是 maxConnections+maxAcceptors 两者和的值。

另外tcp握手和这个accept的逻辑前后关系?网上说三次握手后才进入accept队列,但我更认为accept就是3次握手,具体源码应该在linux中看到,先不验证,没大所谓

3 长连接短连接

https://blog.csdn.net/mccand1234/article/details/91346202

长连接指的是建立了tcp之后可以多次发送http。

长连接:指在一个TCP连接上可以连续发送多个数据包,在TCP连接保持期间,如果没有数据包发送,需要双方发检测包以维持此连接(心跳包),一般需要自己做在线维持。

短连接:是指通信双方有数据交互时,就建立一个TCP连接,数据发送完成后,则断开此TCP连接。

比如Http的,只是连接、请求、关闭,过程时间较短,服务器若是一段时间内没有收到请求即可关闭连接。其实长连接是相对于通常的短连接而说的,也就是长时间保持客户端与服务端的连接状态。

  • 通常的短连接操作步骤是:连接→数据传输→关闭连接;
  • 而长连接通常就是:连接→数据传输→保持连接(心跳)→数据传输→保持连接(心跳)→……→关闭连接;

什么时候用长连接,短连接:

  • 长连接多用于操作频繁,点对点的通讯,而且连接数不能太多情况,。每个TCP连接都需要三步握手,这需要时间,如果每个操作都是先连接,再操作的话那么处理 速度会降低很多,所以每个操作完后都不断开,次处理时直接发送数据包就OK了,不用建立TCP连接。例如:数据库的连接用长连接, 如果用短连接频繁的通信会造成Socket错误,而且频繁的Socket创建也是对资源的浪费。

HTTP连接与Socket连接的区别:

  • HTTP是短连接,Socket(基于TCP协议的)是长连接。尽管HTTP1.1开始支持持久连接,但仍无法保证始终连接。而Socket连接一旦建立TCP三次握手,除非一方主动断开,否则连接状态一直保持。
  • HTTP连接服务端无法主动发消息,Socket连接双方请求的发送先后限制。这点就比较重要了,因为它将决定二者分别适合应用在什么场景下。HTTP采用“请求-响应”机制,在客户端还没发送消息给服务端前,服务端无法推送消息给客户端。必须满足客户端发送消息在前,服务端回复在后。Socket连接双方类似peer2peer的关系,一方随时可以向另一方喊话。

二、servlet

servlet三大组件:servlet、filter、listener。

  • servlet:
  • filter:通过filter里的chain.doFilter(request, response);调用servlet,可以实现在前后打印日志
  • listener:监听应用、会话、请求的事件,

我们定义类实现HttpServlet,放到tomcat中,即可拦截到请求。对应的req和resp实例是tomcat创建的

public class MyServlet extends HttpServlet{
    
    
    // 重写doGet方法
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp){
    
    
        resp.getWriter().println("hhh");
    }
}

三、servlet容器

如下图,浏览器发HTTP请求到服务器后,

  • 如果服务器自己去调用业务类,那么服务器还需要自己调用与实现业务类的逻辑。
  • 如果在把请求交给交给servlet容器,让servlet容器自己去调用业务类,就可以让服务器和业务类解耦

这里的服务器指的是后文的连接器,也就是说服务器应该只负责连接,连接后的结果应该交给servlet容器处理

在这里插入图片描述

如果要把业务类放到servlet容器中,那么它首先得是servlet,那么就设计了servlet接口。接口+容器=servlet规范

而tomcat就是实现了servlet规范,他可以放servlet,他就是一个servlet容器。要实现逻辑,只需要实现servlet然后注册到tomcat等servlet容器即可。

我们的jsp是由dispatchServlet派发过来的,原理还是servlet

servlet容器的工作流程

客户发起请求,http服务器利用连接器把请求获取到,然后把请求交给servlet容器。

连接器把请求先封装为request对象,然后再由适配器封装为servlet request对象,然后就交给servlet容器,就可以调用servlet.service()方法了(会先调用servlet.init()

servlet的知识告诉我们实现service()、doGet()、doPost()等方法都是一个道理。你实现了service()方法就不用父类的service()方法了,你没实现service()方法的话就调用父类的service()方法,父类的service()会调用子类的doGet()、doPost()

另外servlet正常是第一次被访问才创建对象,而在spring中我们是容器初始化后就创建了servlet对象调用了init方法,然后直接可以调用service()

四、NIO等知识

BIO是阻塞的,NIO是非阻塞的,而且可以利用事件机制。

请参考本人博客中其他NIO的知识吧,这里不具体说了,主要继承在netty中。本文只会把tomcat的组件对应到nio的组件上

在使用tomcat时,经常会遇到连接数、线程数之类的配置问题,我们先从NIO方面了解一下,后面再从tomcat的角度了解一下

1 BIO与NIO

BIO的知识告诉我们,因为accept()、read()、write()都是阻塞的方法,造成即使新的连接来了,还是得等当前连接处理完再处理其他连接。然后BIO升级为了线程池版,也就是说read()等业务方法很耗时,那么就可以accept()后把read()逻辑放到线程池里去执行。他的弊端是:

  • 方法都是阻塞的,cpu来回阻塞和唤醒,而且设计用户态和内核态切换,耗时
  • 一个线程只能处理一个连接,因为每个连接都得去线程池里read

NIO的知识告诉我们,连接可以注册到selector上,然后利用事件机制selector.select()获取到哪些连接发送事件需要处理了。这就解决了一个线程可以处理多个连接的问题。

1.2 APR

与NIO更好的是APR(Apache Portable Runtime),是Apache可移植运行库,利用本地库可以实现高可扩展性、高性能;Apr是在Tomcat上运行高并发应用的首选模式,但是需要安装apr、apr-utils、tomcat-native等包。

个人不熟悉,先不讨论

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

再多嘴一句,BIO NIO是TCP层之上的协议,不是HTTP协议,他是对socket的封装

2 tomcat中使用的协议

tomcat因为可以注册多个连接器,但是每个连接器都是一种协议protocol。

暂时你先把连接器认为是 接收请求tcp三次握手+形成request 的工具即可。

其中最典型的protocol包括BIO、NIO和APR(Tomcat7中支持这3种,Tomcat8增加了对NIO2的支持,而到了Tomcat8.5和Tomcat9.0,则去掉了对BIO的支持)。

指定tomcat协议

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

对于每个连接器标签<connector>,可以指定是用于处理HTTP的还是AJP请求的,可以处理不同的协议。而HTTP又分为是用哪种通信方式,阻塞还是非阻塞的

  • HTTP/1.1(默认值,使用的协议与Tomcat版本有关)
    • org.apache.coyote.http11.Http11Protocol:BIO(Tomcat7默认)
    • org.apache.coyote.http11.Http11NioProtocol:NIO(Tomcat8默认)
    • org.apache.coyote.http11.Http11Nio2Protocol:NIO2
    • org.apache.coyote.http11.Http11AprProtocol:APR(Tomcat7、8默认,前提是能找到需要的本地库)
  • AJP

五、tomcat参数解析

1 线程数与CPU数

上面NIO的优势在于用几个线程管理很多连接,或者说一个线程selector去进行selector.select()就可以管理至少1024个连接

线程和CPU的关系:

我们知道java可以new很多线程,线程和cpu什么关系?

线程数可以远远大于CPU个数。

比如线程要去进行(等待数据库返回、读写硬盘、读网卡)或者调用了object.wait()等操作,那么就阻塞了。读网卡什么的操作是可以用DMA去准备数据的,DMA准备好数据后,像CPU发起硬件中断,CPU才继续执行该线程,在这期间CPU可以取处理被的线程。所以说,CPU可以处理很多线程

2 acceptCount

在一、2介绍过bind()于accept的关系

accept表示连接等待队列的长度,如果达到该值,请求都不会等待,用户那直接报错connection refused

当到达最大连接数后,tomcat后将后面的请求存放到任务队列进行排序,任务对垒中排队等待的个数达到acceptCount时,拒绝。

一台tomcat的最大请求数量是 maxConnections+acceptCount

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

accept设置经验

acceptCount的设置,与应用在连接过高情况下希望做出什么反应有关系。如果设置过大,后面进入的请求等待时间会很长;如果设置过小,后面进入的请求立马返回connection refused。

3 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。

可以使用ulimit -a查看服务器限制,对于CPU要求更高(计算型)时,不建议配置过大。对于CPU要求不是特别高时,配置建议在2000左右。

3.2 Connections设置经验

另外,BIO的知识告诉我们,最大线程数==最大连接数,因为没有注册事件机制,必须自己去轮询处理。

NIO的最大线程数<<最大连接数

还有,服务器中可以同时接收的连接数为maxConnections+acceptCount

<Connector port="8080" protocol="HTTP/1.1"
           connectionTimeout="20000"
           redirectPort="8443" />

<Connector protocol="AJP/1.3"
           address="::1"
           port="8009"
           redirectPort="8443" />
3.3 查看连接数

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

# 查看服务器访问数量
netstat -an | grep ESTABLISHED | wc -l  

# 查看端口的连接数
netstat -nat|grep -i "80"|wc -l  
netstat –nat | grep 8083

结果如下所示:

img

可以看出,

  • 有一个连接处于listen状态,监听请求;
  • 还有4个已经建立的连接(ESTABLISHED
  • 2个等待关闭的连接(CLOSE_WAIT)。
# 查看进程数
ps aux|grep httpd|wc -l  

ps -ef|grep httpd|wc -l 

# 查看Apache的并发请求数及其TCP连接状态: 
netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}' 

  返回结果示例: 
  LAST_ACK 5 
  SYN_RECV 30 
  ESTABLISHED 1597 
  FIN_WAIT1 51 
  FIN_WAIT2 504 
  TIME_WAIT 1057 
  其中的 
  SYN_RECV表示正在等待处理的请求数; 
  ESTABLISHED表示正常数据传输状态; 
  TIME_WAIT表示处理完毕,等待超时结束的请求数。

4 maxThreads

这里指的是处理请求的线程数,前提是已经通过accept()了,是指去线程池里进行read的操作的线程

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

Threads设置经验

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

ps –e | grep java

结果如下图:

img

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

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

ps –o nlwp 27989
# 输出
NLWP
73

img

可以看到,该进程内部有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输出的结果如下:

img

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

5 其他参数

这个部分的内容可以跳过

  • KeepAlive:KeepAlive是在HTTP1.1中定义的,用来保持客户机和服务器的长连接,通过减少建立TCP Session的次数来提高性能。常用的配置参数有{ KeepAlive, KeepAliveTimeout, MaxKeepAliveRequests}。
  • KeepAliveTimeout:决定一个KeepAlive的连接能保持多少时间,Timeout就尽快shutdown链接,若还有数据必须再建立新的连接了;其实是read()阻塞时间。长连接就是读完最后一次请求的20s后关闭socket
  • MaxKeepAliveRequests:服务多少个请求就shutdown关闭socket连接。
  • maxThreads:tomcat起动的最大线程数,即同时处理的任务个数,默认值为200
    • 一个socket连接对应一个线程,而一个socket可以接收多个http请求,但是线程数决定了同时处理http请求的数量
    • 每一次HTTP请求到达Web服务,tomcat都会创建一个线程来处理该请求,那么最大线程数决定了Web服务容器可以**同时处理多少个请求**。maxThreads默认200,肯定建议增加。但是,增加线程是有成本的,更多的线程,不仅仅会带来更多的线程上下文切换成本,而且意味着带来更多的内存消耗。JVM中默认情况下在创建新线程时会分配大小为1M的线程栈,所以,更多的线程异味着需要更多的内存。线程数的经验值为:1核2g内存为200,线程数经验值200;4核8g内存,线程数经验值800。
  • maxConnections:tomcat能够接受的最大连接数,maxConnections 默认值是10000。但是还有个参数acceptCount可以决定连接数的大小
    • 对于Java的阻塞式BIO,默认值是maxthreads的值;如果在BIO模式使用定制的Executor执行器,默认值将是执行器中maxthreads的值。
    • 对于Java 新的NIO模式,一个线程可以同时处理多个连接
    • 对于windows上APR/native IO模式,maxConnections默认值为8192,这是出于性能原因,如果配置的值不是1024的倍数,maxConnections 的实际值将减少到1024的最大倍数。
    • 如果设置为-1,则禁用maxconnections功能,表示不限制tomcat容器的连接数。
    • maxConnections和accept-count的关系为:当连接数达到最大值maxConnections后,系统会继续接收连接,但不会超过acceptCount的值。
  • acceptCount:当tomcat起动的线程数达到最大时,接受排队的请求个数,默认值为100
    • 情况1:接受一个请求,此时tomcat起动的线程数没有到达maxThreads,tomcat会起动一个线程来处理此请求。
      情况2:接受一个请求,此时tomcat起动的线程数已经到达maxThreads,tomcat会把此请求放入等待队列,等待空闲线程。
      情况3:接受一个请求,此时tomcat起动的线程数已经到达maxThreads,等待队列中的请求个数也达到了acceptCount,此时tomcat会直接拒绝此次请求,返回connection refused
    • 原来tomcat最大连接数取决于maxConnections这个值加上acceptCount这个值,在连接数达到了maxConenctions之后,tomcat仍会保持住连接,但是不处理,等待其它请求处理完毕之后才会处理这个请求。
图解:maxConnections、maxThreads、acceptCount关系

https://blog.csdn.net/quliuwuyiz/article/details/79979031

用一个形象的比喻,通俗易懂的解释一下tomcat的最大线程数(maxThreads)、最大等待数(acceptCount)和最大连接数(maxConnections)三者之间的关系。

我们可以把tomcat比做一个火锅店,流程是取号、入座、叫服务员,可以做一下三个形象的类比:

(1)acceptCount 最大等待数
可以类比为火锅店的排号处能够容纳排号的最大数量;排号的数量不是无限制的,火锅店的排号到了一定数据量之后,服务往往会说:已经客满。
(2)maxConnections 最大连接数
可以类比为火锅店的大堂的餐桌数量,也就是可以就餐的桌数。如果所有的桌子都已经坐满,则表示餐厅已满,已经达到了服务的数量上线,不能再有顾客进入餐厅了。
(3)maxThreads:最大线程数
可以类比为厨师的个数。每一个厨师,在同一时刻,只能给一张餐桌炒菜,就像极了JVM中的一条线程。

整个就餐的流程,大致如下:
  • (1)取号:如果maxConnections连接数没有满,就不需要取号,因为还有空余的餐桌,直接被大堂服务员领上餐桌,点菜就餐即可。如果 maxConnections 连接数满了,但是取号人数没有达到 acceptCount,则取号成功。如果取号人数已达到acceptCount,则拿号失败,会得到Tomcat的Connection refused connect 的回复信息。
  • (2)上桌:如果有餐桌空出来了,表示maxConnections连接数没有满,排队的人,可以进入大堂上桌就餐。
  • (3)就餐:就餐需要厨师炒菜。厨师的数量,比顾客的数量,肯定会少一些。一个厨师一定需要给多张餐桌炒菜,如果就餐的人越多,厨师也会忙不过来。这时候就可以增加厨师,一增加到上限maxThreads的值,如果还是不够,只能是拖慢每一张餐桌的上菜速度,这种情况,就是大家常见的“上一道菜吃光了,下一道菜还没有上”尴尬场景。

maxConnections、maxThreads、acceptCount关系图如下

在这里插入图片描述

五、tomcat的层次

相信别的文章里介绍过N回tomcat的层次了,死记硬背还是不懂,那我就反向操作从xml的角度说吧

Tomcat 服务器的配置主要集中于 tomcat/conf 下的 catalina.policy、catalina.properties、context.xml、server.xml、tomcat-users.xml、web.xml 文件。

这里写图片描述

1 server

对应Server组件,逻辑上表示整个Tomcat,即整个Catalina Servlet容器。它处于Tomcat顶层,可以包含一个或多个Service层。Tomcat提供了该层接口的一个默认实现,所以通常不需要用户自己去实现。

server.xml 是tomcat 服务器的核心配置文件,包含了Tomcat的 Servlet 容器(Catalina)的所有配置。由于配置的属性特别多,我们在这里主要讲解其中的一部分重要配置。

tomcat是一个servlet容器,同时他是一个server,全局唯一

Server是server.xml的根元素,用于创建一个Server实例,默认使用的实现类是org.apache.catalina.core.StandardServer

<Server port="8005" shutdown="SHUTDOWN">
    ...
</Server>

可选参数:

  • port : Tomcat 监听的关闭服务器的端口。
  • shutdown: 关闭服务器的指令字符串,调优的时候要关闭他。

Server内嵌的子元素为 Listener、GlobalNamingResources、Service。

对于监听器:默认配置的5个Listener ,实现了LifecycleListener接口,是server的监听器

<!-- 用于以日志形式输出服务器 、操作系统、JVM的版本信息-->
<Listener className="org.apache.catalina.startup.VersionLoggerListener" />

<!-- 用于加载(服务器启动) 和 销毁 (服务器停止) APR。 如果找不到APR库, 则会输出日志, 并不影响Tomcat启动 ‐‐>
<Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />

<!-- 用于避免JRE内存泄漏问题 -->
<Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />

<!-- 用户加载(服务器启动) 和 销毁(服务器停止) 全局命名服务 -->
<Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />

<!-- 用于在Context停止时重建Executor 池中的线程, 以避免ThreadLocal 相关的内存泄漏 -->
<Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />

GlobalNamingResources 中定义了全局命名服务:

<!-- Global JNDI resources
Documentation at /docs/jndi‐resources‐howto.html
-->
<GlobalNamingResources>
    <!-- Editable user database that can also be used by UserDatabaseRealm to authenticate users
-->
    <Resource name="UserDatabase" auth="Container" type="org.apache.catalina.UserDatabase"
              description="User database that can be updated and saved"
              factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
              pathname="conf/tomcat‐users.xml" />
</GlobalNamingResources>

2 Service

对应Service组件,是包含在Server层中的一个逻辑功能层。它包含一个Engine层,以及一个或多个连接器(Connector)。

service==多个连接器+一个servlet容器

Service组件将一个或多个Connector组件绑定到Engine层上,Connector组件侦听端口,获得用户请求,并将请求交给Engine层处理,同时把处理结果发给用户,从而实现一个特定的实际功能。Tomcat提供了Service接口的默认实现,所以通常也不需要用户定制。

一个Server服务器,可以包含多个Service服务。

该元素用于创建 Service 实例,默认使用 org.apache.catalina.core.StandardService。默认情况下,Tomcat 仅指定了Service 的名称, 值为 “Catalina”。Service 可以内嵌的子元素为 :Listener、Executor、Connector、Engine,其中 :

  • Listener 用于为Service添加生命周期监听器,
  • Executor用于配置Service 共享线程池,如果连接器没有指定自己的线程池,将使用这个线程池
  • Connector 用于配置Service 包含的链接器,
  • Engine 用于配置Service中链接器对应的Servlet 容器引擎。
<Service name="Catalina">
    ...
</Service>

3 Connector

Connector 用于创建链接器实例。默认情况下,server.xml 配置了两个链接器

  • 一个支持HTTP协议,
  • 一个支持AJP协议。

因此大多数情况下,我们并不需要新增链接器配置,只是根据需要对已有链接器进行优化。

<Connector port="8080" protocol="HTTP/1.1"
           connectionTimeout="20000"
           redirectPort="8443" />

<Connector protocol="AJP/1.3"
           address="::1"
           port="8009"
           redirectPort="8443" />
----------------------------------------
可以配置的参数参考如下
<Connector port="8080"
           protocol="HTTP/1.1"
           executor="tomcatThreadPool" 
           maxThreads="1000"
           minSpareThreads="100" 
           acceptCount="1000" 
           maxConnections="1000" 
           connectionTimeout="20000"
           compression="on" 
           compressionMinSize="2048" 
           disableUploadTimeout="true"
           redirectPort="8443"
           URIEncoding="UTF‐8" />

属性说明:

  • port: 端口号,Connector 用于创建服务端Socket 并进行监听, 以等待客户端请求链接。如果该属性设置为0,Tomcat将会随机选择一个可用的端口号给当前Connector使用。

  • executor:本连接器使用的线程池,指向<executor>标签的引用name

  • protocol : 当前Connector 支持的访问协议。 默认为 HTTP/1.1, 并采用自动切换机制选择一个基于 JAVA NIO的链接器或者基于本地APR的链接器(根据本地是否含有Tomcat的本地库判定)。

    • 如果不希望采用上述自动切换的机制, 而是明确指定协议, 可以使用以下值。

    • // 对于Http协议:
      org.apache.coyote.http11.Http11NioProtocol // 非阻塞式 Java NIO 链接器 
      org.apache.coyote.http11.Http11Nio2Protocol // 非阻塞式 JAVA NIO2 链接器
      org.apache.coyote.http11.Http11AprProtocol  // APR 链接器
      
      <Connector port="8080" protocol="org.apache.coyote.http11.Http11Nio2Protocol"
                 connectionTimeout="20000"
                 redirectPort="8443" />
      ------------------
      // 对于AJP协议 
      
      org.apache.coyote.ajp.AjpNioProtocol  // 非阻塞式 Java NIO 链接器 
      org.apache.coyote.ajp.AjpNio2Protocol //非阻塞式 JAVA NIO2 链接器
      org.apache.coyote.ajp.AjpAprProtocol  //APR 链接器
      
  • connectionTimeOut : Connector 接收链接后的等待超时时间, 单位为 毫秒。 -1 表示不超时。

  • redirectPort:接收到一个请求,当前Connector 不支持SSL请求, 接收到了一个请求, 并且也符合security-constraint 约束, 需要SSL传输,Catalina自动将请求重定向到指定的端口。

  • executor: 指定共享线程池的名称, 也可以通过maxThreads、minSpareThreads等属性配置内部线程池。

  • URIEncoding : 用于指定编码URI的字符编码, Tomcat8.x版本默认的编码为 UTF-8 ,

    • Tomcat7.x版本默认为ISO-8859-1。
3.2 Executor

线程池在service中没有executor标签的话就使用的默认的线程池,多个连接器共享一个线程池。

默认情况下,Service 并未添加共享线程池配置。 如果我们想添加一个线程池, 可以在下添加如下配置:

<!--在service标签中配置线程池-->
<Executor name="tomcatThreadPool" 
          namePrefix="catalina‐exec‐" 
          maxThreads="200" 
          minSpareThreads="100"
          maxIdleTime="60000"
          maxQueueSize="Integer.MAX_VALUE" 
          prestartminSpareThreads="false"
          threadPriority="5"
          className="org.apache.catalina.core.StandardThreadExecutor"/>
然后在下面的connector标签中指定

jconsole工具,找到本地线程的bootstrap,可以在线程里看到http-nio-exec-ID ajp-nio-exec-ID线程(原有的)。如果我们自己命名了<Executor name,那么就能看到我们自己的name-exec-线程id

然后我们在下面的connector标签中配置好后,再查看的话会发现比如nio-exec的名字变了

属性 含义
name 线程池名称,用于 Connector中指定。
namePrefix 所创建的每个线程的名称前缀,一个单独的线程名称为 namePrefix+threadNumber。
maxThreads 池中最大线程数。
minSpareThreads 活跃线程数,也就是核心池线程数,这些线程不会被销毁,会一直存在。
maxIdleTime 线程空闲时间,超过该时间后,空闲线程会被销毁,默认值为6000(1分钟),单位毫秒。
maxQueueSize 在被执行前最大线程排队数目,默认为Int的最大值,也就是广义的无限。除非特殊情况,这个值不需要更改,否则会有请求不会被处理的情况发生。
prestartminSpareThreads 启动线程池时是否启动 minSpareThreads部分线程。默认值为false,即不启动。
threadPriority 线程池中线程优先级,默认值为5,值从1到10。
className 线程池实现类,未指定情况下,默认实现类为 org.apache.catalina.core.StandardThreadExecutor。如果想使用自定义线程池首先需要实现 org.apache.catalina.Executor接口。

如果不配置共享线程池,那么Catalina 各组件在用到线程池时会独立创建。

4 container四大容器

这里写图片描述

4.0 管道

你可能听说过了tomcat有4大容器,而且每个容器有自己的管道。我把四大容器称为EHCW,他们是==父子容器==的关系。

Tomcat里还有个组件pipeline,他有个list属性walve阀门。walve实现类有

实现类为:

  • StandardEngineValve
  • StandardHostValve
  • StandardContextValve
  • StandardWrapperValve

可以实现RequestFilterWalve接口自定义阀门。可以记录日志

Container处理请求是使用Pipeline-Valve管道来处理的!(Valve是阀门之意,通过这个阀门后才进入下一个阀门)

Pipeline-Valve是责任链模式,责任链模式是指在一个请求处理的过程中有很多处理者依次对请求进行处理,每个处理者负责做自己相应的处理,处理完之后,在函数体的最后一句交给下一个处理着继续处理。(转入下一个pipeline处理)

这里写图片描述

在源码中体现为,

请求来了之后new Request,request.setHost,req.setContext,req.setWarpper,


// service的getPipeline().getFirst().invoke()方法
connector.getService().getContainer().getPipeline().getFirst().invoke();

// 在invoke()里最后一句又调用host的方法
host.getPipeline().getFirst().invoke();

// 在他的最后一句又调用context的方法
context.getPipeline().getFirst().invoke();

// 在invoke()里最后一句又调用wrapper的方法
wrapper.getPipeline().getFirst().invoke();

// 然后在wrapper里行过滤链FilterChain,在过滤链的doFilter()里追踪到`Servlet`的`service()`方法
wrapper.allocate()得到servlet,

4.1 Engine

对应Engine组件,该层是请求分发处理层,可以连接多个Connector。它从Connector接收请求后,解析出可以完成用户请求的URL,根据该URL可以把请求匹配到正确的Host上,当Host处理完用户请求后,Engine层把结果返回给适合的连接器,再由连接器传输给用户。该层的接口一般不需要用户来定制,特殊情况下,用户可以通过实现该接口来提供自定义的引擎。

实现类StandardEngine

Engine 作为Servlet 引擎的顶级元素,内部可以嵌入: Cluster、Listener、Realm、Valve和Host

<Engine name="Catalina"
        defaultHost="localhost">
    ...
</Engine>
  • name
  • defaultHost:默认的host。主机输入localhost的时候默认找的host

engine有四大组件:

  • Cluster: (很少使用)实现tomcat集群,例如session共享等功能,通过配置server.xml可以实现,对其包含的所有host里的应用有效,该模块是可选的。其实现方式是基于pipeline+valve模式的,
    • 在调优的文章里我们会看到他可以解决session共享问题,原理是复制,明显不好,所以很少用
  • Realm: 实现用户权限管理模块,例如用户登录,访问控制等,通过通过配置server.xml可以实现,对其包含的所有host里的应用有效,该模块是可选的;
  • Pipeline: 每个容器对象都有一个pipeline,它不是通过server.xml配置产生的,是必须有的。它就是容器对象实现逻辑操作的骨架,在pipeline上配置不同的valve,当需要调用此容器实现逻辑时,就会按照顺序将此pipeline上的所有valve调用一遍,这里可以参考责任链模式;
  • Valve: 实现具体业务逻辑单元。可以定制化valve(实现特定接口),然后配置在server.xml里。对其包含的所有host里的应用有效。定制化的valve是可选的,但是每个容器有一个缺省的valve,例如engine的StandardEngineValve,是在StandardEngine里自带的,它主要实现了对其子host对象的StandardHostValve的调用,以此类推。

4.2 Host

对应Host组件,该层表示一个虚拟主机,一个Engine层可以包含多个Host层,每个Host层可以包含一个或多个Context层,对应不同的Web应用。因为Tomcat给出的Host接口的实现(类StandardHost)提供了重要的附加功能,所以用户通常不需要定制Host。

实现类StandardHost

每个host对应一个webapps目录,记住是webapps,不是下面的项目。因为tomcat里原来只配置了一个localhost,所以可以理解为host就是webapps,而context是我们的项目应用。

Host 元素用于配置一个虚拟主机, 它支持以下嵌入元素:Alias、Cluster、Listener、Valve、Realm、Context。如果在Engine下配置Realm, 那么此配置将在当前Engine下的所有Host中共享。 同样,如果在Host中配置Realm , 则在当前Host下的所有Context中共享。Context中的Realm优先级 >Host 的Realm优先级 >Engine中的Realm优先级。

<Host name="localhost"
      appBase="webapps"
      unpackWARs="true"
      autoDeploy="true">
    ...
</Host>

属性说明:

  • 1) name: 当前Host通用的网络名称, 必须与DNS服务器上的注册信息一致。 Engine中包含的Host必须存在一个名称与Engine的defaultHost设置一致。
  • 2) appBase: 当前Host的应用基础目录, 当前Host上部署的Web应用均在该目录下(可以是绝对目录,相对路径)。默认为webapps这个选项对我们理解很重要
  • 3) unpackWARs设置为true, Host在启动时会将appBase目录下war包解压为目录。设置为false, Host将直接从war文件启动。
  • 4) autoDeploy: 控制tomcat是否在运行时定期检测并自动部署新增或变更的web应用。热部署

怎么理解host,难道我们一台主机还能有多个ip?的确可以,一方面我们可以安装多个网卡设置多个ip,另一方面可以修改/etc/hosts文件,让不同的域名解析DNS都映射到本机的ip,这叫虚拟主机。下面我们假设增加一个host标签

通过给Host添加别名,我们可以实现同一个Host拥有多个网络名称,配置如下:

<Host name="www.AAA.com"
      appBase="webapps"
      unpackWARs="true"
      autoDeploy="true">
    <Alias>www.web2.com</Alias>
</Host>

我们就可以通过www.AAA.com访问那个host了,而不只是localhost

这个时候,我们就可以通过两个域名访问当前Host下的应用(需要确保DNS或hosts中添加了域名的映射配置)。

浏览器请求–>去etc/hosts下进行域名解析,拿到你自己配置的,转到tomcat里

这样就实现了虚拟主机,虽然www.aaa.com 和www.bbb.com不同,但都转到了tomcat下

4.3 Context

对应Context组件,该层代表某个虚拟主机上的实际目录或一个WAR,即单个Web应用程序,它运行在特定的虚拟主机中,使用最为频繁。一个Host层包含多个Context层,每一个Context都有唯一的路径,Host层接到请求后,根据用户请求的URL,将请求定位到Context层。

部署应用的方式:war,文件夹,server.xml,conf。war包解压后就是文件夹,文件夹下必须有WEB-INF/子文件夹,WEB-INF/classes存放class,servlet可以放到这里。类上注解@WebServlet可以指定拦截地址。所以我们得去找dispatcherServlet类。tomcat中有实现类StandardContext

  • 描述符的方式可以指定不放在webapps下的文件夹
  • 配置文件里有unpackWARs属性指定默认解压war包
  • Jar包里只有class文件,需要我们配置其他目录
  • War那个会负责遍历.war结尾的文件,不会管.jar

我们在webapps下可以看到root、examples等目录,这些都是项目,root是指如果只是知道了www主机名,没有指定要访问哪个项目,默认访问的就是那个下面,比如www.baidu.com ,只是指定了host,没有指定context,那么访问的就是root那个项目。

Context 用于配置一个Web应用,默认的配置如下:

<Context docBase="myApp"
        path="/App">
   ....
</Context>

属性描述:

  • 1) docBase:Web应用目录或者War包的部署物理路径。可以是绝对路径,也可以是相对于 Host appBase的相对路径。
  • 2) path:Web应用的Context 访问路径。如果我们Host名为localhost, 则该web应用访问的根路径为: http://localhost:8080/App。

它支持的内嵌元素为:CookieProcessor, Loader, Manager,Realm,Resources,WatchedResource,JarScanner,Valve。

<Host name="www.tomcat.com"	appBase="webapps" unpackWARs="true"
      autoDeploy="true">
    <Context docBase="D:\servlet_project03" path="/myApp"></Context>
    <Valve className="org.apache.catalina.valves.AccessLogValve"
           directory="logs"
           prefix="localhost_access_log" 
           suffix=".txt" pattern="%h %l %u %t &quot;%r&quot; %s %b" />
</Host>

4.4 Servlet

Wrapper:Wrapper是context下面的,httpServlet实现类只有一个实例,共用的话请求过来都调用同一个,会并发不安全。为了解决安全,可以实现singleThreadModel接口,单线程模型,此时每个请求都是一个实例。tomcat最多20实例。多出来的请求阻塞。重用会重新初始化。tomcat中有实现类StandardWrapper

  • 同一个context下可能有多个类型servlet实例,所以要用wrapper对servlet分类

5 tomcat-users.xml

该配置文件中,主要配置的是Tomcat的用户,角色等信息,用来控制Tomcat中manager, host-manager的访问权限。

猜你喜欢

转载自blog.csdn.net/hancoder/article/details/113065917
今日推荐