Tomcat

1、前言

面试的时候被问到对于tomcat相关参数的修改,这里研究不是很深入,平常用springboot 直接打war 包也没有太大的理解,有些时候都忘记自己内部还有个tomcat了,本文主要小总结下Tomcat 的相关调优的知识点。

2.Tomcat 架构

image.png

tomcat 的本质是一个 servlet 容器,servlet 用于处理相关请求

Tomcat中最顶层的容器是Server,代表着整个服务器,从上图中可以看出,一个Server可以包含至少一个Service,用于具体提供服务。

Service主要包含两个部分:Connector和Container。从上图中可以看出 Tomcat 的心脏就是这两个组件,他们的作用如下

1、Connector用于处理连接相关的事情,并提供Socket与Request和Response相关的转化;

2、Container用于封装和管理Servlet,以及具体处理Request请求;

一个Tomcat中只有一个Server,一个Server可以包含多个Service,一个Service只有一个Container,但是可以有多个Connectors,这是因为一个服务可以有多个连接,如同时提供Http和Https链接,也可以提供向相同协议不同端口的连接,示意图如下(Engine、Host、Context下边会说到):

image.png

Server标签设置的端口号为8005,shutdown=”SHUTDOWN” ,表示在8005端口监听“SHUTDOWN”命令,如果接收到了就会关闭Tomcat。一个Server有一个Service,当然还可以进行配置,一个Service有多个,Service左边的内容都属于Container的,Service下边是Connector。

image.png

2.1 Connector和Container的微妙关系

一个请求发送到Tomcat之后,

首先经过Service然后会交给我们的Connector

Connector用于接收请求并将接收的请求封装为Request和Response来具体处理

Request和Response封装完之后再交由Container进行处理

Container处理完请求之后再返回给Connector

最后在由Connector通过Socket将处理的结果返回给客户端,这样整个请求的就处理完了!

2.2 connector 架构

从上文我们可以直到,一个请求的流程

客户端–>service–>connector(封装成为request、response)–> 交给container处理–> 返回给connector –> 客户端

image.png

Connector就是使用ProtocolHandler来处理请求的,不同的ProtocolHandler代表不同的连接类型,比如:Http11Protocol使用的是普通Socket来连接的,Http11NioProtocol使用的是NioSocket来连接的。

其中ProtocolHandler由包含了三个部件:Endpoint、Processor、Adapter。

(1)Endpoint用来处理底层Socket的网络连接,Processor用于将Endpoint接收到的Socket封装成Request,Adapter用于将Request交给Container进行具体的处理。

(2)Endpoint由于是处理底层的Socket网络连接,因此Endpoint是用来实现TCP/IP协议的,而Processor用来实现HTTP协议的,Adapter将请求适配到Servlet容器进行具体的处理。

(3)Endpoint的抽象实现AbstractEndpoint里面定义的Acceptor和AsyncTimeout两个内部类和一个Handler接口。Acceptor用于监听请求,AsyncTimeout用于检查异步Request的超时,Handler用于处理接收到的Socket,在内部调用Processor进行处理。

2.3 container 架构

image.png

Container处理请求是使用Pipeline-Valve管道来处理的!(Valve是阀门之意)

image.png

image.png

(1)Connector在接收到请求后会首先调用最顶层容器的Pipeline来处理,这里的最顶层容器的Pipeline就是EnginePipeline(Engine的管道);

(2)在Engine的管道中依次会执行EngineValve1、EngineValve2等等,最后会执行StandardEngineValve,在StandardEngineValve中会调用Host管道,然后再依次执行Host的HostValve1、HostValve2等,最后在执行StandardHostValve,然后再依次调用Context的管道和Wrapper的管道,最后执行到StandardWrapperValve。

(3)当执行到StandardWrapperValve的时候,会在StandardWrapperValve中创建FilterChain,并调用其doFilter方法来处理请求,这个FilterChain包含着我们配置的与请求相匹配的Filter和Servlet,其doFilter方法会依次调用所有的Filter的doFilter方法和Servlet的service方法,这样请求就得到了处理!

