nginx 健康檢查和負載均衡機制分析

nginx 是優秀的反向代理服務器,這裏主要講它的健康檢查和負載均衡機制,以及這種機制帶來的問題。所謂健康檢查,就是當後端出現問題(具體什麼叫出現問題,依賴於具體實現,各個實現定義不一樣),不再往這個後端分發請求,並且做後續的檢查,直到這個後端恢復正常。所謂負載均衡,就是選擇後端的方式,如何(根據後端的能力)將請求均衡的分發到後端。此外,當請求某個後端失敗時,要將該請求分發到其它後端(redispatch)。這裏以ngx_http_upstream_round_robin(簡稱RR)做爲負載均衡模塊,以ngx_http_proxy_module(檢查proxy)作爲後端代理模塊。


nginx 的健康檢查和負載均衡是密切相關的,它沒有獨立的健康檢查模塊,而是使用業務請求作爲健康檢查,這省去了獨立健康檢查線程,這是好處。壞處是,當業務複雜時,可能出現誤判,例如後端響應超時,這是可能是後端宕機,也可能是某個業務請求自身出現問題,跟後端無關。如果後端宕機,nginx還要在將它標記爲不可用之後,仍不時的將業務請求分發給它,以檢查後端是否恢復。


nginx 完成客戶端請求Header部分的解析,upstream 調用RR模塊的peer.get 選擇具體的後端。當請求結束,upstream 調用RR模塊的peer.free,向RR反饋後端的健康情況。當upstream和後端通信時,出現錯誤會調用 ngx_http_upstream_next,

void ngx_http_upstream_next(ngx_http_request_t *r, ngx_http_upstream_t *u, ngx_uint_t ft_type); 第三個參數指明錯誤類型,共有如下錯誤類型


#define NGX_HTTP_UPSTREAM_FT_ERROR           0x00000002

#define NGX_HTTP_UPSTREAM_FT_TIMEOUT         0x00000004

#define NGX_HTTP_UPSTREAM_FT_INVALID_HEADER  0x00000008

#define NGX_HTTP_UPSTREAM_FT_HTTP_500        0x00000010

#define NGX_HTTP_UPSTREAM_FT_HTTP_502        0x00000020

#define NGX_HTTP_UPSTREAM_FT_HTTP_503        0x00000040

#define NGX_HTTP_UPSTREAM_FT_HTTP_504        0x00000080

#define NGX_HTTP_UPSTREAM_FT_HTTP_404        0x00000100

#define NGX_HTTP_UPSTREAM_FT_UPDATING        0x00000200

#define NGX_HTTP_UPSTREAM_FT_BUSY_LOCK       0x00000400

#define NGX_HTTP_UPSTREAM_FT_MAX_WAITING     0x00000800

#define NGX_HTTP_UPSTREAM_FT_NOLIVE          0x40000000


ngx_http_upstream_next,只要錯誤類型不是 NGX_HTTP_UPSTREAM_FT_HTTP_404,都認爲後端有問題(NGX_PEER_FAILED)

if (ft_type == NGX_HTTP_UPSTREAM_FT_HTTP_404) {                                                              

   state = NGX_PEER_NEXT;                                                                                  

} else {                                                                                                    

   state = NGX_PEER_FAILED;                                                                                

}

ngx_http_upstream_next 調用RR的peer.free,RR根據state判斷剛纔接受請求的後端是否健康。

if (ft_type != NGX_HTTP_UPSTREAM_FT_NOLIVE) {

   u->peer.free(&u->peer, u->peer.data, state);

}


ngx_http_upstream_next 如果超過最大重試次數(默認爲後端的個數,每試過一個,就減1),或者proxy設置不允許redispatch,則向客戶端返回響應status。

if (u->peer.tries == 0 || !(u->conf->next_upstream & ft_type)) {

   ngx_http_upstream_finalize_request(r, u, status);

}

proxy 模塊的 proxy_next_upstream 配置,在何種情況下將請求redispatch到下一個後端。


