1. 程式人生 > >nginx和tomcat之間連線複用要注意的問題

nginx和tomcat之間連線複用要注意的問題

一個典型的web請求處理架構是:前段nginx作為反向代理,後端使用tomcat處理具體業務。

如果沒有連線複用,每個請求nginx都會和tomcat新建連線和關閉連線,這明顯不是一個好的做法。所以通常我們在nginx和tomcat上配置keepalive引數。

設定這個引數後,nginx接收到tomcat的響應後一般情況不會關閉底層的socket連線,servlet在傳送完響應資料後呼叫resp.getWriter.close()關閉輸出流。到此tomcat和nginx由於設定了keepalive引數,都沒有關閉socket連線,下一個請求就可以複用這個連線繼續傳送資料給tomcat。

連線複用減少建立連線和關閉連線所帶來的網路開銷,但同時也會引入一個問題:上一個請求可能還有一些資料殘留在socket中,下一個請求繼續使用此socket,此時tomcat解析請求行就會出錯返回400錯誤碼給nginx。

一個實際的場景:我最近在做一個上傳加速模組,前端使用nginx做反向代理,後端使用tomcat伺服器做業務處理。

1. nginx先向tomcat傳送一個token過期的上傳請求

2. tomcat接收請求並交給servlet處理,servlet發現token已過期立即返回403錯誤碼給nginx(注意:tomcat沒有讀取body中的資料。問題就出在這裡

3. nginx向tomcat傳送另一個正常的上傳請求,立即得到了400錯誤碼。

問題就來了:為什麼前一次的錯誤請求導致後一次的正常請求也失敗了呢?

分析日誌發現一個奇怪的現象:nginx訪問日誌和tomcat的訪問日誌都有相關日誌,但是servlet的日誌沒有並且tomcat的訪問日誌出現一堆無意義的字元

。當把nginx的keepalive關掉後,問題立刻消失。

初步猜測是連線複用的問題,通過相關同學抓包分析發現tomcat返回的403錯誤碼後nginx繼續會把沒有傳送完的body資料傳送給tomcat,但是此時tomcat認為我已經處理完此socket連線的上一個請求了,只要有新的資料到來我就認為是一個新的請求了,此時tomcat回去嘗試解析請求行,但是此時拿到的卻是上一個請求的body二進位制資料,tomcat解析請求行出錯返回400給nginx,且列印了一條全是亂碼的訪問日誌。

至此,我們找到了問題所以。socket複用後,tomcat在返回響應前如果沒有清理本次請求的剩餘垃圾資料,就會導致如上問題。最後我們的解決方案就是根據請求的content-length欄位,把body中剩餘資料讀取出來丟掉。

另外還要注意:如果第一次請求tomcat返回400或500錯誤碼,上述問題卻不見了!那麼又來一個問題:為什麼403會有問題,而400或500卻沒問題呢?

事實上nginx會根據根據不同的錯誤碼對socket連線進行不同處理。

403 nginx不會關閉連線

下面的錯誤碼nginx會主動關閉連線,不管有沒有設定keepalive引數

400 Bad Request
413 Request Entity Too Large
414 Request URI Too Large
497 HTTP to HTTPS
495 HTTPS Cert Error
496 HTTPS No Cert
500 Internal Server Error
501 Not Implemented