本文已参与「新人创作礼」活动.一起开启掘金创作之路。
tomcat和nginx性能分析和调优
中间件对性能的影响
通常项目部署都会使用tomcat,一个请求过来先经过nginx,然后到网关,网关再到微服务,服务再到服务之间的调用,中间经过了层层链路。本文针对tomcat和nginx进行分析
| 中间件 | 并发数 | 平均响应 | 90%响应时间 | 最大响应时间 | 错误率(1s内响应) |
|---|---|---|---|---|---|
| B | 200 | 310 | 667 | 707 | 0 |
| A->B | 200 | 469 | 981 | 1008 | 4.5% |
| 网关->A->B | 200 | 876 | 1513 | 1632 | 38.00% |
| nginx->网关->A->B | 200 | 995 | 1602 | 1688 | 38.00% |
通过上面这组数据可以看出每多一个中间件接口响应就慢一些,当中间件越堆越多时,你再怎么取改代码都是没有用的,需要对整个调用链做一个排差,找出最慢的那个服务做优化。
压测工具
jemter介绍
聚合报告参数介绍
- Label:每个 JMeter 的 element(例如 HTTP Request)都有一个 Name 属性,这里显示的就是 Name 属性的值。
- #Samples:表示你这次测试中一共发出了多少个请求,如果模拟10个用户,每个用户迭代10次,那么这里显示100。
- Average:平均响应时间——默认情况下是单个 Request 的平均响应时间,当使用了 Transaction Controller 时,也可以以Transaction 为单位显示平均响应时间。
- Median:中位数,也就是 50% 用户的响应时间。
- 90%95%99% Line: 90% 95% 99% 用户的响应时间。
- Min:最小响应时间。
- Max:最大响应时间。
- Error% :本次测试中出现错误的请求的数量/请求的总数。
- Throughput:吞吐量——默认情况下表示每秒完成的请求数(Request per Second),当使用了 Transaction Controller 时,也可以表示类似 LoadRunner 的 Transaction per Second 数。
- Received KB/Sec:每秒从服务器端接收到的数据量。
Tomcat
Tomcat主要的配置参数
SpringBoot项目默认配置可以在org.springframework.boot.autoconfigure.web.ServerProperties类中查看
maxConnections 最大连接数
这个参数是指在同一时间,Tomcat 能够接受的最大连接数。对于 Java 的阻塞式 BIO,默认值是 maxthreads 的值;可以通过配置 Executor 执行器来修改这个值。
对于 Java 新的 NIO 模式,maxConnections 默认值是 10000。
对于 windows 上 APR/native IO模式,maxConnections 默认值为 8192,这是出于性能原因,如果配置的值不是 1024 的倍数,maxConnections 的实际值将减少到 1024 的最大倍数。
如果设置为 -1,则禁用 maxconnections 功能,表示不限制tomcat容器的连接数。
简单来说就是 Tomcat 总共允许建立多少连接。
maxThreads 最大线程数
每一次 Http 请求到达 Web 服务,Tomcat 都会创建一个线程来处理该请求,最大线程数决定了 Web 服务同时可以处理多少请求。maxThreads 默认值为 200,建议增加,但是增加线程是有成本的,更多的线程代表会有更多的上下文切换,也意味着 JVM 会分配更多的内存。
acceptCount 排队连接数
当 Tomcat 的最大连接数 maxConnections 被占满之后,后续的请求就会进行排队,排队的最大数量就是 acceptCount,举个例子,当前 maxConnections 为 10,acceptCount 为 5,并且 maxConnections 已经使用了 10,那么后续的请求就会排队,每来一个请求,acceptCount 就会 +1 ,当 acceptCount 增加到 5 ,在后续的请求就会被直接放弃。
connection-timeout 连接建立时间
HTTP 协议运行在 TCP 之上,所以每次请求到来,客户端和服务端会建立一次 TCP 连接,建立连接需要三次握手,所以就需要一定的时间,connection-timeout 限制了连接建立的时间,当建立连接时间超过这个值,连接就会建立失败。默认为 20000ms。
Tomcat 的四种线程模型
| IO模型 | 描述 |
|---|---|
| BIO | 同步阻塞式IO,即Tomcat使用传统的java.io进行操作。该模式下每个请求都会创建一个线程,对性能开销大,不适合高并发场景。优点是稳定,适合连接数目小且固定架构。 |
| NIO | 同步非阻塞式IO,jdk1.4 之后实现的新IO。该模式基于多路复用选择器监测连接状态在同步通知线程处理,从而达到非阻塞的目的。比传统BIO能更好的支持并发性能。Tomcat 8.0之后默认采用该模式 |
| APR | 全称是 Apache Portable Runtime/Apache可移植运行库),是Apache HTTP服务器的支持库。可以简单地理解为,Tomcat将以JNI的形式调用Apache HTTP服务器的核心动态链接库来处理文件读取或网络传输操作。使用需要编译安装APR 库 |
| AIO (asynchronous I/O) | 异步非阻塞式IO,jdk1.7后之支持 。与nio不同在于不需要多路复用选择器,而是请求处理线程执行完程进行回调调知,已继续执行后续操作。Tomcat 8之后支持。 |
使用指定IO模型
Tomcat容器
配置 server.xml 文件当中的 修改即可。默认配置 8.0 protocol=“HTTP/1.1” 8.0 之前是 BIO 8.0 之后是NIO
BIO
protocol=“org.apache.coyote.http11.Http11Protocol“
NIO
protocol=”org.apache.coyote.http11.Http11NioProtocol“
AIO
protocol=”org.apache.coyote.http11.Http11Nio2Protocol“
APR
protocol=”org.apache.coyote.http11.Http11AprProtocol“
SpringBoot内置容器
SpringBoot内置的Tomcat默认是基于NIO来实现的
import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.connector.Connector;
import org.apache.coyote.ProtocolHandler;
import org.apache.coyote.http11.AbstractHttp11Protocol;
import org.springframework.boot.web.embedded.tomcat.TomcatConnectorCustomizer;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
import org.springframework.stereotype.Component;
/**
* @author hzt
* @version 1.0.0
* @ClassName AppTomcatConnectorCustomizer.java
* @createTime 2021年07月08日 09:33:00
*/
@Slf4j
@Component
public class AppTomcatConnectorCustomizer implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {
@Override
public void customize(ConfigurableServletWebServerFactory factory) {
((TomcatServletWebServerFactory) factory).setProtocol("org.apache.coyote.http11.Http11Nio2Protocol");
((TomcatServletWebServerFactory) factory).addConnectorCustomizers(new TomcatConnectorCustomizer() {
@Override
public void customize(Connector connector) {
ProtocolHandler protocol = connector.getProtocolHandler();
log.info("Tomcat({}) -- MaxConnection:{};MaxThreads:{};MinSpareThreads:{}", //
protocol.getClass().getName(), //
((AbstractHttp11Protocol<?>) protocol).getMaxConnections(), //
((AbstractHttp11Protocol<?>) protocol).getMaxThreads(), //
((AbstractHttp11Protocol<?>) protocol).getMinSpareThreads());
}
});
}
}
Tomcat connector 并发参数
| 名称 | 描述 |
|---|---|
| acceptCount | 等待最大队列 |
| address | 绑定客户端特定地址,127.0.0.1 |
| bufferSize | 每个请求的缓冲区大小。bufferSize * maxThreads |
| compression | 是否启用文档压缩 |
| compressableMimeTypes | text/html,text/xml,text/plain |
| connectionTimeout | 客户发起链接 到 服务端接收为止,中间最大的等待时间 |
| connectionUploadTimeout | upload 情况下连接超时时间 |
| disableUploadTimeout | true 则使用connectionTimeout |
| enableLookups | 禁用DNS查询 true |
| keepAliveTimeout | 当长链接闲置 指定时间主动关闭 链接 ,前提是客户端请求头 带上这个 head"connection" " keep-alive" |
| maxKeepAliveRequests | 最大的 长连接数 |
| maxHttpHeaderSize | |
| maxSpareThreads | BIO 模式下 最多线闲置线程数 |
| maxThreads(执行线程) | 最大执行线程数 |
| minSpareThreads(初始线业务线程 10) | BIO 模式下 最小线闲置线程数 |
线程模型
BIO线程模型
NIO线程模型
Tomcat性能压测
我们先写一个简单的接口代码如下
@GetMapping("/hello")
public String getList() throws InterruptedException {
//模拟业务请求处理0.6秒
Thread.sleep(600);
return "hello world";
}
使用jemter工具进行压测,不做任何配置修改,使用SpringBoot的默认配置
根据上面介绍的内容可知,默认有200个最大线程数,我们压测200的并发查看结果
为了保证结果的准确性,先对刚启动的项目做1分钟的预热
查看聚合报告可以发现,200并发下错误率为零最大响应时间也只有723完全没问题
接下来修改并发数为201查看压测结果,先看执行一次的结果
通过上图可以看到当并发数超过了最大线程数时,最大响应时间已经到了1058,错误率也达到了0.5%,这错误率就是多出来的1个并发数需要等待空闲线程执行。
修改tomcat配置参数如下所示
server:
port: 8082
servlet:
context-path: /
tomcat:
accept-count: 100 #排队连接数
# 连接超时时间
connection-timeout: 1000
# 最大连接数,可以适应 APR 模式
max-connections: 8192
max-threads: 800
#4核8G内存,线程数经验值为800,操作系统之间做线程之间切换调度是有开销的,不是越多越好
再进行一轮压测查看结果
从上图可以看出设置了800的最大线程数后201并发已经没有异常情况出现,此时如果有801个线程进来又该怎么办。这时候如果在提高最大线程数对性能的提示已经变得微乎其微了,因为,增加线程是有成本的,更多的线程,不仅仅会带来更多的线程上下文切换成本,而且意味着带来更多的内存消耗。JVM中默认情况下在创建新线程时会分配大小为1M的线程栈,所以,更多的线程意味着需要更多的内存。线程数的经验值为:1核2g内存,线程数经验值200;4核8g内存,线程数经验值800。