一、生产案发现场以及排查过程
3月23日晚上22:46:06,front服务挂掉发生告警,查看系统日志:
可以看到堆栈溢出。
马上猜想到跟下午新增的一个配置有关:
server.maxHttpHeaderSize: 10240000
(赶紧回滚镜像,恢复生产。。。)
猜想归猜想,还是得拿出点证据。
服务对应的jvm参数:
java -Xms1024m -Xmx1024m -XX:MetaspaceSize=512m -XX:MaxMetaspaceSize=512m -XX:SoftRefLRUPolicyMSPerMB=5000 -XX:+PrintGCDetails -Xloggc:/data/logs/saas-front/log/gc/gc.log -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/data/logs/saas-front/log/oom -javaagent:/usr/local/skywalking/skywalking-agent/skywalking-agent.jar -Dskywalking.agent.service_name=saas-front-aws_us_west_2 -jar saas-front.jar --spring.profiles.active=aws_us_west_2
于是可以去/data/logs/saas-front/log/oom目录拿到OOM后的快照,使用Eclipse Memory Analyzer工具进行分析。
很快啊,查看Leak Suspects就可以发现两个溢出点:
Problem Suspect 1:
42 instances of "org.apache.coyote.http11.Http11OutputBuffer",
loaded by "sun.misc.Launcher$AppClassLoader @ 0xc0015438"
occupy 430,106,544 (44.09%) bytes.
Biggest instances:
org.apache.coyote.http11.Http11OutputBuffer @ 0xc5860360 - 10,240,632 (1.05%) bytes.
org.apache.coyote.http11.Http11OutputBuffer @ 0xc586b6b0 - 10,240,632 (1.05%) bytes.
org.apache.coyote.http11.Http11OutputBuffer @ 0xc7b733a0 - 10,240,632 (1.05%) bytes.
org.apache.coyote.http11.Http11OutputBuffer @ 0xca055528 - 10,240,632 (1.05%) bytes.
org.apache.coyote.http11.Http11OutputBuffer @ 0xccc2d290 - 10,240,632 (1.05%) bytes.
org.apache.coyote.http11.Http11OutputBuffer @ 0xcd5f51f0 - 10,240,632 (1.05%) bytes.
......
存在42个Http11OutputBuffer实例,每个占用了10M,一共占用了420M(44.09%)的堆空间。
可以看出是tomcat的线程占用的空间。
Problem Suspect 2:
945 instances of "byte[]", loaded by "<system class loader>"
occupy 420,340,888 (43.09%) bytes.
Biggest instances:
byte[10248192] @ 0xc4bc2c10 GET / HTTP/1.1..host:honedora.comm..x-real-ip:43.245.160.455..x-forwarded-for:43.245.160.455..user-agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.366..accept-language:en-US,en;q=0.55..... - 10,248,208 (1.05%) bytes.
byte[10248192] @ 0xc70776d8 GET / HTTP/1.1..host:thenicegoods.comm..x-real-ip:43.245.160.455..x-forwarded-for:43.245.160.455..user-agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.366..accept-language:en-US,en;q=0.... - 10,248,208 (1.05%) bytes.
byte[10248192] @ 0xc7b7bc00 GET /prometheus HTTP/1.1..host:172.30.2.144:180222..user-agent:Prometheus/2.17.11..accept:application/openmetrics-text; version=0.0.1,text/plain;version=0.0.4;q=0.5,*/*;q=0.11..accept-encoding:gzipp..x-prometheus-scrape-timeout-seconds:10.0000000............. - 10,248,208 (1.05%) bytes.
byte[10248192] @ 0xc943e7e8 GET //website/wp-includes/wlwmanifest.xml HTTP/1.1..host:cattyy.comm..x-real-ip:43.245.160.455..x-forwarded-for:43.245.160.455..cookie:client_id=5718685622856744966..user-agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko... - 10,248,208 (1.05%) bytes.
......
存在945个byte[]实例,其中40多个最大的实例一共占用了400M空间。
这里还是tomcat线程占用的空间。
基本上这里可以确定tomcat配置的最大请求头信息大小有问题。
二、当初为什么设置这个参数-maxHttpHeaderSize
前面发现访问某个商品链接页面报500:
查看服务的报错日志:
10:29:08.901 user [http-nio-8022-exec-9] ERROR o.a.coyote.http11.Http11Processor - Error processing request
org.apache.coyote.http11.HeadersTooLargeException: An attempt was made to write more data to the response headers than there was room available in the buffer. Increase maxHttpHeaderSize on the connector or write less data into the response headers.
at org.apache.coyote.http11.Http11OutputBuffer.checkLengthBeforeWrite(Http11OutputBuffer.java:464)
at org.apache.coyote.http11.Http11OutputBuffer.write(Http11OutputBuffer.java:417)
at org.apache.coyote.http11.Http11OutputBuffer.write(Http11OutputBuffer.java:403)
at org.apache.coyote.http11.Http11OutputBuffer.sendHeader(Http11OutputBuffer.java:363)
at org.apache.coyote.http11.Http11Processor.prepareResponse(Http11Processor.java:975)
at org.apache.coyote.AbstractProcessor.action(AbstractProcessor.java:369)
at org.apache.coyote.Response.action(Response.java:211)
at org.apache.coyote.Response.sendHeaders(Response.java:437)
at org.apache.catalina.connector.OutputBuffer.doFlush(OutputBuffer.java:281)
at org.apache.catalina.connector.OutputBuffer.close(OutputBuffer.java:241)
at org.apache.catalina.connector.Response.finishResponse(Response.java:440)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:374)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:408)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:853)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1587)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:748)
报错提示请求头缓存区空间不够用,说是可以加大maxHttpHeaderSize这个值或者减少写入数据。
我当时的解决方法:
1、根据网上所说,springboot可以直接配置server.maxHttpHeaderSize = 10M
2、后面排查到了写入响应头过大的URL,通过修改代码逻辑来减少不必要的写入
有一个接口www.baidu.com/products/shoe,接口执行流程是
①查询商品
②拦截器拦截设置店铺通用model
③根据某些逻辑判断链接是否需要重定向
- true 重定向
- false 返回跳转到商品详情页
重定向的结果是,model会在链接后面拼接了大量参数
www.baidu.com/products/new-shoe?cardConfig=***&name=***&customCodeList=***&deviceType=***省略...
并且会往响应头里面设置Location = 上面这条重定向的url,用于告诉浏览器重定向跳转。(这里是报错原因)
而返回到商品详情页,model是会用于页面显示或者页面js逻辑判断。
于是我在拦截器里面做了判断,如果属于重定向,那么不设置model。
然后①②都修改完上线,没过多久,就出现了前面的OOM。。。
三、来谈谈maxHttpHeaderSize
maxHttpHeaderSize参数是属于tomcat服务器的参数,默认是8k,springboot内置tomcat默认配置8k。
在tomcat的server.xml 里面也可以设置maxHttpHeaderSize。
<Connector port="8099" protocol="HTTP/1.1" maxHttpHeaderSize="8192" connectionTimeout="20000" redirectPort="8443" />
Tomcat对servlet规范中的response和request做了实现,其中会有一个Buffer用于缓存httpHeader信息。
如下图,coyoteResponse的Header Buffer会控制着httpheader的存储。
这个Header Buffer是不可以扩充的,tomcat启动的时候会设置它的固定大小(默认8192字节),无论是请求头的输出还是输入都不可以超出这个大小。
而这个固定大小正是maxHttpHeaderSize来配置的。
我们可以通过一个例子验证一下,
springboot配置
server.max-http-header-size: 666666666
写一个测试接口,Debug查看:
可以看到,coyoteResponse底下确实有一个headerBuffer对象,里面有一个hb的属性,是byte数组,大小为666666666
四、总结
前面堆栈信息显示的对象org.apache.coyote.http11.Http11OutputBuffer @ 0xc5860360,也是对应
这里的Header Buffer,每一个tomcat用户线程都会申请一块Header Buffer空间。
当网站并发线程比较多的时候,内存空间占用比较大,从而导致新的线程没办法申请内存空间,最终出现OOM。
maxHttpHeaderSize这个参数不能设置过大,根据项目情况设置足够用即可,还要充分考虑设置请求头的大小和数量,是否有优化的空间。