前言
上一篇文章我们讲解了一下Tomcat底层的结构和执行原理,我们需要重点去掌握的是Tomcat的高内聚低耦合的设计,以及责任链模式,以及Tomcat NIO编程模式,这些是Tomcat比较核心的点,本篇文章我们将对Tomcat的参数做一些了解,然后通过Jemeter压测来对Tomcat进行调优。
一.Tomcat参数
首先我们来了解一下Tomcat可以优化的参数,官方文档上有详细的解释 :https://tomcat.apache.org/tomcat-8.0-doc/config/index.html
1.Connector 连接器优化
Connector连接器是用来处理客户端请求的,Tomcat主要提供了 BIO,NIO,AIO 三种方式,BIO是同步阻塞,适用于少量请求的情况,NIO是同步非阻塞,适用于连接数多并发高的业务场景。Tomcat8默认使用NIO,一般也不需要去修改,如果需要修改IO模式修改server.xml 的Connector
<Connector port="8080" protocol="HTTP/1.1"connectionTimeout="20000"redirectPort="8443"maxParameterCount="1000"/>
protocol=“HTTP/1.1” 默认NIO , 直接修改
- protocol=“org.apache.coyote.http11.Http11NioProtocol” : NIO
- protocol=“org.apache.coyote.http11.Http11Protocol” : BIO ,Tomcat9已经移除
- protocol=“org.apache.coyote.http11.Http11AprProtocol” :AIO ,不稳定,性能没有想象中那么高,不用
所以:连接器一般不用优化
2.Executor 线程池优化
配置文档:https://tomcat.apache.org/tomcat-8.0-doc/config/executor.html
Tomcat底层通过Excutor线程池来处理任务,线程池是我们可以优化的点,如果对线程池不理解的同学可以先去恶补一下哦。官方文档中提供了几个重要的参数
maxThreads (int)
该池中活动线程的最大数量,默认为200 ,最大线程数是一个很重要的优化点,我们应该根据机器的性能去合理设置。优化方案:通常情况下通过压测工具去压测Tomcat,然后修改最大线程数,当CPU及内存占用在80%左右,且平均响应时间和吞吐量达到最优,那么这就是一个合理的线程数。
要注意的是线程数不是越大越好,线程数过多,CPU在线程中的上下文切换会越耗时,反而会导致整体性能下降,或者CPU 100%导致系统卡死的情况。但是如果线程数过少会导致无法充分发挥CPU性能,所以通过压测来修改最大线程数是比较合理的方式。网上也有一些通用公式:线程数 = CPU核心数 * (1 + IO耗时/CPU耗时) ,这个也只能作为参考不一定适合所有场景。
minSpareThreads(int)
始终保持活动状态的最小线程数(空闲和活动),默认为25 , 最小线程数可以根据预估并发量来调整,当并发上来minSpareThreads 线程消耗完就会创建新的线程,最大可以创建maxThreads个线程。
maxIdleTime (int)
非核心线程最大空闲时间,超过这个时间线程会被销毁,默认1分钟,该值一般不需要怎么调整。
maxQueueSize (int)
线程池的等待队列,当最大线程数都使用完之后,请求会在队列中排队,默认值为Integer.MAX_VALUE ,当所有的线程都在忙碌,而队列也满了的时候,请求就会被拒绝,该值根据并发量 和 平均响应时间 预估进行设置,
如果不设置很有可能一瞬间来了大量的请求都会被Tomcat接受从而把资源(CPU/内存)消耗空导致服务器宕机。
我们应该对服务器有一个基本的负载能力预估,比如:我只能处理每秒500的请求,那么注定超过500的请求是需要拒绝掉的,我们需要在响应时间和资源利用率之间找到一个平衡点,所以:如果,队列设置太大会导致大量请求等待,造成平均耗时较长。设置太小又回导致大量请求被拒绝,你需要通过监控和性能测试来确定最佳的maxQueueSize值。观察系统的响应时间、吞吐量、资源利用率等指标,并根据这些指标来调整队列大小。这里给点建议:如果是计算型业务,可以设置几十到几百,如果是IO型业务,可以设置到几百到一千。配置如下:
<Executor name="tomcatThreadPool" namePrefix="catalina-exec-" maxQueueSize="200"maxThreads="200" minSpareThreads="24"/><Connector executor="tomcatThreadPool"port="8080" protocol="HTTP/1.1"connectionTimeout="20000"redirectPort="8443"maxParameterCount="1000" />
3.关闭不要功能
Tomcat有一些组件在某些业务场景下是不需要的,开启后会消耗服务器资源,可以选择关闭
删除AJP
AJP是Tomcat用来兼容阿帕奇的,一般我们都用不到,所以可以在server.xml配置文件中把他删除或者注释
<Connector protocol="AJP/1.3"address="::1"port="8009"redirectPort="8443"maxParameterCount="1000"/>
关闭热部署
Tomcat的autoDeploy表示自动部署,即热部署。当autoDeploy设置为true时,Tomcat会检查appBase目录下是否有新的或被覆盖的WAR包,并会重新加载web程序。这样,你就可以在不重启Tomcat的前提下部署程序,也是比较消耗性能的,生产环境中可以关闭 : autoDeploy=“false”
<Host name="localhost" appBase="webapps"unpackWARs="true" autoDeploy="false">
另外一个就是 reloadable属性,用于控制Tomcat在运行时是否监视Web应用程序的/WEB-INF/classes和/WEB-INF/lib目录下class文件的改动,关闭如下:
<Context docBase="your_webapp_path" path="/your_webapp" reloadable="false"> ...
</Context>
关闭默认Servlet
Tomcat提供了处理html静态资源的DefaultServlet,和处理JSP的JSPServlet,在齐纳后端分离的项目中一般是不需要Tomcat去处理今天资源。而是通过Nginx做动静分离了,所以是可以把它关闭,找到web.xml把下面配置注释即可
<servlet><servlet-name>default</servlet-name><servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class><init-param><param-name>debug</param-name><param-value>0</param-value></init-param><init-param><param-name>listings</param-name><param-value>false</param-value></init-param><load-on-startup>1</load-on-startup></servlet><servlet><servlet-name>jsp</servlet-name><servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class><init-param><param-name>fork</param-name><param-value>false</param-value></init-param><init-param><param-name>xpoweredBy</param-name><param-value>false</param-value></init-param><load-on-startup>3</load-on-startup></servlet>
其实还可以关闭的东西有很多,比如Websocket,session 如果你用不到都可以把他关闭,这样就可以把资源节约出来了。
4.启用数据压缩
Tomcat可以通过配置其服务器设置来启用压缩功能,以减少网络传输的数据量并提高网站的性能和加载速度。
<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" compression="on" compressionMinSize="2048" compressableMimeType="text/html,text/xml,text/javascript,text/css,text/plain" />
- compression : 开启压缩
- compressionMinSize : 小于2M不压缩
- compressableMimeType :针对于哪些数据进行压缩
二.Tomcat项目压测
1.准备项目
IDEA导入Tomcat源码后,创建一个Servlet,在Service方法中sleep 100毫秒模拟方法的耗时,代码如下
public class MyDefaultServlet extends HttpServlet {@Overrideprotected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {try {Thread.sleep(100);System.out.println("================成功访问到Service================");} catch (InterruptedException e) {throw new RuntimeException(e);}}
}
然后找到web.xml文件对Servet做配置,把url映射到 createOrder 路径
<servlet><servlet-name>MyDefaultServlet</servlet-name><servlet-class>org.apache.catalina.servlets.MyDefaultServlet</servlet-class></servlet><servlet-mapping><servlet-name>MyDefaultServlet</servlet-name><url-pattern>/createOrder</url-pattern></servlet-mapping>
项目启动后,我们就可以通过 http://localhost:8080/createOrder来访问项目了
2.JConsole指标监控
Tomcat优化不是盲目设置参数,在Tomcat调优的过程中我们需要对相关参数指标进行监控,比如:错误数、线程池、CPU 以及 JVM 内存等等,根据真实的执行情况进行调试。这里我们通过Java自带的监控工具 jconsole 来监控Tomcat 。 使用cmd执行 jconsole
,然后在本地连接里面找到Tomat的启动进程,点击连接
如果是远程的话需要开启远程连接,我们可以在 Tomcat 的 bin 目录下新建一个名为setenv.sh的文件,内容如下
1 export JAVA_OPTS="${JAVA_OPTS} -Dcom.sun.management.jmxremote"
2 export JAVA_OPTS="${JAVA_OPTS} -Dcom.sun.management.jmxremote.port=8011"
3 export JAVA_OPTS="${JAVA_OPTS} -Djava.rmi.server.hostname=x.x.x.x"
4 export JAVA_OPTS="${JAVA_OPTS} -Dcom.sun.management.jmxremote.ssl=false"
5 export JAVA_OPTS="${JAVA_OPTS} -Dcom.sun.management.jmxremote.authenticate=false"
这样 JMX 的监听端口 8011 就开启了,接下来通过 JConsole 来连接这个端口。
在概览中可以看到:堆内存,线程数,CPU占用率等几个重要指标,下面是线程情况,我们可以看到工作线程,Poller线程,Acceptor线程情况。
有了这些指标我们就可以结合压测工具进行压测,一般在极限压力下CPU和内存使用率达到80%较佳,然后不停的调整Tomcat相关参数,比如:线程池数量,JVM内存大小等,以至于达到一个追加的吞吐量。
我们还可以再MBean里面看到一些核心类的值,比如:请求数量,错误数量等等
3.jemeter工具安装
下载jemeter压测工具:https://jmeter.apache.org/download_jmeter.cgi ,下载后解压,找到bin/jemeter.bat 启动。启动后切换一下语言环境 :options - choose language - chinese
然后再测试计划中 - 右键 - 新建线程组,线程组就是用来模拟客户端的请求的
然后设置线程数,和时间,比如:模拟2S钟500个请求,循环发送10次(一共5000个样本),也可以一直循环发送
添加Http请求,线程组 - 右键 - 添加取样器 - Http请求,然后指定好Tomcat的ip:port和资源路径
如果需要设置请求参数可以在 参数一栏设置
然后给线程组添加 结果报告或者 聚合报告来监控压测结果
4.开始压测
点击:开始按钮,jemeter就会按照设定的线程数向后端发请求进行压力测试了,测试的结果在聚合报告中可以查看,压测效果如下
大概含义是:总共发送了5000个请求,平均耗时 207毫秒,最小耗时100毫秒,最大耗时685毫秒,吞吐量: 每秒1307个请求。
我们可以把线程组 - 循环勾上,这样可以一直让jemeter发请求进行压测,然后观察JVM情况如下:
从jconsole中来看,线程峰值是218,堆内存的使用150M都不到,CPU使用率也才1%不到,这是因为我的电脑配置是:12核,24G内存。所以我的资源使用率是很低的,可以尝试把Tomcat线程数调大。
<Executor name="tomcatThreadPool" namePrefix="catalina-exec-"maxThreads="1000" minSpareThreads="500"/><Connector port="8080" protocol="HTTP/1.1"executor="tomcatThreadPool"connectionTimeout="20000"redirectPort="8443"maxParameterCount="1000"/>
然后把jemeter的压测数据调大,改成1000线程,1s ,然后循环压测效果如下:
这一次堆内存占用达到了:600M ,线程峰值达到了 1020个线程,cpu使用率:2%左右(CPU太强悍了) ,jemeter压测效果:达到了 8600+ QPS ,如下
QPS变高了,但是也出现了错误率,错误率一般是因为并发太高,一瞬间请求太多(1000/1s)导致Tomcat处理不过来拒绝了请求导致的。我们继续加大压测力度,jemeter线程数调整为 2000 ,然后再加大 Tomcat线程数到 2000
<Executor name="tomcatThreadPool" namePrefix="catalina-exec-"maxThreads="2000" minSpareThreads="1000"/>
持续一分钟,jemeter 压测效果如下: 尽管我们把线程数调到 2000,压测出来的吞吐量反而降到了6000左右,而且最大耗时接近9S了,所以Tomcat线程数并不是越大越好
尽管Tomcat官方说的NIO最大可以处理到(1024 * 8)个连接,但是它并不适合你的机器,我们接下里就是不停的去压测和调整线程数获取其他参数来使性能达到最优即可。当然我这里的测试并不精准,因为我使用的是本地PC测试。 项目本身,Jemeter压测工具,Jconsole监控工具都在 本地,都在抢夺CPU和内存,同时还需要考虑 网络开销,所以只能做一个相对值的参考。
在企业中我们应该把应用部署在真实的服务器上,然后在本地通过jemeter远程压测才能测试出真实的效果,然后一边压测一边调试相关参数。
三.其他方面调优
一个系统的性能遵循漏桶原则,所以系统调优需要从多方面进行,比如:应用本身的性能优化的再好,但是业务很耗时,一样会拖慢整体性能。业务很快但是网络不行,也会导致整体性能降低,下面我们从几个方面,讨论一下优化思路。
上面案例中我睡眠了100毫秒来模拟业务耗时,假如我业务耗时只有10毫秒会发生什么情况,是不是性能直接提升十倍呢? 所以我们的业务耗时越小,应用的整体QPS也会越高,在实际的开发中我们优化业务的思路有很多,比如:
1.算法优化
业务越快越好:假如 1个线程1s能处理1次业务,那么200个线程1s能处理200个业务,如果把业务耗时减少到10毫秒,那么整体的性能提升 100倍: 200 * (1000 / 10) = 20000 ,这是理论数据,所以业务越快越好。
优化算法,让代码变得高效,比如:减少嵌套循环,避免大数据循环嵌套处理
2.数据库优化
业务处理难免会和数据库打交道,数据库的数据基于磁盘IO,Java应用和数据库交互也会产生网络IO,当表数据较大时SQL处理就会比较慢,会拖慢整个业务,所以需要做数据库优化,比如:SQL优化,索引优化,主从优化,大表拆解等从而提升查询速度
3.缓存优化
使用缓存:减少数据库操作,减少数据库交互次数,或直接基于Redis 或 本地内存操作。甚至使用Nginx缓存 ,CDN缓存,客户端缓存等
4.优化JVM
高并发下会瞬间创建大量的对象,当内存不够时频繁触发GC,GC的时候会出现STW(停顿应用线程来执行垃圾回收)导致业务处理耗时边长。这个时候就需要进行JVM优化,通过设置JVM参数来减少CG,或者减少Full GC ,选择合适的垃圾回收器也能很大程度提升应用性能。
ps:避免在for循环中new对象哦!!!
5.异步优化
业务能够使用异步就尽可能异步话,可以通过线程池来加大业务处理速度。或者通过MQ消峰,把耗时的部分业务异步处理,能大大提升业务处理速度。
6.硬件优化
给机器或应用增加 内存和CPU,或者网络带宽对性能的提升是非常大的,比如:同样的条件下,2核CPU核8核CPU带来的性能体验是完全不一样的。
7.集群优化
单机优化不动了就可以通过加机器做集群,通过Lvs集群+nginx集群(单机3-5W并发)实现负载均衡,把请求负载到多台Tomcat上一起来处理业务,加入:一台机器能处理2000QPS,那么搞10台就可以处理2W QPS。也可以使用F5这种负载均衡器,他的并发能力可以达到几十万+,可不是Nginx能比的
8.机房分流
如果用户量非常大遍布全国各地,那么可以做CDN分流,比如:成都机房部署一套集群架构能够承担 20W并发,那么根据用户群分布,我可以在上海,北京,杭州等多地都部署几套,然后通过CDN把用户流量负载均衡到对应城市的机房集群中,这样一来每秒百万级请求都可以处理。
所以:说到最后想要抗住高并发,就是加机器,加钱。然而并发量能够每秒上万的项目又有多少,不占20%把。
看到最后的兄弟评论区见:评论区打 “我看到最后啦”