(4)当所有的Pipeline-Valve都执行完之后,并且处理完了具体的请求,这个时候就可以将返回的结果交给Connector了,Connector在通过Socket的方式将结果返回给客户端。

2.4 总结下请求过程

  1. 假设来自客户的请求为:http://localhost:8080/test/index.jsp 请求被发送到本机端口8080,被在那里侦听的Coyote HTTP/1.1 Connector;
  2. Connector把该请求交给它所在的Service的Engine来处理,并等待Engine的回应;
  3. Engine获得请求localhost:8080/test/index.jsp,匹配它所有虚拟主机Host;
  4. Engine匹配到名为localhost的Host(即使匹配不到也把请求交给该Host处理,因为该Host被定义为该Engine的默认主机);
  5. localhost Host获得请求/test/index.jsp,匹配它所拥有的所有Context;
  6. Host匹配到路径为/test的Context(如果匹配不到就把该请求交给路径名为””的Context去处理)
  7. path=”/test”的Context获得请求/index.jsp,在它的mapping table中寻找对应的servlet;
  8. Context匹配到URL PATTERN为*.jsp的servlet,对应于JspServlet类,构造HttpServletRequest对象和HttpServletResponse对象,作为参数调用JspServlet的doGet或doPost方法
  9. Context把执行完了之后的HttpServletResponse对象返回给Host;
  10. Host把HttpServletResponse对象返回给Engine Engine把HttpServletResponse对象返回给Connector
  11. Connector把HttpServletResponse对象返回给客户browser

2、Tomcat调优

2.1 采用动静分离

现在很少在 tomcat 上面处理静态资源,一般处理动态资源,将所有对于 静态资源的请求拦截在Nignx上

2.2 线程池优化

打开tomcat的serve.xml,配置Executor,相关参数说明如下。

image.png

name:给执行器(线程池)起一个名字;

namePrefix:指定线程池中的每一个线程的 name 前缀;

maxThreads:线程池中最大的线程数量,假设请求的数量超过了 750,这将不是意味着将 maxThreads 属性值设置为 750,它的最好解决方案是使用「Tomcat集群」。也就是说,如果有 1000 请求,两个 Tomcat 实例设置 maxThreads = 500,而不在单 Tomcat 实例的情况下设置 maxThreads=1000。

minSpareThreads:线程池中允许空闲的线程数量(多余的线程都杀死);maxIdLeTime:一个线程空闲多久算是一个空闲线程

image.png

对于线程池参数的修改,需要去判断你是CPU密集型还是IO密集型,默认线程数是200

总结下

Executor元素中有很多可选属性,我简单列举一些常用的属性

1. maxThreads:最大线程数。经验值范围为200-800。可以从400设置起再进行调优。这个一定要参考自己的机器性能,我之前有一台物理机,64G内存,16CPU,8核,可以使用lscpu命令查看具体的cpu信息。maxThreads可以直接设置到1600。jemter1500并发,报错率控制在了0.33%,相当优秀。但是要注意:不要以为自己机器内存很大,我就可以无限创建线程。线程数过多,线程上下文切换也会非常的频繁,导致cpu打满,cpu满了,请求就会无限的等待,直到超时报错。查看上下文切换次数的命令。
vmstat :监控系统上下文切换

2. minSpareThreads: 此项属性开启,tomcat会在启动时预先创建一些线程。默认值为10。此项功能,可以预防流量突然激增。

3.prestartminSpareThreads: 当我们设置了minSpareThreads时,要注意开启这个值。此值默认为false。此值如果不手动设置为true,minSpareThreads的值不会生效。

4. maxQueueSize: 线程池队列的长度。默认值为Integer.MAX_VALUE,当请求数超过maxThreads时,请求就开始放入队列。此处要注意,如果任务处理时间过长,不能使用默认值。否则会导致很多请求报超时错误

