模拟请求,简单分析Tomcat参数作用

/ 后端 / 没有评论 / 63浏览

常用设置参数

spring:
  servlet:
    multipart:
      #上传单个文件大小限制
      max-file-size: 5MB
      #单次请求全部文件及数据大小限制
      max-request-size: 10MB
server:
  tomcat:
    #表单请求最大限制
    max-http-form-post-size: 2MB
    max-swallow-size: 2MB
    #1:关闭长连接 -1:不限制 
    max-keep-alive-requests: 100
    #长连接过期时间
    keep-alive-timeout: 5S
    #服务器在任何给定时间接受和处理的最大连接数;
    #一旦达到了这个限制,操作系统仍然可以根据“acceptCount”属性接受连接;
    max-connections: 8192
    #在接受连接后,连接器为呈现请求URI行而等待的时间。
    connection-timeout: 3S
    #当所有可能的请求处理线程都在使用时,传入连接请求的最大队列长度;
    accept-count: 100
    #工作线程
    threads:
      #最大数量
      max: 200
      #最小数量
      min-spare: 10

```  ` 

# 测试
### 注意
测试需要使用postman或者jmeter等工具,如使用浏览器点击测试,请不要用chrome;
(谷歌浏览器同时只能对同一个URL提出一个请求,如果有更多的请求的话,则会串行执行。如果请求阻塞,后续相同请求也会阻塞。)

