invalid signature 错误原因验签失败_Nginx 失败重试机制
可直接點擊上方藍字
(網易游戲運維平臺)
關注我們,獲一手游戲運維方案
src
網易游戲 SRE,喜歡鉆研與分享。背景
Nginx 作為目前應用較廣的反向代理服務,原生提供了一套失敗重試機制,來保證服務的可用性。本文主要是通過一些簡單例子來剖析 Nginx 失敗重試機制,讓讀者能對該機制有一個基礎的了解,避免在使用過程中踩坑。
本文中的結論在以下環境進行驗證:
版本詳情:nginx/1.16.0
安裝方式:使用 apt 從 nginx 官方源安裝
如何定義 fails
在了解 Nginx 的失敗重試機制之前,需要先了解 Nginx 如何定義失敗。
Nginx 通過 proxy_next_upstream 參數來定義什么情況下會被認為是 fails,從而觸發失敗重試機制。
fails 可以分成兩類:
默認錯誤,包括 error、timeout
選擇定義錯誤,包含 invalid_header 以及各種異常 http 狀態碼錯誤等
默認錯誤
關于默認錯誤,我們再詳細解析一下這兩種錯誤。關于這兩種錯誤的定義,官網文檔已經描述的非常清楚了:
error: an error occurred while establishing a connection with the server, passing a request to it, or reading the response headertimeout: a timeout has occurred while establishing a connection with the server, passing a request to it, or reading the response header
出現 error 的場景,常見的是上游服務器的服務重啟、停止,或者異常崩潰導致的無法提供正常服務。而 timeout 的情況,就是代理請求過程中達到對應的超時配置,主要包括了:
proxy_connect_timeout,建立三次握手的時間
proxy_read_timeout,建立連接后,等待上游服務器響應以及處理請求的時間
proxy_send_timeout,數據回傳的間隔時間(注意不是數據發送耗時)
選擇定義錯誤
關于選擇定義錯誤,異常狀態碼部分(也就是 4xx、5xx 錯誤)應該是比較好理解,這里主要說一下 invalid_header
invalid_header: a server returned an empty or invalid response;
這個場景就是上游服務器返回空響應或者非法響應頭,包括且不僅限于:
上游服務器的業務使用了非標準的 HTTP 協議,nginx 校驗不通過
因服務異常導致響應請求處理返回了異常 header(或者空 header)
NOTE
默認只有 error、timeout 會被認為是 fails,統計到健康檢測的 max_fails 計數,如果通過 proxy_next_upstream 定義了其他類型的 fails,那這部分 fails 也會被算到計數器。
在選擇自定義錯誤的配置上,一定要十分慎重,必須要結合業務實際情況來調整配置,而不是直接復制網上或者其他站點的配置,否則可能踩坑:
配置了不合理的錯誤類型,可能導致一些非預期的所有節點被踢掉的情況
缺少對關鍵錯誤類型的定義,導致出問題的節點一直沒有被踢掉,影響客戶端訪問
重試機制解析
Nginx 的失敗重試,就是為了實現對客戶端透明的服務器高可用。然而這部分失敗重試機制比較復雜且官方文檔沒詳細介紹,本文將對其解析,并配合實際場景例子使之更容易被理解。
基礎失敗重試
這部分介紹最常見、最基礎的失敗重試場景。
為了方便理解,使用了以下配置進行分析(proxy_next_upstream 沒有特殊配置):
upstream?test?{????server?127.0.0.1:8001?fail_timeout=60s?max_fails=2;?#?Server?A
????server?127.0.0.1:8002?fail_timeout=60s?max_fails=2;?#?Server?B
}?
模擬后端異常的方式是直接將對應服務關閉,造成 connect refused 的情況,對應 error 錯誤。
在最初始階段,所有服務器都正常,請求會按照輪詢方式依次轉發給 AB 兩個 Server 處理。假設這時 A 節點服務崩潰,端口不通,則會出現這種情況:
請求 1 轉到 A 異常,再重試到 B 正常處理,A fails +1
請求 2 轉到 B 正常處理
請求 3 轉到 A 異常,再重試到 B 正常處理,A fails +1 達到 max_fails 將被屏蔽 60s
屏蔽 A 的期間請求都只轉給 B 處理,直到屏蔽到期后將 A 恢復重新加入存活列表,再按照這個邏輯執行
如果在 A 的屏蔽期還沒結束時,B 節點的服務也崩潰,端口不通,則會出現:
請求 1 轉到 B 異常,此時所有線上節點異常,會出現:
AB 節點一次性恢復,都重新加入存活列表
請求轉到 A 處理異常,再轉到 B 處理異常
觸發?no live upstreams 報錯,返回 502 錯誤
所有節點再次一次性恢復,加入存活列表
請求 2 依次經過 AB 均無法正常處理, 觸發 no live upstreams 報錯,返回 502 錯誤
重試限制方式
默認配置是沒有做重試機制進行限制的,也就是會盡可能去重試直至失敗。
Nginx 提供了以下兩個參數來控制重試次數以及重試超時時間:
proxy_next_upstream_tries:設置重試次數,默認 0 表示無限制,該參數包含所有請求 upstream server 的次數,包括第一次后之后所有重試之和;
proxy_next_upstream_timeout:設置重試最大超時時間,默認 0 表示不限制,該參數指的是第一次連接時間加上后續重試連接時間,不包含連接上節點之后的處理時間
為了方便理解,使用以下配置進行說明(只列出關鍵配置):
proxy_connect_timeout?3s;proxy_next_upstream_timeout?6s;
proxy_next_upstream_tries?3;
upstream?test?{
????server?127.0.0.1:8001?fail_timeout=60s?max_fails=2;?#?Server?A
????server?127.0.0.1:8002?fail_timeout=60s?max_fails=2;?#?Server?B
????server?127.0.0.1:8003?fail_timeout=60s?max_fails=2;?#?Server?C
}
第 2~3 行表示在 6 秒內允許重試 3 次,只要超過其中任意一個設置,Nginx 會結束重試并返回客戶端響應(可能是錯誤碼)。我們通過 iptables DROP 掉對 8001、8002 端口的請求來模擬 connect timeout 的情況:
iptables?-I?INPUT??-p?tcp?-m?tcp?--dport?8001?-j?DROPiptables?-I?INPUT??-p?tcp?-m?tcp?--dport?8002?-j?DROP
則具體的請求處理情況如下:
請求 1 到達 Nginx,按照以下邏輯處理
先轉到 A 處理,3s 后連接超時,A fails +1
重試到 B 處理,3s 后連接超時,B fails +1
到達設置的 6s 重試超時,直接返回 `504 Gateway Time-out` 到客戶端,不會重試到 C
請求 2 轉到 C 正常處理
請求 3 到達 Nginx
先轉到 B 處理,3s 后連接超時,B 達到 max_fails 將被屏蔽 60s
轉到 C 正常處理
請求 4 達到 Nginx:
先轉到 A 處理,3s 后連接超時,A 達到 max_fails 將被屏蔽 60s
轉到 C 正常處理
后續的請求將全部轉到 C 處理直到 AB 屏蔽到期后重新加入服務器存活列表
從上面的例子,可以看出 proxy_next_upstream_timeout 配置項對重試機制的限制,
重試次數的情況也是類似,這里就不展開細講了。
關于 backup 服務器
Nginx 支持設置備用節點,當所有線上節點都異常時啟用備用節點,同時備用節點也會影響到失敗重試的邏輯,因此單獨列出來介紹。
upstream 的配置中,可以通過 backup 指令來定義備用服務器,其含義如下:
正常情況下,請求不會轉到到 backup 服務器,包括失敗重試的場景
當所有正常節點全部不可用時,backup 服務器生效,開始處理請求
一旦有正常節點恢復,就使用已經恢復的正常節點
backup 服務器生效期間,不會存在所有正常節點一次性恢復的邏輯
如果全部 backup 服務器也異常,則會將所有節點一次性恢復,加入存活列表
如果全部節點(包括 backup)都異常了,則 Nginx 返回 502 錯誤
為了方便理解,使用了以下配置進行說明:
upstream?test?{????server?127.0.0.1:8001?fail_timeout=60s?max_fails=2;?#?Server?A
????server?127.0.0.1:8002?fail_timeout=60s?max_fails=2;?#?Server?B
????server?127.0.0.1:8003?backup;?#?Server?C
}?
在最初始階段,所有服務器都正常,請求會按照輪詢方式依次轉發給 AB 兩個節點處理。當只有 A 異常的情況下,與上文沒有 backup 服務器場景處理方式一致,這里就不重復介紹了。
假設在 A 的屏蔽期還沒結束時,B 節點的服務也崩潰,端口不通,則會出現:
請求 1 轉到 B 處理,異常,此時所有線上節點異常,會出現:
AB 節點一次性恢復,都重新加入存活列表
請求轉到 A 處理異常,再重試到 B 處理異常,兩者 fails 都 +1
因 AB 都異常,啟用 backup 節點正常處理,并且 AB 節點一次性恢復,加入存活列表
請求 2 再依次經過 A、B 節點異常,轉到 backup 處理,兩者 fails 都達到 max_fails:
AB 節點都將會被屏蔽 60s,并且不會一次性恢復
backup 節點正式生效,接下來所有請求直接轉到 backup 處理
直到 AB 節點的屏蔽到期后,重新加入存活列表
假設 AB 的屏蔽期都還沒結束時,C 節點的服務也崩潰,端口不通,則會出現
請求 1 轉到 C 異常,此時所有節點(包括 backup)都異常,會出現:
ABC 三個節點一次性恢復,加入存活列表
請求轉到 A 處理異常,重試到 B 處理異常,最后重試到 C 處理異常
觸發 `no live upstreams` 報錯,返回 502 錯誤
所有節點再次一次性恢復,加入存活列表
請求 2 依次經過 AB 節點異常,重試到 C 異常,最終結果如上個步驟,返回 502 錯誤
踩坑集錦
如果不熟悉 HTTP 協議,以及 Nginx 的重試機制,很可能在使用過程中踩了各種各樣的坑:
部分上游服務器出現異常卻沒有重試
一些訂單創建接口,客戶端只發了一次請求,后臺卻創建了多個訂單,等等…
以下整理了一些常見的坑,以及應對策略。
需要重試卻沒有生效
接口的 POST 請求允許重試,但實際使用中卻沒有出現重試,直接報錯。
從 1.9.13 版本,Nginx 不再會對一個非冪等的請求進行重試。如有需要,必須在 proxy_next_upstream 配置項中顯式指定 non_idempotent 配置。參考 RFC-2616 的定義:
冪等 HTTP 方法:GET、HEAD、PUT、DELETE、OPTIONS、TRACE
非冪等 HTTP 方法:POST、LOCK、PATCH
如需要允許非冪等請求重試,配置參考如下(追加 non_idemponent 參數項):
proxy_next_upstream?error?timeout?non_idemponent;該配置需要注意的點:
添加非冪等請求重試是追加參數值,不要把原來默認的 error/timeout 參數值去掉
必須明確自己的業務允許非冪等請求重試以避免業務異常
禁止重試的場景
一些場景不希望請求在多個上游進行重試,即使上游服務器完全掛掉。
正常情況下,Nginx 會對 error、timeout 的失敗進行重試,對應默認配置如下:
proxy_next_upstream?error?timeout;如希望完全禁止重試,需要顯式指定配置來關閉重試機制,配置如下:
proxy_next_upstream?off;重試導致性能問題
錯誤配置了重試參數導致 Nginx 代理性能出現異常
默認的 error/timeout 是不會出現這種問題的。在定義重試場景時,需要結合業務情況來確定是否啟用自定義錯誤重試,而不是單純去復制其他服務的配置。比如對于某個業務,沒有明確自己業務情況,去網上復制了 Nginx 配置,其中包括了:
proxy_next_upstream?error?timeout?invalid_header?http_500?http_503?http_404;那么只需要隨便定義一個不存在的 URI 去訪問該服務頻繁去請求該服務,就可以重復觸發 Nginx no live upstreams 報錯,這在業務高峰情況下,性能將受到極大影響。同樣,如果使用的代碼框架存在不標準 HTTP 處理響應情況,惡意構造的請求同樣也會造成類似效果。
因此在配置重試機制時,必須先對業務的實際情況進行分析,嚴謹選擇重試場景。
異常的響應超時重試
某冪等接口處理請求耗時較長,出現非預期的重試導致一個請求被多次響應處理。
假設該接口處理請求平均需要 30s,而對應的代理超時為:
proxy_read_timeout?30s;默認的重試包含了 timeout 場景,在這個場景下,可能會有不到一半的請求出現超時情況,同時又因為是冪等請求,所有會進行重試,最終導致一個的超時請求會被發到所有節點處理的請求放大情況。
因此在進行超時設置時,也必須要跟進業務實際情況來調整。可以適當調大超時設置,并收集請求相關耗時情況進行統計分析來確定合理的超時時間。
異常的連接超時重試
因上游服務器異常導致連接問題,客戶端無超時機制,導致請求耗時非常久之后才失敗。
已知所有上游服務器異常,無法連接或需要非常久(超過 10s)才能連接上,假設配置了連接超時為:
proxy_connect_timeout?10;在這種情況下,因客戶端無超時設置,冪等請求將卡住 10*n 秒后超時(n 為上游服務器數量)。
因此建議:
客戶端設置請求超時時間
配置合理的 proxy_connect_timeout
配合 proxy_next_upstream_timeout、proxy_next_upstream_tries 來避免重試導致更長超時
往期精彩
﹀
﹀
﹀
MongoDB 最佳實踐:為什么升級之后負載升高了
Linux網絡數據包的揭秘以及常見的調優方式總結
Python:requests超時機制實現
斷點原理與實現
疑難雜癥篇之 ulimit
總結
以上是生活随笔為你收集整理的invalid signature 错误原因验签失败_Nginx 失败重试机制的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 在同一网段内运行同一命令_一高楼两层同时
- 下一篇: 利用函数wavread对语音信号进行采样