1、前言
面试的时候被问到对于tomcat相关参数的修改,这里研究不是很深入,平常用springboot 直接打war 包也没有太大的理解,有些时候都忘记自己内部还有个tomcat了,本文主要小总结下Tomcat 的相关调优的知识点。
2.Tomcat 架构
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下边会说到):
Server标签设置的端口号为8005,shutdown=”SHUTDOWN” ,表示在8005端口监听“SHUTDOWN”命令,如果接收到了就会关闭Tomcat。一个Server有一个Service,当然还可以进行配置,一个Service有多个,Service左边的内容都属于Container的,Service下边是Connector。
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 –> 客户端
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 架构
Container处理请求是使用Pipeline-Valve管道来处理的!(Valve是阀门之意)
(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 总结下请求过程
- 假设来自客户的请求为:http://localhost:8080/test/index.jsp 请求被发送到本机端口8080,被在那里侦听的Coyote HTTP/1.1 Connector;
- Connector把该请求交给它所在的Service的Engine来处理,并等待Engine的回应;
- Engine获得请求localhost:8080/test/index.jsp,匹配它所有虚拟主机Host;
- Engine匹配到名为localhost的Host(即使匹配不到也把请求交给该Host处理,因为该Host被定义为该Engine的默认主机);
- localhost Host获得请求/test/index.jsp,匹配它所拥有的所有Context;
- Host匹配到路径为/test的Context(如果匹配不到就把该请求交给路径名为””的Context去处理)
- path=”/test”的Context获得请求/index.jsp,在它的mapping table中寻找对应的servlet;
- Context匹配到URL PATTERN为*.jsp的servlet,对应于JspServlet类,构造HttpServletRequest对象和HttpServletResponse对象,作为参数调用JspServlet的doGet或doPost方法
- Context把执行完了之后的HttpServletResponse对象返回给Host;
- Host把HttpServletResponse对象返回给Engine Engine把HttpServletResponse对象返回给Connector
- Connector把HttpServletResponse对象返回给客户browser
2、Tomcat调优
2.1 采用动静分离
现在很少在 tomcat 上面处理静态资源,一般处理动态资源,将所有对于 静态资源的请求拦截在Nignx上
2.2 线程池优化
打开tomcat的serve.xml,配置Executor,相关参数说明如下。
name:给执行器(线程池)起一个名字;
namePrefix:指定线程池中的每一个线程的 name 前缀;
maxThreads:线程池中最大的线程数量,假设请求的数量超过了 750,这将不是意味着将 maxThreads 属性值设置为 750,它的最好解决方案是使用「Tomcat集群」。也就是说,如果有 1000 请求,两个 Tomcat 实例设置 maxThreads = 500,而不在单 Tomcat 实例的情况下设置 maxThreads=1000。
minSpareThreads:线程池中允许空闲的线程数量(多余的线程都杀死);maxIdLeTime:一个线程空闲多久算是一个空闲线程
对于线程池参数的修改,需要去判断你是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 连接池优化
executor:指定这个连接器所使用的执行器(线程池);
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协议。
禁用的方法也很简单。找到如下配置,直接全部注释掉就好了。
2.5 调节JVM 原生参数
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…