SpringBoot开发最大的好处是简化配置,内置了Tomcat, 在SpringBoot2.0.x版本中内置Tomcat版本是8.5.x,SpringBoot内置Tomcat的默认设置中,Tomcat的等待队列长度默认是100,Tomcat的最小工作线程数默认分配10,Tomcat的最大线程数是200,最大连接数是10000,至于最大并发量和最大连接数,常常理解成最大并发量就是最大连接数,实际上是有些牵强的,最大连接数并不一定就是最大并发量。
SpringBoot内置Tomcat的包重要配置和类在
package org.springframework.boot.autoconfigure.web;
内置的tomcat
找到ServerProperties中的public static class Tomcat对象
package org.springframework.boot.autoconfigure.web;
…………
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties {
//内容
}
在public static class Tomcat中有很多配置
/**
* Maximum number of worker threads.
*/
private int maxThreads = 200;
/**
* Minimum number of worker threads.
*/
private int minSpareThreads = 10;
/**
* Maximum size, in bytes, of the HTTP post content.
*/
private int maxHttpPostSize = 2097152;
/**
* Maximum size, in bytes, of the HTTP message header.
*/
private int maxHttpHeaderSize = 0;
/**
* Whether requests to the context root should be redirected by appending a / to
* the path.
*/
private Boolean redirectContextRoot = true;
/**
* Whether HTTP 1.1 and later location headers generated by a call to sendRedirect
* will use relative or absolute redirects.
*/
private Boolean useRelativeRedirects;
/**
* Character encoding to use to decode the URI.
*/
private Charset uriEncoding = StandardCharsets.UTF_8;
/**
* Maximum number of connections that the server a***epts and processes at any
* given time. Once the limit has been reached, the operating system may still
* a***ept connections based on the "a***eptCount" property.
*/
private int maxConnections = 10000;
/**
* Maximum queue length for in***ing connection requests when all possible request
* processing threads are in use.
*/
private int a***eptCount = 100;
默认最大连接数maxConnections = 10000
默认队列长度a***eptCount = 100
默认最大工作线程数maxThreads = 200
默认最小工作线程数 minSpareThreads = 10
也就是说配置如下
server.tomcat.a***ept-count = 100
server.tomcat.max-connections = 10000
server.tomcat.max-threads = 200
server.tomcat.min-spare-threads=10
在这里有个点儿
Maximum number of connections that the server a***epts and processes at any given time. Once the limit has been reached, the operating system may still a***ept connections based on the "a***eptCount" property
服务器在任何给定时间接受和处理的最大连接数。一旦达到限制,操作系统仍然可以根据“a***eptCount”属性接受连接
也就是说当服务器已经达到最大连接数后,操作系统任然可以根据队列长度来接收连接
有时候我们通常会认为在默认配置下,最大并发量就是最大连接数,超过最大连接数10000后会出现tomcat拒绝连接的情况,触发的请求任务超过默认值200(最大线程数)+默认值100(等待队列长度)后,tomcat会拒绝处理请求任务
最大并发量,每个人都它的理解是不一样的
如果在乎tomcat运行能够同时处理的任务数量,那最大并发量可能理解成最大工作线程数(max-threads)---不包含队列里的数量(a***eptCount)
如果在乎tomcat运行能够接纳的最大最多的任务数量,那最大并发量可以理解成最大连接数(max-connections)+队列长度的数量(a***ept-count) --- 包含队列里的数量(a***eptCount)
通常对SpringBoot内置Tomcat调优主要是针对最大连接数(maxConnections = 10000),队列长度(a***eptCount = 100),最大工作线程数(maxThreads = 200)和最小工作线程数 (minSpareThreads = 10)按需设置,一般根据服务器的性能(CPU)以及该程序可能面临的业务峰值(IO数据库等操作)进行参考调优。
测试一下SpringBoot内置Tomcat的最小工作线程和最大工作线程以及最大连接数和队列长度
我用的版本是SpringBoot2.0.5
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
简单编写一个http请求的Controller
package boot.example.web.tomcat.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.annotation.Resource;
/**
* 蚂蚁舞
*/
@Controller
@RequestMapping(value="/demo")
public class BootTomcatController {
@Resource
private BootTomcatService bootTomcatService;
@RequestMapping(value="/test/{count}")
@ResponseBody
public String test(@PathVariable(name = "count", required = true) int count) throws InterruptedException {
bootTomcatService.testTomcatThread(count);
return "hello world " + count;
}
}
处理业务的Service
package boot.example.web.tomcat.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
/**
* 蚂蚁舞
*/
@Service
public class BootTomcatService {
private final Logger log = LoggerFactory.getLogger(this.getClass());
void testTomcatThread(int count) throws InterruptedException {
log.info(count+"");
//Thread.sleep(40*1000);
}
}
配置Tomcat的参数
server.port=8080
server.tomcat.a***ept-count = 2
server.tomcat.max-connections = 12
server.tomcat.max-threads = 6
server.tomcat.min-spare-threads=3
在这里,我把a***ept-count配置成2 把max-connections配置成12,把max-threads配置成6,min-spare-threads配置成3
新建一个测试线程
package boot.example.web.tomcat;
import ***.hutool.http.HttpRequest;
/**
* 蚂蚁舞
*/
public class ThreadTest extends Thread {
private final int count;
public ThreadTest(int count){
this.count = count;
}
public void run() {
try {
// 设置超时时间很大
String result = HttpRequest.get("http://127.0.0.1:8080/demo/test/"+count).timeout(10000*1000).execute().body();;
System.out.println(result);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
这里用到hutool的http请求
<dependency>
<groupId>***.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.5.6</version>
</dependency>
启动的main方法
package boot.example.web.tomcat;
public class TestTomcat {
public static void main(String[] args) {
for (int i = 1; i < 30; i++) {
ThreadTest test = new ThreadTest(i);
test.start();
try {
Thread.sleep(200);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(i);
}
}
}
启动SpringBoot Tomcat 然后启动测试的main方法 30次请求访问
控制台日志
可以看到他的工作线程是3个,完全是符合server.tomcat.min-spare-threads=3的配置,而且线程数的使用没有达到最大线程数
假如我们把它的工作线程给临时阻塞着,故意让他达到最大线程数,40s时间
void testTomcatThread(int count) throws InterruptedException {
log.info(count+"");
Thread.sleep(40*1000);
}
如此的话运行程序 请求30次
可以看到控制台首先进入了6个请求,阻塞着处理任务,那么配置server.tomcat.max-threads = 6是生效的,等40s之后6个任务线程处理完了业务,接下来又是6个任务线程处理,再等40后还处理了2个任务线程,请求的次数30次 实际处理任务数加起来只有14次
请求端日志(有报错)
报错信息如下
Exception in thread "Thread-14" java.lang.RuntimeException: ***.hutool.core.io.IORuntimeException: ConnectException: Connection refused: connect
at boot.example.web.tomcat.ThreadTest.run(ThreadTest.java:24)
Caused by: ***.hutool.core.io.IORuntimeException: ConnectException: Connection refused: connect
at ***.hutool.http.HttpRequest.send(HttpRequest.java:1164)
at ***.hutool.http.HttpRequest.execute(HttpRequest.java:969)
at ***.hutool.http.HttpRequest.execute(HttpRequest.java:940)
at boot.example.web.tomcat.ThreadTest.run(ThreadTest.java:21)
Caused by: java.***Caused by: java.***.ConnectException: Connection refused: connect
at java.***at java.***.DualStackPlainSocketImpl.waitForConnect(Native Method)
at java.***at java.***.DualStackPlainSocketImpl.socketConnect(DualStackPlainSocketImpl.java:85)
at java.***at java.***.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350)
at java.***at java.***.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206)
at java.***at java.***.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188)
at java.***at java.***.PlainSocketImpl.connect(PlainSocketImpl.java:172)
at java.***at java.***.SocksSocketImpl.connect(SocksSocketImpl.java:392)
at java.***at java.***.Socket.connect(Socket.java:589)
at sun.***at sun.***.***workClient.doConnect(***workClient.java:175)
at sun.***at sun.***.www.http.HttpClient.openServer(HttpClient.java:463)
at sun.***at sun.***.www.http.HttpClient.openServer(HttpClient.java:558)
at sun.***at sun.***.www.http.HttpClient.<init>(HttpClient.java:242)
at sun.***at sun.***.www.http.HttpClient.New(HttpClient.java:339)
at sun.***at sun.***.www.http.HttpClient.New(HttpClient.java:357)
at sun.***at sun.***.www.protocol.http.HttpURLConnection.getNewHttpClient(HttpURLConnection.java:1220)
at sun.***at sun.***.www.protocol.http.HttpURLConnection.plainConnect0(HttpURLConnection.java:1156)
at sun.***at sun.***.www.protocol.http.HttpURLConnection.plainConnect(HttpURLConnection.java:1050)
at sun.***at sun.***.www.protocol.http.HttpURLConnection.connect(HttpURLConnection.java:984)
at ***.hutool.http.HttpConnection.connect(HttpConnection.java:377)
at ***.hutool.http.HttpRequest.send(HttpRequest.java:1159)
... 3 more
14:18:57:25
Exception in thread "Thread-15" java.lang.RuntimeException: ***.hutool.core.io.IORuntimeException: ConnectException: Connection refused: connect
at boot.example.web.tomcat.ThreadTest.run(ThreadTest.java:24)
Caused by: ***.hutool.core.io.IORuntimeException: ConnectException: Connection refused: connect
at ***.hutool.http.HttpRequest.send(HttpRequest.java:1164)
at ***.hutool.http.HttpRequest.execute(HttpRequest.java:969)
at ***.hutool.http.HttpRequest.execute(HttpRequest.java:940)
at boot.example.web.tomcat.ThreadTest.run(ThreadTest.java:21)
Caused by: java.***Caused by: java.***.ConnectException: Connection refused: connect
at java.***at java.***.DualStackPlainSocketImpl.waitForConnect(Native Method)
at java.***at java.***.DualStackPlainSocketImpl.socketConnect(DualStackPlainSocketImpl.java:85)
at java.***at java.***.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350)
at java.***at java.***.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206)
at java.***at java.***.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188)
at java.***at java.***.PlainSocketImpl.connect(PlainSocketImpl.java:172)
at java.***at java.***.SocksSocketImpl.connect(SocksSocketImpl.java:392)
at java.***at java.***.Socket.connect(Socket.java:589)
at sun.***at sun.***.***workClient.doConnect(***workClient.java:175)
at sun.***at sun.***.www.http.HttpClient.openServer(HttpClient.java:463)
at sun.***at sun.***.www.http.HttpClient.openServer(HttpClient.java:558)
at sun.***at sun.***.www.http.HttpClient.<init>(HttpClient.java:242)
at sun.***at sun.***.www.http.HttpClient.New(HttpClient.java:339)
at sun.***at sun.***.www.http.HttpClient.New(HttpClient.java:357)
at sun.***at sun.***.www.protocol.http.HttpURLConnection.getNewHttpClient(HttpURLConnection.java:1220)
at sun.***at sun.***.www.protocol.http.HttpURLConnection.plainConnect0(HttpURLConnection.java:1156)
at sun.***at sun.***.www.protocol.http.HttpURLConnection.plainConnect(HttpURLConnection.java:1050)
at sun.***.www.protocol.http.HttpURLConnection.connect(HttpURLConnection.java:984)
at ***.hutool.http.HttpConnection.connect(HttpConnection.java:377)
at ***.hutool.http.HttpRequest.send(HttpRequest.java:1159)
... 3 more
关键点在于
Caused by: ***.hutool.core.io.IORuntimeException: ConnectException: Connection refused: connect
也就是说30次请求,总共处理了14次请求,剩下的16次请求被tomcat给拒绝了----达到tomcat的最大能接纳的请求数后拒绝多余的请求连接。
30次请求进入tomcat后,16条被拒接,14条请求被处理,处理先是最大线程数max-threads=6,在处理的时候,有6条请求加上正在处理的6条满足最大连接数max-connections=12,在这里,还有2条,是a***ept-count=2配置的2条,这2条请求任务恰恰是最容易忽略的,如此最终处理了14条请求,也就是说tomcat拒绝请求的条件是大于了最大连接数+队列长度的数量。
这里的测试是在设置很大超时时间下进行的
timeout(10000*1000)
tomcat参数调优配置(和硬件本身的支撑有很大关系)
server.tomcat.min-spare-threads=10
默认最小的线程数10就可以,这个参数没有多大必要去配置,默认的已经很好了,建议配置范围10-50之间
server.tomcat.max-threads = 200
处理任务的最大线程数默认200,一般应用都是支持的,如果要优化看应用(写的程序)复不复杂,需不需要依托计算机的算力,也就是会不会大量消耗cpu,如果大量消耗cpu,那么这个max-threads不能设置过大,如果仅仅只是普通的入库查询操作,增删改查,max-threads可以设置大一些,但是也不能过大,过大会导致请求的响应变慢 ,建议设置在200-1200之间,大概是min-space-threads的20倍
server.tomcat.max-connections = 10000
最大连接线程数,这个值默认10000已经够大了,有时候真正的业务还没有达到这个值都已经要多服务部署了,因此该参数没有增大的必要,但是可以改小,改到max-thread的20倍左右
server.tomcat.a***ept-count = 100
至于队列中的默认100这个值,也满足需求了,非要改建议大于min-spare-threads小于max-threads之间的某个倍数值就可以,这个参数不能设置太小。
一般的请求都是有超时机制的,一个http请求,可能几十秒后都还没有得到数据,那就会自动超时,自动超时并不代表被tomcat拒绝,可能是tomcat还没有开始处理到它
这里记录一下SpringBoot内置Tomcat配置多端口启动
@Bean
public TomcatServletWebServerFactory getFactory() {
TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory();
Connector[] connectors = this.connectors();
if (connectors != null && connectors.length > 0) {
tomcat.addAdditionalTomcatConnectors(connectors);
}
return tomcat;
}
private Connector[] connectors() {
String ports = "8081,8082,8083,8084";
if (StringUtils.isEmpty(ports)) {
return null;
}
String[] port = ports.split(",");
List<Connector> connectors = new ArrayList<>();
for (String s : port) {
Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
connector.setPort(Integer.parseInt(s));
connector.setScheme("http");
//connector.setRedirectPort(8041);
connectors.add(connector);
}
return connectors.toArray(new Connector[]{});
}
15:16:08.692 spring-boot-logging [restartedMain] INFO o.s.b.d.a.OptionalLiveReloadServer - LiveReload server is running on port 35729
15:16:08.734 spring-boot-logging [restartedMain] INFO o.s.j.e.a.AnnotationMBeanExporter - Registering beans for JMX exposure on startup
15:16:08.752 spring-boot-logging [restartedMain] INFO o.a.coyote.http11.Http11NioProtocol - Starting ProtocolHandler ["http-nio-8080"]
15:16:08.754 spring-boot-logging [restartedMain] INFO o.a.tomcat.util.***.NioSelectorPool - Using a shared selector for servlet write/read
15:16:08.767 spring-boot-logging [restartedMain] INFO o.a.coyote.http11.Http11NioProtocol - Starting ProtocolHandler ["http-nio-8081"]
15:16:08.773 spring-boot-logging [restartedMain] INFO o.a.coyote.http11.Http11NioProtocol - Starting ProtocolHandler ["http-nio-8082"]
15:16:08.777 spring-boot-logging [restartedMain] INFO o.a.coyote.http11.Http11NioProtocol - Starting ProtocolHandler ["http-nio-8083"]
15:16:08.782 spring-boot-logging [restartedMain] INFO o.a.coyote.http11.Http11NioProtocol - Starting ProtocolHandler ["http-nio-8084"]
15:16:08.797 spring-boot-logging [restartedMain] INFO o.s.b.w.e.tomcat.TomcatWebServer - Tomcat started on port(s): 8080 (http) 8081 (http) 8082 (http) 8083 (http) 8084 (http) with context path ''
15:16:08.802 spring-boot-logging [restartedMain] INFO boot.example.web.tomcat.AppTomcat - Started AppTomcat in 5.725 seconds (JVM running for 6.629)
Hello World!
这一条
Tomcat started on port(s): 8080 (http) 8081 (http) 8082 (http) 8083 (http) 8084 (http) with context path ''