我們是如何解決偶發性的502錯誤的

張京發表於2022-04-03

從ingress的監控中心,我們看到,失敗率雖然不高,但始終保持在0.05到0.1的水平:

image.png

我們用這樣的條件去查詢,發現絕大部分錯誤是502錯誤:

status>=500 | select status, count(*) a group by status order by a desc

image.png

那麼502錯誤到底是個什麼錯誤呢?百度百科給出的解釋是:

502 Bad Gateway是指錯誤閘道器,無效閘道器;在網際網路中表示一種網路錯誤。 表現在WEB瀏覽器中給出的頁面反饋。 它通常並不意味著上游伺服器已關閉(無響應閘道器/代理) ,而是上游伺服器和閘道器/代理使用不一致的協議交換資料。 鑑於網際網路協議是相當清楚的,它往往意味著一個或兩個機器已不正確或不完全程式設計。

還有人說是超時導致的:

image.png

馬上在評論區有人反駁:

image.png

百度百科對504錯誤的解釋:

504錯誤代表閘道器超時(Gateway timeout),是指伺服器作為閘道器或代理,但是沒有及時從上游伺服器收到請求。 伺服器(不一定是Web 伺服器)正在作為一個閘道器或代理來完成客戶(如您的瀏覽器或我們的CheckUpDown 機器人)訪問所需網址的請求。

顯然,504錯誤才是超時,而502並不是。

而且從我們對502錯誤日誌的進一步分析來看,發生502錯誤時的請求時間和響應時間都極短,不可能是超時。

image.png

查502與504的區別,只有這個說法相對靠譜:

image.png

也就是說我們後端的服務是能夠響應的,但響應不符合要求,所以出現了502錯誤。但這種錯誤並不是必然的,如果是必然出現,則網站整體不可用,早就被發現了,正因為它是偶發的,所以有必要看一下在發生502的時候到底發生了什麼。

為此我們把nginx的logtail日誌的stderr輸出開啟:

image.png

此前這裡本來是false,現在我們把它改成true,使它能夠將錯誤日誌輸出出來,便於我們查詢原因。

stderr錯誤輸出之後,立刻就能在日誌裡看到大量的這種錯誤:

2022/04/02 16:59:55 [error] 11168#11168: *739601507 recv() failed (104: Connection reset by peer) while reading response header from upstream, client: 49.93.83.68, server: www.domain.com, request: "POST /myserver/service HTTP/1.1", upstream: "http://192.108.1.121:8080/myserver/service", host: "www.domain.com"

從字面意思來理解,是說上游伺服器直接關閉了連線。但是上游伺服器為什麼要關閉連線呢?將錯誤資訊放入搜尋引擎進一步排查,很多文章將我們的思路導向了keepalive這個方向,最應該檢查的是keepalive_timeout和keepalive_request這兩個屬性。

什麼是keepalive?這是http 1.1協議的預設配置,在http 1.0的時候,如果你的網頁上有10個圖片,那麼瀏覽器和伺服器之間要同時建立10個連線,把這10個圖片發過去然後再關閉這10個連線,顯然對於伺服器來說,建立10個連線再關閉10個連線,消耗是比較大的。所以在http 1.1協議裡增加了keepalive的功能,在發10張圖片的時候只需要建立一個連線就夠了,只要還有內容要傳輸,這個通道會始終保持開放狀態,不會在傳輸完畢之後立刻關閉,這就是keepalive保活的意思。

但是keepalive不能把這個連線永遠保持,如果沒有內容了還繼續保持,無疑也是一種浪費,所以這裡就產生了超時的概念,keepalive_timeout的意思是說如果這個連線當中沒有內容傳輸了並且超過了這個時間,那麼就把這個連線斷掉,keepalive_requests的意思是說我們這個連線最多允許傳輸多少個內容,超過這個內容那麼也把它斷掉。

那麼這個keepalive_timout和我們的502錯誤之間有什麼關係呢?因為所有網站的架構都不是瀏覽器直接連線後端的應用伺服器,而一定是中間有nginx伺服器做反向代理的,瀏覽器和nginx伺服器之間建立keepalive連線,nginx再和後端的應用伺服器建立keepalive連線,所以這是兩種不同的keepalive連線。我們把瀏覽器和nginx之間的keepalive連線叫做ka1,把nginx和應用伺服器之間的keepalive連線叫做ka2。

如果ka1的超時設定為100秒,也就是說如果100秒之內沒有新內容要傳輸,就把nginx和瀏覽器之間的連線斷掉。而同時,我們把ka2設定為50秒,也就是說如果nginx和應用伺服器之間沒有新內容要傳輸,那麼就把應用伺服器和nginx之間的連線斷掉。那麼這時候就會產生一個問題:前50秒沒有傳輸內容,在第51秒的時候,瀏覽器向nginx發了一個請求,這時候ka1還沒有斷掉,因為沒有到100秒的時間,所以這是沒有問題的,但是當nginx試圖嚮應用伺服器發請求的時候就出問題了,ka2斷了!因為ka2的超時設定是50秒,這時候已經超了,所以就斷了,這時候nginx無法再從應用伺服器獲得正確響應,只好返回瀏覽器502錯誤!

但是我們根本就沒有設定過這些引數啊,怎麼會有這種問題呢?

這沒關係,既然沒有設定過,那系統肯定用的是預設引數,我們來看一下ka1的預設設定是多少,也就是nginx(ingress)和瀏覽器之間的預設的keepalive_timeout值:

upstream-keepalive-timeout

Sets a timeout during which an idle keepalive connection to an upstream server will stay open. default: 60

ka1的預設設定是60秒。

我們再看一下ka2的預設設定是多少秒,Tomcat官方文件上說:

The number of milliseconds this Connector will wait for another HTTP request before closing the connection. The default value is to use the value that has been set for the connectionTimeout attribute. Use a value of -1 to indicate no (i.e. infinite) timeout.

預設值等同於connectionTimeout的值,那connectionTimeout等於多少呢?

The number of milliseconds this Connector will wait, after accepting a connection, for the request URI line to be presented. Use a value of -1 to indicate no (i.e. infinite) timeout. The default value is 60000 (i.e. 60 seconds) but note that the standard server.xml that ships with Tomcat sets this to 20000 (i.e. 20 seconds). Unless disableUploadTimeout is set to false, this timeout will also be used when reading the request body (if any).

connectionTimeout的預設值是60秒,但是,他們提供的標準的server.xml裡把這個值設為了20秒!

那麼現在問題就很清楚了,我們的ka1是60秒,而ka2是20秒,從21秒到60秒之間的任何時間有請求進來都會發生502錯誤。

找到了問題的根源,解決起來就好辦了,我們只需要確保ka1的超時設定小於ka2的設定就夠了。或者修改ka1,或者修改ka2,都是可以的。

我們先修改ka1看一下,對於ingress來說,要修改ka1需要在ingress的configMap中修改,所以我們找到configMap設定的地方,給它增加一個新的屬性:

image.png

這裡我們把upstream-keepalive-timeout設為4,確保它低於ka2的20,設定完之後,ingress會自動載入新設定,我們看一下結果:

image.png

原先不斷產生的502錯誤徹底消失了!

再來看一下錯誤圖:

image.png

注意那個黃顏色的5XX比例,從我們設定好的那一瞬間,永遠趴在了地上!

相關文章