springboot?max-http-header-size最大長度的那些事及JVM調(diào)優(yōu)方式
問題
線上程序出現(xiàn)了OOM,程序日志中的輸出為
Exception in thread "http-nio-8080-exec-1027" java.lang.OutOfMemoryError: Java heap space
Exception in thread "http-nio-8080-exec-1031" java.lang.OutOfMemoryError: Java heap space
看線程名稱應(yīng)該是tomcat的nio工作線程,線程在處理程序的時(shí)候因?yàn)闊o法在堆中分配更多內(nèi)存出現(xiàn)了OOM,幸好JVM啟動(dòng)參數(shù)配置了-XX:+HeapDumpOnOutOfMemoryError,使用MAT打開拿到的hprof文件進(jìn)行分析。
第一步就是打開Histogram看看占用內(nèi)存最大的是什么對象:

可以看到byte數(shù)組占用了接近JVM配置的最大堆的大小也就是8GB,顯然這是OOM的原因。
第二步看一下究竟是哪些byte數(shù)組,數(shù)組是啥內(nèi)容:

可以看到很明顯這和HTTP請求相關(guān),一個(gè)數(shù)組大概是10M的大小。
第三步通過查看GC根查看誰持有了數(shù)組的引用:

這符合之前的猜測,是tomcat的線程在處理過程中分配了10M的buffer在堆上。
至此,馬上可以想到一定是什么參數(shù)設(shè)置的不合理導(dǎo)致了這種情況,一般而言tomcat不可能為每一個(gè)請求分配如此大的buffer。
第四步就是檢查代碼里是否有tomcat或服務(wù)器相關(guān)配置,看到有這么一個(gè)配置:
max-http-header-size: 10000000
至此,基本已經(jīng)確定了八九不離十就是這個(gè)不合理的最大http請求頭參數(shù)導(dǎo)致的問題。
關(guān)于http header最大長度的那些事
http協(xié)議,超文本傳輸協(xié)議,HyperText Transfer Protocol,是互聯(lián)網(wǎng)上應(yīng)用最為廣泛的一種網(wǎng)絡(luò)協(xié)議,所有的WWW文件都遵守這個(gè)標(biāo)準(zhǔn)。
關(guān)于http協(xié)議消息的格式,大家可以網(wǎng)上自行搜索,這里不再贅述。
本文關(guān)注的是其header部分,如下圖所示(紅框標(biāo)注部分):

問題原型
有一個(gè)web application提供web service,這個(gè)web application基于java開發(fā),部署在tomcat容器上。
問題是:當(dāng)客戶端發(fā)送一個(gè)GET請求,結(jié)果得到400的response,意思是說bad request。
檢查了這個(gè)request的代碼實(shí)現(xiàn)邏輯,并沒有相關(guān)input validation的邏輯,并且檢查server端日志發(fā)現(xiàn),request請求似乎并沒有到達(dá)我們自己代碼實(shí)現(xiàn)邏輯部分。這是為什么呢?
問題解釋
遇到這個(gè)問題時(shí),第一步就是查看server端日志,但是覺得很tricky的是,最開始并沒有發(fā)現(xiàn)相關(guān)的日志,只是發(fā)現(xiàn)request并沒有到達(dá)我們自己代碼實(shí)現(xiàn)邏輯部分。
后來,mina同學(xué)眼神很好,發(fā)現(xiàn)了如下日志:

通過日志note信息發(fā)現(xiàn),該條日志在info級(jí)別下只會(huì)打印一次,之后都會(huì)是debug級(jí)別才打印,難怪之前沒有注意到這條日志。
從日志信息可知,request的header部分太大,超過了tomcat允許的最大值。
默認(rèn)情況下,tomcat(8.0版本)允許的http請求header的最大值是8024個(gè)字節(jié)(8KB)。
那為什么之前沒有出現(xiàn)這個(gè)問題呢?
原因是,項(xiàng)目遷移到SCP平臺(tái)上之后,改成JWT token做權(quán)限校驗(yàn),這個(gè)JWT token會(huì)被添加到request的header,然而JWT token一般來說都很大(平均有6k個(gè)字節(jié)左右),所以說在增加了JWT token這個(gè)header以及其他一些相關(guān)的headers之后,整個(gè)request的header部分就超過8024個(gè)字節(jié),于是就出現(xiàn)了這個(gè)問題。
那么如何解決這個(gè)問題呢?可以從兩個(gè)方面考慮:
增加tomcat允許http header最大值。這個(gè)配置參數(shù)maxHttpHeaderSize可以設(shè)置tomcat允許的http header最大值。
減少header的size,比如不要添加無關(guān)的header到request。
擴(kuò)展
在研究這個(gè)問題的過程中,其實(shí)還有一些其他疑問。首先,一個(gè)request的轉(zhuǎn)發(fā)流程大致如下:

