SpringBoot中Web容器配置和调优
SpringBoot的启动容器主要是Tomcat,Jetty,Undertow三种容器类型,具体的配置类为
org.springframework.boot.autoconfigure.web.ServerProperties
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties {/*** Server HTTP port.*/private Integer port;/*** Network address to which the server should bind.*/private InetAddress address;@NestedConfigurationPropertyprivate final ErrorProperties error = new ErrorProperties();/*** Strategy for handling X-Forwarded-* headers.*/private ForwardHeadersStrategy forwardHeadersStrategy;/*** Value to use for the Server response header (if empty, no header is sent).*/private String serverHeader;/*** Maximum size of the HTTP message header.*/private DataSize maxHttpHeaderSize = DataSize.ofKilobytes(8);/*** Type of shutdown that the server will support.*/private Shutdown shutdown = Shutdown.IMMEDIATE;@NestedConfigurationPropertyprivate Ssl ssl;@NestedConfigurationPropertyprivate final Compression compression = new Compression();@NestedConfigurationPropertyprivate final Http2 http2 = new Http2();private final Servlet servlet = new Servlet();private final Tomcat tomcat = new Tomcat();private final Jetty jetty = new Jetty();private final Netty netty = new Netty();private final Undertow undertow = new Undertow();public Integer getPort() {return this.port;}..............
}
Server相关
port
端口 默认值为8080
address
org.apache.coyote.AbstractProtocol
类中的地址(Tomcat)
ErrorProperties
path
如果发生error,将会跳到的页面。实际上是SpringMvc控制。
public void registerErrorPages(ErrorPageRegistry errorPageRegistry) {ErrorPage errorPage = new ErrorPage(this.dispatcherServletPath.getRelativePath(this.properties.getError().getPath()));errorPageRegistry.addErrorPages(errorPage);}
includeException
结果是否包含Exception,默认为false,如果设置为true,默认情况下会将Exception返回给前端
private void addErrorDetails(Map<String, Object> errorAttributes, WebRequest webRequest,boolean includeStackTrace) {Throwable error = getError(webRequest);if (error != null) {while (error instanceof ServletException && error.getCause() != null) {error = error.getCause();}errorAttributes.put("exception", error.getClass().getName());if (includeStackTrace) {addStackTrace(errorAttributes, error);}}addErrorMessage(errorAttributes, webRequest, error);}
includeStacktrace
结果是否包含异常栈信息,默认为Never,有三个选项
- never 默认情况
- ALWAYS 总是返回异常栈的信息
- ON_PARAM 请求参数加入 trace且值为 true的时候会打印栈的信息
protected boolean isTraceEnabled(ServerRequest request) {return getBooleanParameter(request, "trace");}
includeMessage
Exception中的异常信息,默认为never,有三个选项
never 默认值
always 总是返回message 如果没有信息 返回
No message available
ON_PARAM 加入请求参数 message且为true的时候返回message
includeBindingErrors
是否返回 error,默认为never,有三个选项
never永远不打印
always 总是
ON_PARAM 加入请求参数为 errors 且为true的时候打印
whitelabel
白标,主要是Undertow的时候有用,当没有配置errorPath的时候,且这个值为true的时候默认一个页面。
protected Mono<ServerResponse> renderErrorView(ServerRequest request) {Map<String, Object> error = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.TEXT_HTML));int errorStatus = getHttpStatus(error);ServerResponse.BodyBuilder responseBody = ServerResponse.status(errorStatus).contentType(TEXT_HTML_UTF8);return Flux.just(getData(errorStatus).toArray(new String[] {})).flatMap((viewName) -> renderErrorView(viewName, responseBody, error)).switchIfEmpty(this.errorProperties.getWhitelabel().isEnabled()? renderDefaultErrorView(responseBody, error) : Mono.error(getError(request))).next();}
ForwardHeadersStrategy
是否透传Forward的请求头,有三个值选择,默认为 空
- NATIVE 用当前容器支持的透传
- FRAMEWORK Spring 框架支持的透传
- NONE 完全不支持透传
这个的作用,我们在分布式系统中,当多台机器转发调用的时候,容易找不到每个部分的ip以及访问者的IP,也就是 X-Forwarded-*
相关参数,这个参数方便我们判断,
具体的信息看类org.apache.catalina.valves.RemoteIpValve
serverHeader
返回的头,也就是为每一个服务请求返回设置头,如果设置为 a
,那么返回的响应头就有
server: a
maxHttpHeaderSize
默认为8Kb,最大的请求头大小。
shutdown
关闭状态,值分为两种,默认为IMMEDIATE,如果是linux 就看kill的参数了。
- GRACEFUL 等所有请求完成后才关闭
- IMMEDIATE 立马关闭,
Ssl
ssl相关参数,
Compression
压缩相关配置
enabled
是否开启压缩
mimeTypes
压缩文件类型,
private static Predicate[] getCompressionPredicates(Compression compression) {List<Predicate> predicates = new ArrayList<>();predicates.add(new MaxSizePredicate((int) compression.getMinResponseSize().toBytes()));predicates.add(new CompressibleMimeTypePredicate(compression.getMimeTypes()));if (compression.getExcludedUserAgents() != null) {for (String agent : compression.getExcludedUserAgents()) {RequestHeaderAttribute agentHeader = new RequestHeaderAttribute(new HttpString(HttpHeaders.USER_AGENT));predicates.add(Predicates.not(Predicates.regex(agentHeader, agent)));}}return predicates.toArray(new Predicate[0]);}
excludedUserAgents
请求头中包含的User-Agent,是否需要排除压缩
minResponseSize
这个长度一定要在请求头中声明,不然这个属性将被忽略。
@Override
public boolean resolve(HttpServerExchange value) {if (value.getResponseHeaders().contains(Headers.CONTENT_LENGTH)) {return this.maxContentSize.resolve(value);}return true;
}
Http2
enable
是否为true,如果为true使用HTTP2.默认为false。
Http2总体相比较于1.1来说做了部分的性能优化,
- 引入Hpack (
org.apache.coyote.http2.Hpack
) 来优化 头部- 引入Stream (
org.apache.coyote.http2.Stream
) 来解决队头阻塞问题
具体的实现类为 org.apache.coyote.http2.Http2Protocol
Servlet相关设置
这里就长话短说
contextParameters
上下文参数,
contextPath
项目路径
applicationDisplayName
项目名称
registerDefaultServlet
是否自动注入到容器
encoding
编码
JSP
jsp相关参数,
Session
session相关参数
persistent
是否持久化
Tomcat相关
tomcat工厂类为 org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory
,tomcat为默认的Spring-boot容器。
accesslog
日志参数配置
enabled
是否开启tomcat自己的日志,默认为false 不开启。
propertyMapper.from(tomcatProperties::getAccesslog).when(ServerProperties.Tomcat.Accesslog::isEnabled).to((enabled) -> customizeAccessLog(factory));
conditionIf
if (!getState().isAvailable() || !getEnabled() || logElements == null|| condition != null&& null != request.getRequest().getAttribute(condition)|| conditionIf != null&& null == request.getRequest().getAttribute(conditionIf)) {return;}
如果这个值设置了,且这个值没传。那么不打印日志
conditionUnless
如果这个值设置了,且传了。那么就不打印日志
pattern
日志格式。
directory
日志文件夹
prefix
日志文件夹前缀
…
Threads
这个参数调节Tomcat的最大工作线程和最小工作线程,
在tomcat中,线程池的创建方式如下
max参数是线程池的
maximumPoolSize
的值,默认为200minSpare 是线程池的
corePoolSize
的值,默认为10tomcat中线程池的队列是
org.apache.tomcat.util.threads.TaskQueue
,他是一个阻塞队列。他的策略如下- 如果当前线程池满,装入队列(等待线程执行)
- 如果当前线程池还没达到最小的参数,直接装入队列(直接用现有线程执行)
- 如果当前提交数小于线程的最大值,返回false(新建线程执行)
- 其他情况直接装入队列
线程拒绝策略为默认(tomcat的线程池没配置最大队列)
public void setMinSpareThreads(int minSpareThreads) {this.minSpareThreads = minSpareThreads;Executor executor = this.executor;if (internalExecutor && executor instanceof java.util.concurrent.ThreadPoolExecutor) {// The internal executor should always be an instance of// j.u.c.ThreadPoolExecutor but it may be null if the endpoint is// not running.// This check also avoids various threading issues.((java.util.concurrent.ThreadPoolExecutor) executor).setCorePoolSize(minSpareThreads);}}public void setMaxThreads(int maxThreads) {this.maxThreads = maxThreads;Executor executor = this.executor;if (internalExecutor && executor instanceof java.util.concurrent.ThreadPoolExecutor) {// The internal executor should always be an instance of// j.u.c.ThreadPoolExecutor but it may be null if the endpoint is// not running.// This check also avoids various threading issues.((java.util.concurrent.ThreadPoolExecutor) executor).setMaximumPoolSize(maxThreads);}}
优化
这个参数是Tomcat调优的主要手段,这个值调大了容易造成资源浪费,调小了容易造成造成性能瓶颈。
这个值要根据性能测试以及业务复杂度,消耗资源度等综合评估,并不是简单的根据CPU核数来确定,具体还要看业务在CPU上的执行表现,以及业务是否波动,请求规律等
- 比如一个可预见请求量很少,且与其他共用服务共用服务器的,可以将最小值和最大值都调小
- 其他就根据CPU测试表现,数据库访问型,且访问较大或者独享资源。可以调大这个值。
basedir
tomcat的baseDir,如果没有指定。那么直接创建到临时目录
File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
backgroundProcessorDelay
后台线程的处理间隔,默认值为10S,它最终将设置进Engine中。他会在启动的时候执行
org.apache.catalina.core.ContainerBase#startInternal
// Start our threadif (backgroundProcessorDelay > 0) {monitorFuture = Container.getService(ContainerBase.this).getServer().getUtilityExecutor().scheduleWithFixedDelay(new ContainerBackgroundProcessorMonitor(), 0, 60, TimeUnit.SECONDS);}protected void threadStart() {if (backgroundProcessorDelay > 0&& (getState().isAvailable() || LifecycleState.STARTING_PREP.equals(getState()))&& (backgroundProcessorFuture == null || backgroundProcessorFuture.isDone())) {if (backgroundProcessorFuture != null && backgroundProcessorFuture.isDone()) {// There was an error executing the scheduled task, get it and log ittry {backgroundProcessorFuture.get();} catch (InterruptedException | ExecutionException e) {log.error(sm.getString("containerBase.backgroundProcess.error"), e);}}backgroundProcessorFuture = Container.getService(this).getServer().getUtilityExecutor().scheduleWithFixedDelay(new ContainerBackgroundProcessor(),backgroundProcessorDelay, backgroundProcessorDelay,TimeUnit.SECONDS);}}
在后台线程通过DFS去遍历所有的Container,执行后台线程处理。
大约处理时事情有(初始化的时候):
org.apache.catalina.core.ContainerBase#backgroundProcess
- org.apache.catalina.Cluster#backgroundProcess 集群相关的
- org.apache.catalina.Realm#backgroundProcess 领域相关的
- org.apache.catalina.Valve#backgroundProcess 管道相关的
- 触发时间监听
org.apache.catalina.core.StandardWrapper#backgroundProcess
触发周期类事件的监听,可以尝试做Tomcat的动态加载
org.apache.catalina.core.StandardContext#backgroundProcess
- org.apache.catalina.Loader#backgroundProcess 资源重加载
- org.apache.catalina.Manager#backgroundProcess 数据的持久化
- org.apache.catalina.WebResourceRoot#backgroundProcess 资源更新和回收
- org.apache.catalina.core.DefaultInstanceManager#backgroundProcess 缓存的释放
优化,
这个值如果设置过低,后台线程允许频繁,可能触发一些无效的事情,造成CPU飘高。
如果设置过低,可能会有些数据更新或者缓存回收不及时
这个要根据综合情况,建议观察
org.apache.catalina.core.ContainerBase.ContainerBackgroundProcessor
的执行时间和CPU的消耗程度,可以考虑用Arthas 和top -H -p
命令
maxHttpFormPostSize
最大表单请求,默认值为2MB。如果大于这个值,抛出IllegalStateException
while (len > -1) {len = getStream().read(buffer, 0, CACHED_POST_LEN);if (connector.getMaxPostSize() >= 0 &&(body.getLength() + len) > connector.getMaxPostSize()) {// Too much datacheckSwallowInput();throw new IllegalStateException(sm.getString("coyoteRequest.chunkedPostTooLarge"));}if (len > 0) {body.append(buffer, 0, len);}}
maxSwallowSize
主要作用于长连接,即Http1.1的控制
- 在发生IO异常的时候,会check这个参数,check这个参数是否大于了文件传输过来的大小。
public long end() throws IOException {final boolean maxSwallowSizeExceeded = (maxSwallowSize > -1 && remaining > maxSwallowSize);long swallowed = 0;// Consume extra bytes.while (remaining > 0) {int nread = buffer.doRead(this);tempRead = null;if (nread > 0 ) {swallowed += nread;remaining = remaining - nread;if (maxSwallowSizeExceeded && swallowed > maxSwallowSize) {// Note: We do not fail early so the client has a chance to// read the response before the connection is closed. See:// https://httpd.apache.org/docs/2.0/misc/fin_wait_2.html#appendixthrow new IOException(sm.getString("inputFilter.maxSwallow"));}} else { // errors are handled higher up.remaining = 0;}}// If too many bytes were read, return the amount.return -remaining;}
打开注释的链接,可以看见
Below is a message from Roy Fielding, one of the authors of HTTP/1.1.
Why the lingering close functionality is necessary with HTTP
The need for a server to linger on a socket after a close is noted a couple times in the HTTP specs, but not explained. This explanation is based on discussions between myself, Henrik Frystyk, Robert S. Thau, Dave Raggett, and John C. Mallery in the hallways of MIT while I was at W3C.
If a server closes the input side of the connection while the client is sending data (or is planning to send data), then the server’s TCP stack will signal an RST (reset) back to the client. Upon receipt of the RST, the client will flush its own incoming TCP buffer back to the un-ACKed packet indicated by the RST packet argument. If the server has sent a message, usually an error response, to the client just before the close, and the client receives the RST packet before its application code has read the error message from its incoming TCP buffer and before the server has received the ACK sent by the client upon receipt of that buffer, then the RST will flush the error message before the client application has a chance to see it. The result is that the client is left thinking that the connection failed for no apparent reason.
There are two conditions under which this is likely to occur:
- sending POST or PUT data without proper authorization
- sending multiple requests before each response (pipelining) and one of the middle requests resulting in an error or other break-the-connection result.
The solution in all cases is to send the response, close only the write half of the connection (what shutdown is supposed to do), and continue reading on the socket until it is either closed by the client (signifying it has finally read the response) or a timeout occurs. That is what the kernel is supposed to do if SO_LINGER is set. Unfortunately, SO_LINGER has no effect on some systems; on some other systems, it does not have its own timeout and thus the TCP memory segments just pile-up until the next reboot (planned or not).
Please note that simply removing the linger code will not solve the problem – it only moves it to a different and much harder one to detect.
大致的意思是
通常在长连接中,如果服务端在客户端发送或者计划发送数据的时候关闭输入端,服务器要发送RST给客户端,客户端收到RST后,客户端刷新TCP,根据RST包参数返回un-ACK包。一般服务器这个时候会发送一个错误的message,如果在关闭前发送了一个message。RST将会刷掉错误message,从而导致链接失败而没错误信息。触发有两个条件
- 请求为PUT 和 POST的时候(这个时候数据量大,容易导致message包延后)
- 在多个请求的时候,一个导致错误,其他导致关闭连接。
解决方案是,不写了,读还是要读的。把message读出来。如果设置了SO_LINGER(继续发送)。
猜想maxSwallowSize的目的是,如果数据量太大,那么也只读这么多数据。然后抛出异常
- 这个参数也会影响keepAlive 的打开和关闭。
private void checkMaxSwallowSize() {// Parse content-length headerlong contentLength = -1;try {contentLength = request.getContentLengthLong();} catch (Exception e) {// Ignore, an error here is already processed in prepareRequest// but is done again since the content length is still -1}if (contentLength > 0 &&(contentLength - request.getBytesRead() > protocol.getMaxSwallowSize())) {// There is more data to swallow than Tomcat will accept so the// connection is going to be closed. Disable keep-alive which will// trigger adding the "Connection: close" header if not already// present.keepAlive = false;}}
这里限制了长连接的长度,如果长连接在我们这里的长度太长,会将长连接置为关闭状态。
redirectContextRoot
如果没Path 是否从定向到 ‘/’ 默认为true
if(mappingData.wrapper == null && noServletPath &&contextVersion.object.getMapperContextRootRedirectEnabled()) {// The path is empty, redirect to "/"path.append('/');pathEnd = path.getEnd();mappingData.redirectPath.setChars(path.getBuffer(), pathOffset, pathEnd - pathOffset);path.setEnd(pathEnd - 1);return;}
useRelativeRedirects
重定向是否用相对路径,默认为true
if (getRequest().getCoyoteRequest().getSupportsRelativeRedirects() &&getContext().getUseRelativeRedirects()) {locationUri = location;} else {locationUri = toAbsolute(location);}
uriEncoding
连接编码,默认为UTF-8
maxConnections
最大连接数,默认为8192
限制连接的实现类为 org.apache.tomcat.util.threads.LimitLatch
实现原理为AQS,主要方法为
countUpOrAwait
如果满了就进入阻塞,如果没满就+1countDown
当前连接减一
acceptCount
backlog的数量,最大Pending连接的数量。也就是连接用的数量.默认为100
serverSock.bind(addr, getAcceptCount());
processorCache
最大processorCache的数量,默认为200,理论上来说最大的Cache是maxConnections。他也和MaxThread相关。
相关的类是org.apache.coyote.AbstractProtocol.RecycledProcessors
。
@Override
public boolean push(Processor processor) {int cacheSize = handler.getProtocol().getProcessorCache();boolean offer = cacheSize == -1 ? true : size.get() < cacheSize;//avoid over growing our cache or add after we have stoppedboolean result = false;if (offer) {result = super.push(processor);if (result) {size.incrementAndGet();}}if (!result) handler.unregister(processor);return result;
}
这个值控制着org.apache.tomcat.util.collections.SynchronizedStack
同步栈的大小。
- 如果关闭链接的时候,就回收Processor.链接大于这个值的时候就会丢弃
- 如果新增链接的时候,先从这里取链接,如果取完了,那就直接创建
当请求频繁的时候,这个值可以设置为最大线程数,如果请求只有一小部分时间巅峰。可以调小这个值,如果最大线程数大于这个值,不调整这个值也不会收到影响。
additionalTldSkipPatterns
限制tomcat加载jar,通常对Class做了特殊处理的时候会用到,比如(加密等)
代码在 org.apache.tomcat.util.scan.StandardJarScanner#scan
relaxedPathChars
允许的特殊字符,在路径中包含特殊字符的时候。请求会失败。调整这个值可以兼容
org.apache.tomcat.util.http.parser.HttpParser
类中会存IS_NOT_REQUEST_TARGET,IS_ABSOLUTEPATH_RELAXED,IS_QUERY_RELAXED 三个128字符的数组,对应128个码,
特殊字符解析的时候会报错,如果relaxedPathChars排除了这个,就不会报错
relaxedQueryChars
同上
connectionTimeout
链接超时时间,
// Set back socket to blocking modeSocket.timeoutSet(data.socket, getConnectionTimeout() * 1000);
resource
静态资源配置
主要是缓存的时间
Mbeanregistry
Mbean的配置
Remoteip
和ForwardHeadersStrategy 相关,都是请求头参数
SpringBoot中Web容器配置和调优相关推荐
- Springboot内置Tomcat配置参数调优
Springboot内置Tomcat配置参数调优,首先,线程数是一个重点,每一次HTTP请求到达Web服务器,Web服务器都会创建一个线程来处理该请求,该参数决定了应用服务同时可以处理多少个HTTP请 ...
- Docker中应用的性能调优指南(一)- 先谈谈容器化性能调优
摘要: 前言 性能调优是一个老生常谈的话题,通常情况下,一个应用在上线之前会进行容量规划.压力测试并进行验证,而性能调优则是在容量规划与验证结果之间出现差异时会进行的必然手段.从某种角度来讲,性能调优 ...
- Servlet容器中web.xml配置context-param与init-param
Servlet容器中web.xml配置<context-param>与<init-param>的区别与作用 <context-param>的作用: web.xml的 ...
- Nginx安装/负载均衡/反向代理配置与调优
[Nginx安装] Linux下直接使用包管理安装 sudo apt-get install nginx 使用whereis命令查看安装位置 whereis nginx #sbin下代表nginx可执 ...
- php+php-fom+nginx配置参数调优详解
文章目录 一.前言 1.mysql配置参数: 2.注意 二.php参数配置及讲解 1.phpini的基本设置 2.php参数设置 三.php-fpm设置 1.设置子进程数,增加并发量 2.防止频繁出现 ...
- 在RedHat Enterprise Linux 上Oracle 9i的安装配置与调优
1 安装配置Oracle 9i数据库 本章描述内容如下所示: 1.1 安装前的准备工作 介绍在安装Oracle之前所需的准备工作. 1.2安装前的系统设置 介绍在安装Oracle之前所必须的系统设置. ...
- springboot 中 mybatis configuration 配置失效问题
springboot 中 mybatis configuration 配置失效问题 环境 场景 springboot角度分析 SqlSessionFactory 设置Configuration Myb ...
- springboot中druid数据源配置无效的问题和jar包找不到问题
springboot中druid数据源配置无效的问题 阿里云的仓库 链接: 阿里云仓库. 自己在springboot项目中,引入druid的依赖,希望引入druid数据源. 但是idea中,虽然在这个 ...
- SpringBoot中Logback常用配置以及自定义输出到MySql数据库
之前基于SpringBoot开发的项目运行一段时间后,客户使用网站偶尔会出现接口调用失败的情况,每次产品经理询问是怎么回事的时候,都需要让运维提下最近的日志才能分析具体原因,这样时效性和便利性不能满足 ...
最新文章
- signature=680da11b802226668317d65ae7c38eb7,encryption with designated verifiers
- 区块链以太坊五大开发工具,你喜欢哪个?
- linux(6/17)--文件打包上传和下载
- nbns协议_网络协议详解1 - NBNS
- 节能原理 (能量平衡)
- HandlerThread
- Windows平台下kafka环境的搭建
- 记录一个JS异常Uncaught NotFoundError
- 10行代码让你轻松搞定对象检测
- iPhone 12 mini大幅砍单 苹果可能又要因为屏幕而向三星支付违约金
- Ajax datatype:'JSON'的error问题Status1:200,JSON格式
- -创建日期和时间数组--提取年月日-显示格式
- TCP之keepalive
- IntelliJ IDEA创建和配置Maven项目并运行
- 四级英语测试软件,英语四级必备软件推荐
- 【Tomcat】修改密码
- linux sd卡修复工具,免费的SD卡数据恢复工具介绍
- Ffmpeg 视频教程 向视频中添加文字
- win10照片文件夹里面图片,突然不显示缩略图
- 实现Excel里每个sheet的排序并整合在一个sheet里
热门文章
- Laravel4.1数据库 数据库填充(六)
- OpenCV自适应直方图均衡CLAHE图像和分块大小不能整除的处理
- ChipGAN: A Generative Adversarial Network for Chinese Ink Wash Painting Style Transfer翻译
- oozie action shell 实战(完整配置文件)
- Mybatis—动态SQL语句与逆向工程
- c语言vsprintf函数,vsprintf函数
- 一文带你入门机器学习中的树模型(附源码)
- 网络优化之net.ipv4.tcp_tw_recycle参数
- Linux命令行与shell脚本编程大全(shell脚本编程基础部分)
- 208核、6TB内存!阿里云发布全球最强云服务器:挑战摩尔定律极限