目錄
- 問題解決
- 應用部署環境
- 現象
- 解決
- 過程
- 最終解決
- 問題分析
- 連接重置
- Tomcat 的 Connector
- Nginx 104
- 類似問題解決思路
- 總結
問題解決
應用部署環境
- 語言:java
- 框架:ssm
- web容器:tomcat
- 負載:nginx
- 外層代理:F5
現象
根據客戶需求對接一個停車繳費的功能,發布到生產環境之后發現,少量賬單同時支付沒有問題,一旦同時支付的賬單數量超過某個值,就會出現網路連接問題,穩定復現。
解決
過程
首先查看了應用的日志,發現用戶提示網絡異常的時候,服務端沒有任何相關的日志打印,確定請求沒有發到服務端
查看Nginx Error日志發現打印了錯誤信息
2021/09/09 08:38:56 [error] 16299#16299: *240963 readv() failed (104: Connection reset by peer) while reading upstream, client: ****, server: ****, request: "POST ****?formData=E172Rfbkeuw2Z6fFYyg95hUMDmDwaOZT7Mqopwu07lo%3CVxsdDikPopy1XjjtjmvSusJwb7UF3erixZi5Wy099%3CewyDvM3wWhvE8X/z/vxKow2ttM1iHPSmWn...
通過nginx日志發現,雖然是nginx層拋出了錯誤,但是以日志內容來看,其實nginx已經是將請求的報文完整的接收了下來(這個也是在解決問題之后才反應過來),所以其實問題應該是出在Nginx將請求轉給被代理的應用服務的時候。
當時在排查問題的時候,沒有考慮到還有一層tomcat,導致哪怕是當時懷疑了問題不在nginx這塊,還是不敢相信自己,去網上一頓亂搜。
最終解決
在tomcat/conf/server.xml中,增加Connector中的參數配置maxHttpHeaderSize="65536",增加允許tomcat接收的最大請求頭大小
<Connector port="****" protocol="org.apache.coyote.http11.Http11NioProtocol" URIEncoding="UTF-8" maxHttpHeaderSize="65536" connectionTimeout="20000" acceptCount="500" maxThreads="500" redirectPort="****" />
問題分析
連接重置
TCP RST
正常情況,服務端使用socket建立一個服務端監聽,客戶端通過socket向服務端監聽發起連接, 雙方經過TCP握手協議之后,數據開始傳輸,TCP協議規定連接在建立之后,雙方只要有一端發起關閉的信號,兩端就會走放手協議的流程(四次揮手),不再進行數據傳輸。但是如果一端發起關閉信號之后,不再接收請求,另外一端依然不進入關閉流程,而是依然不停的發送數據,或者是關閉的一端緩存區的數據沒有讀完就進行了關閉,這時候,關閉的一端就會返回一個RST的信號,告訴另外一端連接被重置
其他情況的RST
除了上邊的一種情況,RST還可能出現在客戶端找不到服務端端口,服務端因為各種關閉不接收數據等等場景中,但是無一例外,最終就是一端的數據,沒有被另外一端完整讀取到 ,比如以下幾種情況
- 客戶端直接找不到想要連接的服務端
- 一端早就處于關閉的狀態了,另外一端還在傻乎乎的給他傳輸數據
- 一端關閉的時候,沒有讀完另外一端發過來的數據
Tomcat 的 Connector
其實在一定程度上說,Tomcat和Nginx的作用相同,只不過兩者的職責不同,Nginx使用了異步非阻塞高性能的組合,可以代理各種各樣的URI資源,而Tomcat代理的是一個一個的Servlet容器,它可以容納所有遵循Servlet規范的應用,并且統一將它們管理。Connector是其中最重要的一部分,它是一個HTTP連接器,它通過啟動一個Socket監聽,用來接收不同類型的請求,然后把他們解析成對應的Servlet規范的請求,才會將這些請求分發到不同的Servlet中進行處理。當然,內部做了很多其他的事情包括請求校驗攔截,請求轉化,請求異步線程處理等等。這里只是簡單介紹一下,后續會增加關于tomcat部分的文章
Nginx 104
在我們這個案例的場景下分析,nginx要將拿到的請求轉發給tomcat中的應用,需要跟tomcat的Connector建立連接,可以將nginx理解為客戶端,將tomcat中的Connector理解為socket服務端。tomcat給Connector一套默認的配置,其中maxHttpHeaderSize默認的值是4096字節,也就是4kb。超過4kb的請求頭大小的請求,不進行處理,當然這里也有可能發生兩種情況,第一種是Connector一開始就知道nginx發過來的請求頭過大,直接不接收,響應回去RST標識,還有一種是Connector沒有管請求頭的大小,直接去接收,但是因為沒有將請求頭數據讀取完就關閉了,響應了RST。這部分沒有細看,但是不論怎么說,都是因為上邊說過的,沒有正常處理完客戶端發送過來所有的數據。
類似問題解決思路
在開始無腦查詢的時候,其實有很多答案雖然錯誤碼是104,但是報錯的原因是不相同的,解決方案也是各不相同,看到過大概以下幾種解決思路
- nginx的buffer太小,timeout太小。
- 長連接,增加長連接超時時間
- 將 http version改到1.1 (其實也是使用長連接解決,因為http1.1默認使用長連接)
雖然個人試其他解決方式的時候,都沒有成功,也有可能是因為tomcat Connector 連接器的最大請求頭4K大小的這個默認配置從最基礎的環節直接給把其他配置砍掉了。但是不論使用何種方式解決,最終來說我們就一個思路(雖然說了很像沒說),先找到是哪端沒有將數據讀取完畢,然后想辦法讓它正常讀取
總結
本片文章根據個人發生的實際生產問題,著手解決并且進行問題分析,通過對nginx104的跟蹤,對連接重置的概念有一個更詳細的了解。