剛剛談到,只要錯誤類型不是 NGX_HTTP_UPSTREAM_FT_HTTP_404,都認爲後端有問題。這裏的錯誤類型包括,連接後端失敗,連接,讀寫後端超時,後端返回了500,502,504等。這個策略是有待商榷的,尤其是讀寫後端超時也判斷爲後端不可用。因爲某個業務請求,可能因爲自身的原因而導致讀寫超時。注意,在proxy_next_upstream 中指定timeout,http_504 是不同的,前者表示upstream連接,讀寫後端超時,後者表示後端返回的http code 是504。


實際上健康檢查不是必須的,因爲redispatch的存在保證了,就算有後端宕機,客戶端仍將收到正確的響應。那麼我們考慮關掉健康檢查。通過upstream 的server配置的max_fails 參數


RR 的peer.get,如果max_fails 爲0,則該後端總是可用的(就算它真有問題)。

if (peer->max_fails == 0

   || peer->fails < peer->max_fails)

{  

   break;

}


因爲redispatch的次數,取決於後端的個數,所以後端的個數稍微多一點是有好處的。


下面是一些佐證分析的測試。


upstream test {

   server 127.0.0.1:8060 max_fails=0;

   server 127.0.0.1:8070 max_fails=0;

   server 127.0.0.1:8080 max_fails=0;

   server 127.0.0.1:8090 max_fails=0;

}

只有8060,8070是存活的,8080,8090處於不可用狀態,這裏max_fails=0,關閉了健康檢查。


proxy_read_timeout 2;

讀超時設爲2S。


proxy_next_upstream error timeout;

默認當 error 和 timeout發生時,redispatch。


測試請求的sleep參數指定後端的sleep時間,code參數指定後端返回的http code。根據time和sleep時間的對比,判斷重試了幾個後端。


time curl "http://127.0.0.1:8099/index.php?sleep=3" -vv

real    0m4.014s

sleep=3,讀超時,重試了2個後端。


修改配置 proxy_next_upstream error;


time curl "http://127.0.0.1:8099/index.php?sleep=3" -vv

real    0m2.018s

讀超時,不再redispatch,重試了1個後端。


修改配置 proxy_next_upstream error http_504;


time curl "http://127.0.0.1:8099/index.php?sleep=1" -vv

real    0m1.022s

這個是正常請求。


time curl "http://127.0.0.1:8099/index.php?sleep=1&code=504" -vv

real    0m2.023s

讓後端返回504,此時nginx會做redispatch,重試了2個後端

但是nginx返回給客戶端的是502,不是504,因爲所有的後端都返回504,nginx認爲後端不可用,返回502.


測試健康檢查,關掉redispatch。proxy_next_upstream off;


curl "http://127.0.0.1:8099/index.php?sleep=3" -vv

返回了兩次502,兩次504。存活的後端返回504,有問題的返回502。


修改 max_fails server 127.0.0.1:8060 max_fails=1; 對8060開啓健康檢查。


curl "http://127.0.0.1:8099/index.php?sleep=3" -vv

第一輪4次請求,返回兩次502,兩次504

8080和8090有問題,返回502,8060和8070響應超時,返回504,因爲8060開啓了健康檢查,並且返回了504,所以被標記爲不可用。

第二輪4次請求,返回三次502,一次504。8070沒有開啓健康檢查,所以仍然返回504。


根據測試分析,業務請求(sleep 3s,或者 輸出 http 504)可以讓nginx誤以爲後端宕了,而這時後端活得好好的。在私有云平臺,這個通常不是問題,把超時設大點,不返回5XX錯誤,可以避免這個問題。但是在公有云平臺,這是致命的,因爲業務可以編程輸出5XX錯誤。有兩種方法應對,一種是關閉健康檢查,一種是修改nginx的代碼,僅對 NGX_HTTP_UPSTREAM_FT_ERROR 判定爲後端有問題。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章