那么,在這個(gè)流程中,為什么request在前面的部分沒有出現(xiàn)這個(gè)問題,而這個(gè)問題出現(xiàn)在最后一個(gè)技術(shù)棧是java/tomcat的component呢?
原因是,每個(gè)web服務(wù)器的http header最大長度的默認(rèn)值不一樣,同時(shí)隨語言、版本不同也會(huì)不一樣。舉個(gè)例子tomcat 5的http header size的默認(rèn)值是4K。
我找到了其他component中對于http header size的默認(rèn)值的定義:
CF Router是用Go語言實(shí)現(xiàn),Go語言的http處理模塊對于它的定義是默認(rèn)值1MB。
App Router是用Nodejs實(shí)現(xiàn),Nodejs的http處理模塊對它的定義是默認(rèn)值80KB。
以上兩個(gè)默認(rèn)值都要遠(yuǎn)遠(yuǎn)大于8KB,這也就解釋了沒什么問題出在最后一個(gè)component。
Tomcat修改maxParameterCount配置
問題
java.lang.IllegalStateException: More than the maximum number of request
parameters (GET plus POST) for a single request ([10,000]) were detected.
Any parameters beyond this limit have been ignored.
To change this limit, set the maxParameterCount attribute
on the Connector.
解決方案
以前使用外部Tomcat部署項(xiàng)目的時(shí)候,可以通過修改server.xml文件中的Connector節(jié)點(diǎn)maxParameterCount屬性值解決這個(gè)問題。
<Connector port=“8080” redirectPort=“8443” protocol=“HTTP/1.1” maxParameterCount="-1" />
因?yàn)镾pringBoot使用的是內(nèi)嵌的Tomcat,無法配置server.xml。經(jīng)過查看相關(guān)API文檔并沒有發(fā)現(xiàn)可以直接在配置文件中配置maxParameterCount屬性,那么我們就在代碼中進(jìn)行配置,在SpringBoot的API文檔中講解了通過實(shí)現(xiàn)WebServerFactoryCustomizer接口可以對Tomcat進(jìn)行相關(guān)配置。
參考

自定義tomcat配置
創(chuàng)建一個(gè)類并實(shí)現(xiàn)WebServerFactoryCustomizer接口的customize方法。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.stereotype.Component;
/**
* 自定義Tomcat容器配置類
*
*/
@Component
public class MyTomcatWebServerFactoryCustomizer
implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
public static final int DEFAULT_MAX_PARAMETER_COUNT = 10000;
private Logger logger = LoggerFactory.getLogger(getClass());
/**
* 單次請求參數(shù)最大限制數(shù)
*/
@Value("${server.tomcat.maxParameterCount}")
private int maxParameterCount = DEFAULT_MAX_PARAMETER_COUNT;
@Override
public void customize(TomcatServletWebServerFactory factory) {
if (logger.isDebugEnabled()) {
logger.debug("MyTomcatWebServerFactoryCustomizer customize");
}
PropertyMapper propertyMapper = PropertyMapper.get();
propertyMapper.from(this::getMaxParameterCount)
.when((maxParameterCount) -> maxParameterCount != DEFAULT_MAX_PARAMETER_COUNT)
.to((maxParameterCount) -> customizerMaxParameterCount(factory, maxParameterCount));
}
/**
* 配置內(nèi)置Tomcat單次請求參數(shù)限制
*
* @param factory
* @param maxParameterCount
*/
private void customizerMaxParameterCount(TomcatServletWebServerFactory factory,
int maxParameterCount) {
factory.addConnectorCustomizers(
connector -> connector.setMaxParameterCount(maxParameterCount));
}
public void setMaxParameterCount(int maxParameterCount) {
this.maxParameterCount = maxParameterCount;
}
public int getMaxParameterCount() {
return maxParameterCount;
}
}
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
關(guān)于spring.factories失效原因分析及解決
這篇文章主要介紹了關(guān)于spring.factories失效原因分析及解決,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-07-07
SpringBoot動(dòng)態(tài)Feign服務(wù)調(diào)用詳解
Feign是Netflix公司開發(fā)的一個(gè)聲明式的REST調(diào)用客戶端; Ribbon負(fù)載均衡、 Hystrⅸ服務(wù)熔斷是我們Spring Cloud中進(jìn)行微服務(wù)開發(fā)非?;A(chǔ)的組件,在使用的過程中我們也發(fā)現(xiàn)它們一般都是同時(shí)出現(xiàn)的,而且配置也都非常相似2022-12-12
Spring5新特性之Reactive響應(yīng)式編程
這篇文章主要介紹了Spring5新特性之Reactive響應(yīng)式編程,響應(yīng)式編程是一種編程范式,通用和專注于數(shù)據(jù)流和變化的,并且是異步的,下文更多詳細(xì)內(nèi)容,需要的小伙伴可以參考一下,希望對你有所幫助2022-03-03
springboot開啟mybatis二級(jí)緩存的步驟詳解
這篇文章給大家介紹了springboot開啟mybatis二級(jí)緩存的詳細(xì)步驟,文中通過代碼示例給大家講解的非常詳細(xì),對大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下2024-02-02
Java中實(shí)現(xiàn)文件上傳下載的三種解決方案(推薦)
這篇文章主要介紹了Java中實(shí)現(xiàn)文件上傳下載的三種解決方案的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-07-07

