動態

詳情 返回 返回

server.max-http-header-size與OOM不得不説的故事 - 動態 詳情

今天的故事是從nacos的升級開始的,出於性能、服務治理等原因我司想把從dubbo2.7.x升級到3.2.x,但在這之前有個前提,那就是nacos首先要升級到2.x,於是乎就開始了我多災多難的nacos升級之旅。

一開始我以為這會是個很簡單的事情,畢竟很多人已經從1.x升級到2.x了,但當運維把測試環境的nacos升級到2.x後沒多會涌現了一堆告警,全都是找不到dubbo的服務提供者,我這邊立馬登到nacos容器中查看,整個nacos的堆已經到了32G,且在一直不停地fullgc過了一會更是健康檢查失敗被k8s重啓了,確認了是堆滿了以後這邊也是立馬dump了一份堆快照到持久卷。

然後就是一頓分析
在這裏插入圖片描述
在這裏插入圖片描述看的出來是直接原因是有大量5M的byte數組,然後分析引用發現是tomcat的nio請求對象的消息頭,難不成是因為請求的消息頭非常大?

這邊也是分析出來大部分的請求都是/v1/cs/configs/listener

於是我在client和server端都查看了這個請求的消息頭,發現消息頭的內容很少,但消息頭的大小確佔用了5M,byte數組後面充斥着大量的0。這是怎麼回事呢,忽然我看到nacos的jvm中有這麼一項參數

--server.max-http-header-size=5242880

5242880 / 1024 / 1024 = 5M

這大大的可疑啊。

Max-HTTP-Header-Size in Spring Boot 2 | Baeldung

這個配置項的含義是Spring Boot 2中的最大HTTP標頭大小,這裏有個很坑的地方就是雖然叫最大,但是這個最大是指所有http請求中可能最大的header的大小,當http的請求的header大於此大小時該請求將會報HTTP Status 400 – Bad Request,而在請求創建的時候就會申請此大小的byte數組用於存儲消息頭(但不限於消息頭這也是一個常見的誤區後面會講到)。

但一個請求5M這聽着很奇怪啊,這個參數是不是我們運維瞎幾把加的,我到了nacos的github上看了一眼好嘛參數確實是有的但人家是

--server.max-http-header-size=524288

大概是512k,好像合理了很多。問了下運維運維説應該是他手抖多加了個0,我尼瑪,這個事情折磨了我好幾周,竟然是因為你小子的手抖。

但我對max-http-header-size這個參數就產生了好奇,nacos為什麼要加這個參數,在issue找到了作者的解釋

https://github.com/alibaba/nacos/issues/9575

但作者的説法比較模稜兩可,所以我又從git log中找到了這次提交是因為/v1/ns/instance/beat

這個心跳接口出現了請求失敗

https://github.com/alibaba/nacos/issues/1069

開發者直接添加了max-http-header-size參數作為一個臨時方案
在這裏插入圖片描述

git log説的蠻清楚的作為臨時方案解決問題,只是沒想到這個臨時竟然都臨到了5年後。。。

事情到此我本以為告一段落,但翻看源碼時,我忽然發現beat的請求參數是掛在http的query中的,並不是放在消息頭裏,那為什麼url參數過長需要調整max-http-header-size這個參數呢?這玩意不是控制消息頭嗎,本着實事求是的原則我在自己的測試應用測試了一下,當url超過8192時報錯內容如下

org.apache.coyote.http11.Http11Processor: Error parsing HTTP request header
 Note: further occurrences of HTTP request parsing errors will be logged at DEBUG level. java.lang.IllegalArgumentException: Request header is too large
    at org.apache.coyote.http11.Http11InputBuffer.fill(Http11InputBuffer.java:781) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
    at org.apache.coyote.http11.Http11InputBuffer.parseRequestLine(Http11InputBuffer.java:451) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:261) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1590) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) ~[?:1.8.0_301]
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) ~[?:1.8.0_301]
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
    at java.lang.Thread.run(Thread.java:748) ~[?:1.8.0_301]

好傢伙,url超長怎麼報了個header過大,然後我調大max-http-header-size竟然又可以正常請求了,我開始凌亂了,不得不又埋頭扎進了tomcat的源碼。

最後通過閲讀org.apache.coyote.http11.Http11InputBuffer#fill方法我發現雖然URL和請求頭部在 HTTP 請求中有着不同的用途,但它們實際上都屬於HTTP請求的一部分,都包含在HTTP報文中。在處理 HTTP 請求時,服務器需要將整個請求報文(包括請求行、請求頭部、請求體等)讀入內存中進行解析和處理。因此,服務器需要為整個請求報文分配一塊緩衝區,並設置一個大小限制以防止惡意或錯誤的請求導致服務器資源耗盡。因此,即使URL和請求頭部在概念上是獨立的,但它們都被視為請求報文的一部分,都會佔用服務器的緩衝區。因此,在許多Web服務器中,URL和請求頭部的大小都受到相同的配置項的限制

最後的最後為nacos提了一個優化建議https://github.com/alibaba/nacos/issues/11810,如果能優化掉server.max-http-header-size這個參數想必對內存的使用會有明顯的降低。

原文地址:https://pebble-skateboard-d46.notion.site/server-max-http-hea...

user avatar u_17513518 頭像 debuginn 頭像 lenglingx 頭像 hankin_liu 頭像 jkdataapi 頭像 shenchendexiaoyanyao 頭像 dreamlu 頭像 mecode 頭像 segmenhcfucsd 頭像 nathannie 頭像 jinjiedefarmer 頭像 tobin_blogs 頭像
點贊 20 用戶, 點贊了這篇動態!
點贊

Add a new 評論

Some HTML is okay.