### 测试程序,模拟耗时操作
```java
 @GetMapping("sleep")
    public String sleep() {
        System.out.println(DateTimeFormatter.ofPattern("HH🇲🇲ss").format(LocalTime.now()) + "  进入方法");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "ok";
    }

1.工作线程数threads.max

(1)threads.max=10;jmeter线程数50进行压测; 控制台输出:

11:00:33  进入方法
11:00:33  进入方法
11:00:33  进入方法
11:00:33  进入方法
11:00:33  进入方法
11:00:33  进入方法
11:00:33  进入方法
11:00:33  进入方法
11:00:33  进入方法
11:00:33  进入方法
11:00:38  进入方法
11:00:38  进入方法
11:00:38  进入方法
11:00:38  进入方法
11:00:38  进入方法
11:00:38  进入方法
11:00:38  进入方法
11:00:38  进入方法
11:00:38  进入方法
11:00:38  进入方法
11:00:43  进入方法
11:00:43  进入方法
11:00:43  进入方法
11:00:43  进入方法
11:00:43  进入方法
11:00:43  进入方法
11:00:43  进入方法
11:00:43  进入方法
11:00:43  进入方法
11:00:43  进入方法

可以看到,每次并发执行10条后,等待执行完毕后继续执行;

结论: 工作线程数据控制了并发执行的线程数;其余请求则排队执行;

2.max-connections

####(1)最大连接数大于线程数,threads.max=10,max-connections=20,accept-count=100;jmeter线程数100进行压测; 正常执行;

结论: 最大并发执行线程数小于最大连接数;即使模拟请求并发量大于tomcat线程数,超过最大连接数,也能正常进行排队等待执行;

####(2)最大连接数小于线程数,threads.max=10,max-connections=5,accept-count=100;jmeter线程数100进行压测;

11:15:36  进入方法
11:15:36  进入方法
11:15:36  进入方法
11:15:36  进入方法
11:15:36  进入方法
11:15:41  进入方法
11:15:41  进入方法
11:15:41  进入方法
11:15:41  进入方法
11:15:41  进入方法

结论: 依然正常执行,不过每次并发数从设置的线程数10减少到了5,tomcat选择根据connect的数量执行;

3.accept-count

(1)最大连接数高于并发请求数,排队数较小,threads.max=5,max-connections=1000,accept-count=10;jmeter线程数100进行压测;

11:18:42  进入方法
11:18:42  进入方法
11:18:42  进入方法
11:18:42  进入方法
11:18:42  进入方法
11:18:47  进入方法
11:18:47  进入方法
11:18:47  进入方法
11:18:47  进入方法
11:18:47  进入方法

正常执行;

(2)最大连接数少于并发请求数,排队数小于请求数,threads.max=5,max-connections=20,accept-count=10;jmeter线程数100进行压测;

org.apache.http.conn.HttpHostConnectException: Connect to localhost:8080 [localhost/127.0.0.1, localhost/0:0:0:0:0:0:0:1] failed: Connection refused: connect
	at org.apache.http.impl.conn.DefaultHttpClientConnectionOperator.connect(DefaultHttpClientConnectionOperator.java:159)
	at org.apache.jmeter.protocol.http.sampler.HTTPHC4Impl$JMeterDefaultHttpClientConnectionOperator.connect(HTTPHC4Impl.java:331)
	at org.apache.http.impl.conn.PoolingHttpClientConnectionManager.connect(PoolingHttpClientConnectionManager.java:373)
	at org.apache.http.impl.execchain.MainClientExec.establishRoute(MainClientExec.java:394)
	at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:237)
	at org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:185)
	at org.apache.http.impl.execchain.RetryExec.execute(RetryExec.java:89)
	at org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:110)
	at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:185)
	at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:83)
	at org.apache.jmeter.protocol.http.sampler.HTTPHC4Impl.executeRequest(HTTPHC4Impl.java:832)
	at org.apache.jmeter.protocol.http.sampler.HTTPHC4Impl.sample(HTTPHC4Impl.java:570)
	at org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy.sample(HTTPSamplerProxy.java:67)
	at org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase.sample(HTTPSamplerBase.java:1231)
	at org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase.sample(HTTPSamplerBase.java:1220)
	at org.apache.jmeter.threads.JMeterThread.doSampling(JMeterThread.java:622)
	at org.apache.jmeter.threads.JMeterThread.executeSamplePackage(JMeterThread.java:546)
	at org.apache.jmeter.threads.JMeterThread.processSampler(JMeterThread.java:486)
	at org.apache.jmeter.threads.JMeterThread.run(JMeterThread.java:253)
	at java.lang.Thread.run(Unknown Source)
Caused by: java.net.ConnectException: Connection refused: connect
	at java.net.DualStackPlainSocketImpl.connect0(Native Method)
	at java.net.DualStackPlainSocketImpl.socketConnect(Unknown Source)
	at java.net.AbstractPlainSocketImpl.doConnect(Unknown Source)
	at java.net.AbstractPlainSocketImpl.connectToAddress(Unknown Source)
	at java.net.AbstractPlainSocketImpl.connect(Unknown Source)
	at java.net.PlainSocketImpl.connect(Unknown Source)
	at java.net.SocksSocketImpl.connect(Unknown Source)
	at java.net.Socket.connect(Unknown Source)
	at org.apache.http.conn.socket.PlainConnectionSocketFactory.connectSocket(PlainConnectionSocketFactory.java:75)
	at org.apache.http.impl.conn.DefaultHttpClientConnectionOperator.connect(DefaultHttpClientConnectionOperator.java:142)
	... 19 more

程序接收到jmeter报错,连接被拒绝;

(3)最大连接数少于并发请求数,排队数大于请求书,threads.max=5,max-connections=20,accept-count=100;jmeter线程数100进行压测;

正常执行;

(4)最大连接数大于并发请求数,请求数超出 最大工作线程+队列,threads.max=5,max-connections=100,accept-count=10;jmeter线程数100进行压测;

正常执行;

4.connection-timeout

(即与客户端连接后,等待客户端发送来数据的时间)
此参数专门用于对抗一种类型的拒绝服务攻击,即一些恶意客户端创建到服务器的 TCP 连接(其效果是 
在服务器上保留一些资源以处理此连接),并且然后就坐在那里而不在该连接上发送任何 HTTP 请求。 
通过缩短此延迟,您可以缩短分配服务器资源的时间,以服务永远不会到来的请求。

1.将connection-timeout=3S,编写程序请求测试

 public static void main(String[] args) {
        try {
            Socket socket = new Socket("127.0.0.1", 8080);
            StringBuffer sb = new StringBuffer();
            sb.append("GET /sleep/ HTTP/1.1\r\n");// 注意\r\n为回车换行
            sb.append("Accept-Language: zh-cn\r\n");
            sb.append("Connection: keep-alive\r\n");
            sb.append("Host:localhost\r\n");
            sb.append("\r\n");
            //连接后,此处睡眠阻塞,不进行发送报文操作
            Thread.sleep(5000);
                        
            OutputStream outputStream = socket.getOutputStream();
            InputStream inputStream = socket.getInputStream();
            byte[] bytes = sb.toString().getBytes();

            outputStream.write(bytes);
            outputStream.flush();

            byte[] b = new byte[8096];
            int len = 0;
            while ((len = inputStream.read(b)) != -1) {
                System.out.println(new String(b, 0, len));
            }
            inputStream.close();
            outputStream.close();
            socket.close();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

控制台报错:

java.net.SocketException: Software caused connection abort: recv failed
	at java.net.SocketInputStream.socketRead0(Native Method)
	at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
	at java.net.SocketInputStream.read(SocketInputStream.java:171)
	at java.net.SocketInputStream.read(SocketInputStream.java:141)
	at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
	at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)
	at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
	at java.io.InputStreamReader.read(InputStreamReader.java:184)
	at java.io.BufferedReader.fill(BufferedReader.java:161)
	at java.io.BufferedReader.readLine(BufferedReader.java:324)
	at java.io.BufferedReader.readLine(BufferedReader.java:389)
	at com.example.tomcattest.TomcatTestApplicationTests.main(TomcatTestApplicationTests.java:29)
Disconnected from the target VM, address: '127.0.0.1:6296', transport: 'socket'

Process finished with exit code 0

结论: 1.connection-timeout超时时间是作用于客户端的,当建立连接后,客户端超时时间之前未发送报文,则断开连接;

2.所以一般的高并发服务端,不宜将connection-timeout设置过长,以免造成线程阻塞;

5.keep-alive

优点:

1.可以保活tcp连接会话,减少频繁创建会话;尤其是https请求,很大程度减少频繁握手校验等造成的损失;

2.可以减少产生time wait;

缺点:

1.由于服务端接收到tcp会话,所以就算后续客户端没有往来发送报文数据,依然会占用连接,以及占用工作线程;

结论

KeepAlive在增加访问效率的同时,也会增加服务器的压力; 所以如果高并发的系统,需要减少会话保持时间;具体也要根据业务特性即服务指标进行判断;

6.结论

1.关于对工作线程的影响:当接收的请求数超过max-connections时,及时工作线程还有闲置线程也不继续处理;

2.当接收的请求数超过工作线程处理时,其他请求根据acceptCount进行排队等待处理;当请求数超出acceptCount,但是还达不到最大连接数,tomcat也继续保持排队;

3.只有并发请求数超过最大工作线程数,并且超过最大连接数,此时排队数超过acceptCount则拒绝访问;

引出之前文章的结论: (1)maxThreads的设置既与应用的特点有关,也与服务器的CPU核心数量有关。通过前面介绍可以知道,maxThreads数量应该远大于CPU核心数量;而且CPU核心数越大,maxThreads应该越大;应用中CPU越不密集(IO越密集),maxThreads应该越大,以便能够充分利用CPU。当然,maxThreads的值并不是越大越好,如果maxThreads过大,那么CPU会花费大量的时间用于线程的切换,整体效率会降低。

(2)maxConnections的设置与Tomcat的运行模式有关。如果tomcat使用的是BIO,那么maxConnections的值应该与maxThreads一致;如果tomcat使用的是NIO,maxConnections值应该远大于maxThreads。

(3)通过前面的介绍可以知道,虽然tomcat同时可以处理的连接数目是maxConnections,但服务器中可以同时接收的连接数为maxConnections+acceptCount 。acceptCount的设置,与应用在连接过高情况下希望做出什么反应有关系。如果设置过大,后面进入的请求等待时间会很长;如果设置过小,后面进入的请求立马返回connection refused。

其他

net.core.somaxconn(全连接队列长度,即)与应用程序的backlog参数,取最小值;
tomcat中的acceptCount即代表了backlog;

1)概念介绍 对于一个TCP链接,Server与Client需要通过三次握手来建立网络链接,当三次握手成功之后,我们就可以看到端口状态由LISTEN转为ESTABLISHED,接着这条链路上就可以开始传送数据了

net.core.somaxconn是Linux中的一个内核(kernel)参数,表示socket监听(listen)的backlog上限。 什么是backlog?backlog就是 socket的监听队列,当一个请求(request)尚未被处理或者建立时,它就会进入backlog。 而socket server可以一次性处理backlog中的所有请求,处理后的请求不再位于监听队列中。 当Server处理请求较慢时,导致监听队列被填满后,新来的请求就会被拒绝。

backlog参数主要用于底层方法int listen(int sockfd, int backlog), 在解释backlog参数之前,我们先了解下tcp在内核的请求过程,其实就是tcp的三次握手:

alt

client发送SYN到server,将状态修改为SYN_SEND,如果server收到请求,则将状态修改为SYN_RCVD,并把该请求放到syns queue队列中。 server回复SYN+ACK给client,如果client收到请求,则将状态修改为ESTABLISHED,并发送ACK给server。 server收到ACK,将状态修改为ESTABLISHED,并把该请求从syns queue中放到accept queue。 在linux系统内核中维护了两个队列:syns queue和accept queue

syns queue 用于保存半连接状态的请求,其大小通过/proc/sys/net/ipv4/tcp_max_syn_backlog指定,一般默认值是512,不过这个设置有效的前提是系统的syncookies功能被禁用。互联网常见的TCP SYN FLOOD恶意DOS攻击方式就是建立大量的半连接状态的请求,然后丢弃,导致syns queue不能保存其它正常的请求。

accept queue 用于保存全连接状态的请求,其大小通过/proc/sys/net/core/somaxconn指定,在使用listen函数时,内核会根据传入的backlog参数与系统参数somaxconn,取二者的较小值。

如果accpet queue队列满了,server将发送一个ECONNREFUSED错误信息Connection refused到client。

2)补充 Linux系统中,该参数的值默认是128 如果Linux系统中部署了经常处理新请求(request)的高负载的服务,那么显然这个值是需要增加到更合适的值的