5. maxIdleTime: 当线程数多于minSpareThreads且线程空闲时。等待maxIdleTime时间,然后线程就开始销毁,一直销毁到线程的数量等于minSpareThreads停止。

2.3 连接池优化

image.png
executor:指定这个连接器所使用的执行器(线程池);

image.png

image.png

protocol:tomcat启动模式。三种选择:

org.apache.coyote.http11.Http11Protocol:bio

org.apache.coyote.http11.Http11NioProtocol:nio

org.apache.coyote.http11.Http11AprProtocol:apr

executor: connector引用线程池,一旦待引用线程池存在。connector中配置的线程相关的属性将失效(maxThreads,minSpareThreads,prestartminSpareThreads,maxQueueSize等参数)

maxConnections:(bio模式下和maxThreads一致,nio:10000,apr:8192。它的意思是:最大连接数。设置为-1,代表不限制连接数

connectionTimeout: 经验值为2000-60000(默认值60000ms)当tomcat接受了连接,但是不能即时处理,会等待connectionTimeout时间。超过此时间,就返回客户端超时错误。此值单位为毫秒。如果设置为-1,将不限制超时

2.4 禁用AJP

AJP协议是tomcat专门用来处理html静态文件的协议。如果我们网站采用的是nginx+tomcat的架构方式,那AJP协议就完全多余了。静态文件的访问,通过nginx完全就可以实现。AJP相对nginx来说,就复杂一点了,效率肯定没有nginx高。所以说,我们可以直接禁用AJP协议。

禁用的方法也很简单。找到如下配置,直接全部注释掉就好了。

image.png

2.5 调节JVM 原生参数

image.png
Xms:堆最小内存

Xmx:堆最大内存

Dfile.encoding:服务端编码

PermSize:非堆内存容量

MaxPermSize:非堆内存最大容量

3 Tomcat 线程池对比 jdk 线程池

java线程池

如果当前运行的线程,少于corePoolSize,则创建一个新的线程来执行任务。

如果运行的线程等于或多于 corePoolSize,将任务加入 BlockingQueue。

如果 BlockingQueue 内的任务超过上限,则创建新的线程来处理任务。

如果创建的线程超出 maximumPoolSize,任务将被拒绝策略拒绝。

tomcat线程池

如果当前运行的线程,少于corePoolSize,则创建一个新的线程来执行任务。

如果线程数大于 corePoolSize了,Tomcat 的线程不会直接把线程加入到无界的阻塞队列中,而是去判断submittedCount(已经提交线程数)是否等于 maximumPoolSize。

如果等于,表示线程池已经满负荷运行,不能再创建线程了,直接把线程提交到队列,

如果不等于,则需要判断,是否有空闲线程可以消费。

如果有空闲线程则加入到阻塞队列中,等待空闲线程消费。

如果没有空闲线程,尝试创建新的线程。(这一步保证了使用无界队列,仍然可以利用线程的 maximumPoolSize)。

如果总线程数达到 maximumPoolSize,则继续尝试把线程加入 BlockingQueue 中。

如果 BlockingQueue 达到上限(假如设置了上限),被默认线程池启动拒绝策略,tomcat 线程池会 catch 住拒绝策略抛出的异常,再次把尝试任务加入中 BlockingQueue 中。

再次加入失败,启动拒绝策略。

总结

JDK 的线程 适合于 CPU 密集任务,在核心线程数跑满后,先丢入阻塞队列,当阻塞队列满了,再创建线程,直到最大线程数,最后执行拒绝策略

Tomcat的线程 适合于 IO密集型,再核心线程数跑满后,当还有任务出现,会继续创建新的线程直到最大线程数,如果最大线程数已经达到,还有任务,丢入阻塞队列,阻塞队列满了,再去执行拒绝策略

参考

juejin.cn/post/684490…
blog.csdn.net/xlgen157387…
www.pdai.tech/md/framewor…
www.cnblogs.com/lyhero11/p/…
blog.csdn.net/qq_39839075…

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享