Tomcat中Max-thread配置项对TPS的影响
压测指标以及对应含义:
压力工具中的线程数和用户数与 TPS的关系?
Jmeter中线程数即虚拟用户数量;TPS即每秒事务数。在性能测试过程中,TPS 之所以重要,是因为它可以反应出一个系统的处理能力。
我们常说的并发是什么?
我们要先说明"并发"这个概念是靠什么数据来承载的。在上面的内容中,我们说了好多的指标,但并发是需要具体的指标来承载的。你可以说,我的并发是 1000TPS,或者 1000RPS,或者 1000HPS,这都随便你去定义(一般我们使用TPS来定义这个概念)。但是在一个具体的项目中,当你说到并发 1000 这样没有单位的词时,一定要让大家都能理解这是什么。比如一个线程平均处理请求的时间是250ms,压力工具是 4 个并发线程,由于每个线程都可以在一秒内完成 4 个事务,所以总的 TPS 是 16。
本次压测过程中,主要监控的几项指标: tps,响应时间,错误数,线程数,cpu使用率,jvm内存;
tps,响应时间,错误率可以使用jmeter来进行压测,结合报告进行判断;
线程数,cpu使用率,jvm内存则使用jconsole来进行监控。
jmeter相关参数配置以及指标
测试场景:
使用梯度施压的测试场景,最大压力值为600虚拟用户,每次增加30线程,每次施压持续一分钟。
Jconsole监控tomcat性能指标;
在jdk安装目录下,打开jconsole.exe可执行程序,即可看到如下界面;
这边可以选择本地进程以及远程进程,远程进程则需要配置
export JAVA_OPTS="${JAVA_OPTS} -Dcom.sun.management.jmxremote"
export JAVA_OPTS="${JAVA_OPTS} -Dcom.sun.management.jmxremote.port=9001"
export JAVA_OPTS="${JAVA_OPTS} -Djava.rmi.server.hostname=x.x.x.x"
export JAVA_OPTS="${JAVA_OPTS} -Dcom.sun.management.jmxremote.ssl=false"
export JAVA_OPTS="${JAVA_OPTS} -Dcom.sun.management.jmxremote.authenticate=false"
正确配置后即可看到应用程序的一个概况:
tomcat参数调整:
Tomcat主要负责两件事:
1、处理 Socket 连接,负责网络字节流与 Request 和 Response 对象的转化。
2、加载和管理 Servlet,以及具体处理 Request 请求
因此。Tomcat设计了两个核心组件连接器(Connector)和容器(Container)来分别做这两件事情。连接器负责对外交流,容器负责内部处理。
连接器用 ProtocolHandler来处理网络连接和应用层协议,包含了 2 个重要部件:
Endpoint 接收到 Socket 连接后,生成一个 SocketProcessor 任务提交到线程池去处理,SocketProcessor 的 run 方法会调用 Processor 组件去解析应用层协议,Processor 通过解析生成 Request 对象后,会调用 Adapter 的 Service 方法。
Tomcat关于ProtocolHandler的实现主要有NIO/NIO2/APR三种方式,分别代表非阻塞io,异步io,APR 本地库实现的非阻塞io;
因此Tomcat调优主要基于两个方向:
线程池参数
accept-count:最大等待数
官方文档的说明为:当所有的请求处理线程都在使用时,所能接收的连接请求的队列的最大长度。当队列已满时,任何的连接请求都将被拒绝。accept-count的默认值为100。
详细的来说:当调用HTTP请求数达到tomcat的最大线程数时,还有新的HTTP请求到来,这时tomcat会将该请求放在等待队列中,这个acceptCount就是指能够接受的最大等待数,默认100。如果等待队列也被放满了,这个时候再来新的请求就会被tomcat拒绝(connection refused)。
maxThreads:最大线程数
每一次HTTP请求到达Web服务,tomcat都会创建一个线程来处理该请求,那么最大线程数决定了Web服务容器可以同时处理多少个请求。maxThreads默认200,肯定建议增加。但是,增加线程是有成本的,更多的线程,不仅仅会带来更多的线程上下文切换成本,而且意味着带来更多的内存消耗。JVM中默认情况下在创建新线程时会分配大小为1M的线程栈,所以,更多的线程异味着需要更多的内存。
maxConnections:最大连接数
官方文档的说明为:
这个参数是指在同一时间,tomcat能够接受的最大连接数。对于Java的阻塞式BIO,默认值是maxthreads的值;如果在BIO模式使用定制的Executor执行器,默认值将是执行器中maxthreads的值。对于Java 新的NIO模式,maxConnections 默认值是10000。
对于windows上APR/native IO模式,maxConnections默认值为8192,这是出于性能原因,如果配置的值不是1024的倍数,maxConnections 的实际值将减少到1024的最大倍数。
如果设置为-1,则禁用maxconnections功能,表示不限制tomcat容器的连接数。
maxConnections和accept-count的关系为:当连接数达到最大值maxConnections后,系统会继续接收连接,但不会超过acceptCount的值。
maxConnections、maxThreads、acceptCount关系
用一个形象的比喻,通俗易懂的解释一下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的值,如果还是不够,只能是拖慢每一张餐桌的上菜速度,这种情况,就是大家常见的"上一道菜吃光了,下一道菜还没有上"尴尬场景。
IO模型选择
I/O 调优实际上是连接器类型的选择,一般情况下默认都是 NIO,在绝大多数情况下都是够用的,除非你的 Web 应用用到了 TLS 加密传输,而且对性能要求极高,这个时候可以考虑 APR,因为 APR 通过 OpenSSL 来处理 TLS 握手和加 / 解密。OpenSSL 本身用 C 语言实现,它还对 TLS 通信做了优化,所以性能比 Java 要高。如果你的 Tomcat 跑在 Windows 平台上,并且 HTTP 请求的数据量比较大,可以考虑 NIO.2,这是因为 Windows 从操作系统层面实现了真正意义上的异步 I/O,如果传输的数据量比较大,异步 I/O 的效果就能显现出来。如果你的 Tomcat 跑在 Linux 平台上,建议使用 NIO,这是因为 Linux 内核没有很完善地支持异步 I/O 模型,因此 JVM 并没有采用原生的 Linux 异步 I/O,而是在应用层面通过 epoll 模拟了异步 I/O 模型,只是 Java NIO 的使用者感觉不到而已。因此可以这样理解,在 Linux 平台上,Java NIO 和 Java NIO.2 底层都是通过 epoll 来实现的,但是 Java NIO 更加简单高效。
Springboot中,可通过如下来配置其他protocol
经测试,在设定的几种测试场景中NIO,NIO2对性能影响不大。所以本文使用nio为io模型进行测试;
确定了tomcat调整的方向,我们即可制定几组测试数据来进行压测,观察配置项对各指标的影响,
测试类代码如下:
即sleep 1000ms,然后使用jdbctemplate进行一次数据库查询;
首先我们观察max-thread对并发的影响;
这里配置max-thread = 50;max-thread=200两组数据,进行梯度压测
max-thread = 50
响应时间:
线程数:
TPS:
请求响应时间:
线程数和响应时间:
通过响应时间和线程数图可以看到,线程达到50左右后,响应时间上升的斜度开始升高,说明到达瓶颈,从tps图中可以看到,程序的并发峰值达到50tps左右,在50线程左右达到最高,而后趋于稳定;通过请求与响应时间图可以看到,当每秒请求(rps)达到35-40左右时, 开始出现响应时间波动;通过线程数与响应时间可以看到,当活动线程数达到50左右,响应时间开始出现升高;
max-thread = 200
响应时间:
线程数:
TPS:
请求响应时间:
线程数和响应时间:
通过响应时间和线程数图可以看到,线程达到200左右后,响应时间开始升高,说明性能存在瓶颈,从tps图中可以看到,程序的并发峰值达到200tps左右,在200线程左右达到最高,而后趋于稳定;通过请求与响应时间图可以看到,当每秒请求(rps)达到190左右时, 开始出现响应时间波动;通过线程数与响应时间可以看到,当活动线程数达到200左右,响应时间开始出现升高;
因此可以得到结论,当系统达到瓶颈时,通过提高max-thread可提升系统的吞吐量,TPS=( max-thread/平均响应时间) ,比如接口响应时间平均为20ms,则50的最大thread可达到2500左右的tps,每个线程每秒钟可处理50个请求;但如果盲目调大最大线程数,会造成大量的线程切换,导致吞吐量无法达到预期的值(如下图所示,设置最大线程数为2000,并用2000的虚拟用户进行压测,系统吞吐量在1700左右:由于线程数为2000,但由于大量线程之间切换,平均响应时间却超过了1s,所以导致每秒事务处理量无法到达预期
带宽对性能的影响:
假设服务器带宽为10Mbps,则每秒1.25MB的下载速度。
当对服务器进行压力测试时,如果服务器各项指标利用率还比较低,而tps已经达到瓶颈,则可以先考虑带宽的因素;
tomcat最大并发,是不是就是maxThread配置的数值?
不是。假设接口响应时间在50ms,则每个线程1秒钟可以处理20个请求,则tps为20*maxthreads