Nginx負載均衡中後端節點伺服器健康檢查的操作梳理

散盡浮華發表於2017-04-09

 

正常情況下,nginx做反向代理,如果後端節點伺服器宕掉的話,nginx預設是不能把這臺realserver踢出upstream負載叢集的,所以還會有請求轉發到後端的這臺realserver上面,這樣勢必造成網站訪問故障。雖然nginx可以在localtion中啟用proxy_next_upstream來解決返回給使用者的錯誤頁面,如下:

例如公司的網站訪問的時候全部變成404頁面,最後發現是後端的一臺伺服器不可用,直接訪問那臺後臺的伺服器的時候,返回的是404頁面,因為upstream 裡面設定了ip_hash。所以導致訪問網站時怎麼重新整理都是404頁面。這時可以使用nginx的一個功能,就是當後端的伺服器返回給nginx502、504、404、執行超時等錯誤狀態的時候,nginx會自動再把這個請求轉發到upstream裡面別的伺服器上面,從而給網站使用者提供更穩定的服務。
配置如下:
location /
{
#如果後端的伺服器返回502、504、執行超時等錯誤,自動將請求轉發到upstream負載均衡池中的另一臺伺服器,實現故障轉移。
proxy_next_upstream http_502 http_504 http_404 error timeout invalid_header;
}
這樣的話,也算是保障了後端伺服器的一個高可用性。(下面例項配置中會用到)

以上的配置大家可以參考一下,但這個還是會把請求轉發給這臺伺服器的,然後再轉發給別的伺服器,這樣以來就浪費了一次轉發,對於網站效能來說也不是最佳理想的方案。為了避免上面說顧慮的情況,可以對nginx後方realserver的健康狀態進行檢查,如果發現後端伺服器不可用,則請求不轉發到這臺伺服器。
目前主要有三種方式可以實現對nginx負載均衡的後端節點伺服器進行健康檢查:
1)ngx_http_proxy_module模組和ngx_http_upstream_module模組(這是nginx自帶模組)
    參考地址:http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_next_upstream
2)nginx_upstream_check_module模組(淘寶技術團隊開發)
    參考地址:https://github.com/yaoweibin/nginx_upstream_check_module
3)ngx_http_healthcheck_module模組
------------------------------------------------------------------------------------------------------------------------

一、利用nginx自帶模組ngx_http_proxy_module和ngx_http_upstream_module對後端節點做健康檢查
嚴格來說,nginx自帶是沒有針對負載均衡後端節點的健康檢查的,但是可以通過預設自帶的ngx_http_proxy_module模組和ngx_http_upstream_module模組中的相關指令來完成當後端節點出現故障時,自動切換到健康節點來提供訪問。下面列出這兩個模組中相關的指令:

1)ngx_http_proxy_module模組中的 proxy_connect_timeout指令、proxy_read_timeout指令和proxy_next_upstream指令
語法:   proxy_connect_timeout time;
預設值:  proxy_connect_timeout 60s;
上下文:  http, server, location
設定與後端伺服器建立連線的超時時間。應該注意這個超時一般不可能大於75秒。
 
語法: proxy_read_timeout time;
預設值:  proxy_read_timeout 60s;
上下文:  http, server, location
定義從後端伺服器讀取響應的超時。此超時是指相鄰兩次讀操作之間的最長時間間隔,而不是整個響應傳輸完成的最長時間。如果後端伺服器在超時時間段內沒有傳輸任何資料,連線將被關閉。
 
---------------------------------------------------------------------------------------
語法: proxy_next_upstream error | timeout | invalid_header | http_500 | http_502 | http_503 | http_504 |http_404 | off ...;
預設值:  proxy_next_upstream error timeout;
上下文:  http, server, location
指定在何種情況下一個失敗的請求應該被髮送到下一臺後端伺服器:
 
error      和後端伺服器建立連線時,或者向後端伺服器傳送請求時,或者從後端伺服器接收響應頭時,出現錯誤
timeout    和後端伺服器建立連線時,或者向後端伺服器傳送請求時,或者從後端伺服器接收響應頭時,出現超時
invalid_header  後端伺服器返回空響應或者非法響應頭
http_500   後端伺服器返回的響應狀態碼為500
http_502   後端伺服器返回的響應狀態碼為502
http_503   後端伺服器返回的響應狀態碼為503
http_504   後端伺服器返回的響應狀態碼為504
http_404   後端伺服器返回的響應狀態碼為404
off        停止將請求傳送給下一臺後端伺服器
 
需要理解一點的是,只有在沒有向客戶端傳送任何資料以前,將請求轉給下一臺後端伺服器才是可行的。也就是說,如果在傳輸響應到客戶端時出現錯誤或者超時,這類錯誤是不可能恢復的。
 
範例如下(這個在文件開頭已介紹):
 
http {
proxy_next_upstream http_502 http_504 http_404 error timeout invalid_header;
}
---------------------------------------------------------------------------------------
 
2)ngx_http_upstream_module模組中的server指令
語法: server address [parameters];
預設值:  ―
上下文:  upstream
 
範例如下:
   upstream name {
      server 10.1.1.110:8080 max_fails=1 fail_timeout=10s;
      server 10.1.1.122:8080 max_fails=1 fail_timeout=10s;
        }
 
--------------指令引數解釋----------------
max_fails=number   設定Nginx與伺服器通訊的嘗試失敗的次數。在fail_timeout引數定義的時間段內,如果失敗的次數達到此值,Nginx就認為伺服器不可用。在下一個fail_timeout時間段,伺服器不會再被嘗試。
                   失敗的嘗試次數預設是1。設為0就會停止統計嘗試次數,即不對後端節點進行健康檢查。認為伺服器是一直可用的。
 
fail_timeout=time  設定伺服器被認為不可用的時間段以及統計失敗嘗試次數的時間段。在這段時間中,伺服器失敗次數達到指定的嘗試次數,伺服器就被認為不可用。
                   預設情況下,該超時時間是10秒。
 
在實際應用當中:
1)如果後端應用是能夠快速重啟的應用,比如nginx的話,自帶的模組是可以滿足需求的。
   但是需要注意,如果後端有不健康節點,負載均衡器依然會先把該請求轉發給該不健康節點,然後再轉發給別的節點,這樣就會浪費一次轉發。
2)如果當後端應用重啟時,重啟操作需要很久才能完成的時候就會有可能拖死整個負載均衡器。
   此時,由於無法準確判斷節點健康狀態,導致請求handle住,出現假死狀態,最終整個負載均衡器上的所有節點都無法正常響應請求。
 
比如公司的業務程式是java開發的,因此後端主要是nginx叢集和tomcat叢集。由於tomcat重啟應部署上面的業務不同,有些業務啟動初始化時間過長,就會導致上述現象的發生,因此不是很建議使用該模式。
並且ngx_http_upstream_module模組中的server指令中的max_fails引數設定值,也會和ngx_http_proxy_module 模組中的的proxy_next_upstream指令設定起衝突。
如果將max_fails設定為0,則代表不對後端伺服器進行健康檢查,這樣還會使fail_timeout引數失效(即不起作用)。
此時判斷後端伺服器情況的唯一依據便是ngx_http_proxy_module模組中的proxy_connect_timeout指令和proxy_read_timeout指令,通過將它們的值調低來發現不健康節點,進而將請求往健康節點轉移。
如果這兩個引數設定得過小,但後端程式的執行或多或少會超過這個時間的話,這種情況nginx的效率是非常低的。

具體例項配置如下:

[root@master-node ~]# vim /usr/local/nginx/conf/nginx.conf
user  www;
worker_processes  8;
  
#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;
  
#pid        logs/nginx.pid;
  
  
events {
    worker_connections  65535;
}
  
  
http {
    include       mime.types;
    default_type  application/octet-stream;
    charset utf-8;
        
    ######
    ## set access log format
    ######
    log_format  main  '$remote_addr $remote_user [$time_local] "$request" '
                  '$status $body_bytes_sent "$http_referer" '
                  '$http_user_agent $http_x_forwarded_for $request_time $upstream_response_time $upstream_addr $upstream_status';
  
    #######
    ## http setting
    #######
    sendfile       on;
    tcp_nopush     on;
    tcp_nodelay    on;
    keepalive_timeout  65;
    proxy_cache_path /var/www/cache levels=1:2 keys_zone=mycache:20m max_size=2048m inactive=60m;
    proxy_temp_path /var/www/cache/tmp;
  
    fastcgi_connect_timeout 3000;
    fastcgi_send_timeout 3000;
    fastcgi_read_timeout 3000;
    fastcgi_buffer_size 256k;
    fastcgi_buffers 8 256k;
    fastcgi_busy_buffers_size 256k;
    fastcgi_temp_file_write_size 256k;
    fastcgi_intercept_errors on;
  
    #
    client_header_timeout 600s;
    client_body_timeout 600s;
   # client_max_body_size 50m;
    client_max_body_size 100m;               #允許客戶端請求的最大單個檔案位元組數
    client_body_buffer_size 256k;            #緩衝區代理緩衝請求的最大位元組數,可以理解為先儲存到本地再傳給使用者
  
    gzip  on;
    gzip_min_length  1k;
    gzip_buffers     4 16k;
    gzip_http_version 1.1;
    gzip_comp_level 9;
    gzip_types       text/plain application/x-javascript text/css application/xml text/javascript application/x-httpd-php;
    gzip_vary on;
  
    ## includes vhosts
    include vhosts/*.conf;
}
 
 
[root@master-node ~]# mkdir /usr/local/nginx/conf/vhosts
[root@master-node ~]# mkdir /var/www/cache
[root@master-node ~]# ulimit 65535
 
[root@master-node ~]# vim /usr/local/nginx/conf/vhosts/LB.conf
upstream LB-WWW {
      ip_hash;                                                  #這是負載均衡的ip_hash負載策略,好處是實現session共享。根據需求決定是否加這個配置。
      server 192.168.1.101:80 max_fails=3 fail_timeout=30s;     #max_fails = 3 為允許失敗的次數,預設值為1。 這是對後端節點做健康檢查。
      server 192.168.1.102:80 max_fails=3 fail_timeout=30s;     #fail_timeout = 30s 當max_fails次失敗後,暫停將請求分發到該後端伺服器的時間
      server 192.168.1.118:80 max_fails=3 fail_timeout=30s;
    }
     
server {
     listen       80;
     server_name  www.wangshibo.com;
   
      access_log  /usr/local/nginx/logs/www-access.log main;
      error_log  /usr/local/nginx/logs/www-error.log;
   
     location / {
         proxy_pass http://LB-WWW;
         proxy_redirect off ;
         proxy_set_header Host $host;
         proxy_set_header X-Real-IP $remote_addr;
         proxy_set_header REMOTE-HOST $remote_addr;
         proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
         proxy_connect_timeout 300;             #跟後端伺服器連線超時時間,發起握手等候響應時間
         proxy_send_timeout 300;                #後端伺服器回傳時間,就是在規定時間內後端伺服器必須傳完所有資料
         proxy_read_timeout 600;                #連線成功後等待後端伺服器的響應時間,已經進入後端的排隊之中等候處理
         proxy_buffer_size 256k;                #代理請求緩衝區,會儲存使用者的頭資訊以供nginx進行處理
         proxy_buffers 4 256k;                  #同上,告訴nginx儲存單個用幾個buffer最大用多少空間
         proxy_busy_buffers_size 256k;          #如果系統很忙時候可以申請最大的proxy_buffers
         proxy_temp_file_write_size 256k;       #proxy快取臨時檔案的大小
         proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504 http_403 http_404;
         proxy_max_temp_file_size 128m;
         proxy_cache mycache;                   #如果該域名負載的訪問請求不需要快取功能,那就將這以下四行全部註釋掉。
         proxy_cache_valid 200 302 1h; 
         proxy_cache_valid 301 1d;
         proxy_cache_valid any 1m;
        }
}

 需要注意的是:

上面的nginx負載均衡配置中已經開啟了cache快取功能,如果不需要快取功能,則將上面vhosts目錄下的虛擬主機配置中的proxy_cache mycache及其下面三行註釋即可!

這裡說下曾經碰到過的一個反常情況:
按照上面第一種nginx upstream的健康檢查配置後,發現將upstream中的後端兩臺機器中的一臺關閉,訪問請求還是會打到這臺關閉的後端機器上
檢視方法:
直接瀏覽器裡訪問,發現訪問結果一會兒好,一會兒壞,這是因為請求不僅打到了後端好的機器上,也打到了關閉的機器上了;

原因分析:
這是因為後端兩臺機器+埠的訪問結果被包含在了 proxy_next_upstream中定義的狀態碼。

解決辦法:
將
proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504 http_404;
改成
proxy_next_upstream error timeout invalid_header http_502 http_503 http_504;

重啟nginx服務後,在瀏覽器裡輸入域名訪問,發現訪問結果是正常的了!
說明:
該域名的訪問請求都打到了後端好著的那臺伺服器上了,那臺關閉的伺服器已經從upstream負載中踢出去了。

這個通過檢視對應域名的access.log日誌能發現:
訪問請求會同時到達後端兩臺機器上,只不過請求到達關閉的那臺機器上時就會通過健康檢查發現它是壞的,就會將它自動提出,這樣在瀏覽器裡的訪問結果顯示的就
只是正常的那臺後端機器處理後的結果了。

檢視error.log錯誤日誌,發現裡面的資訊都是:訪問請求upstream到後端關閉的機器上時,全是"connect() failed (111: Connection refused)",這是正常的,
因為upstream配置裡每個幾秒就會去健康後端機器,當連線失敗時,錯誤資訊就輸出到error.log日誌裡。

二、利用nginx_upstream_check_module模組對後端節點做健康檢查
除了上面介紹的nginx自帶模組,還有一個更專業的模組,來專門提供負載均衡器內節點的健康檢查的。這個就是淘寶技術團隊開發的nginx模組。
用nginx做前端反向代理,如果後端伺服器宕掉的話,nginx是不會把這臺realserver踢出upstream的,還會把請求轉發到後端的這臺realserver上面。所以當某臺機器出現問題時,會看到nginx的日誌會有一段轉發失敗然後轉發正常的日誌。藉助淘寶技術團隊開發的nginx模快nginx_upstream_check_module來檢測後方realserver的健康狀態,如果後端伺服器不可用,則會將其踢出upstream,所有的請求不轉發到這臺伺服器。當期恢復正常時,將其加入upstream
nginx_upstream_check_module,通過它可以用來檢測後端realserver的健康狀態。如果後端realserver不可用,則所以的請求就不會轉發到該節點上。個人比較推薦使用這種方式來檢查nginx後端節點的健康狀態。
在淘寶自己的tengine上是自帶了該模組的,大家可以訪問淘寶tengine的官網http://tengine.taobao.org來獲取該版本的nginx,
如果沒有使用淘寶的tengine的話,可以通過補丁的方式來新增該模組到我們自己的nginx中。部署流程如下:

比如健康檢查配置:
upstream test_web {
    server 192.168.1.21:80;
    server 192.168.1.22:80;
    check interval=3000 rise=2 fall=5 timeout=1000 type=http;
       
}
上面配置的意思是,對test_web這個負載均衡條目中的所有節點,每個3秒(3000毫秒)檢測一次,請求2次正常則標記realserver狀態為up,如果檢測5次都失敗,則標記realserver的狀態為down,超時時間為1秒。

---------------------下面列出nginx_upstream_check_module 模組所支援的指令含義----------------------
Syntax:  check interval=milliseconds [fall=count] [rise=count] [timeout=milliseconds] [default_down=true|false] [type=tcp|http|ssl_hello|mysql|ajp] [port=check_port]
Default: 如果沒有配置引數,預設值是:interval=30000 fall=5 rise=2 timeout=1000 default_down=true type=tcp
Context: upstream

該指令可以開啟後端伺服器的健康檢查功能。指令後面的引數意義是:
interval:向後端傳送的健康檢查包的間隔。
fall(fall_count): 如果連續失敗次數達到fall_count,伺服器就被認為是down。
rise(rise_count): 如果連續成功次數達到rise_count,伺服器就被認為是up。
timeout: 後端健康請求的超時時間,單位毫秒。
default_down: 設定初始時伺服器的狀態,如果是true,就說明預設是down的,如果是false,就是up的。預設值是true,也就是一開始伺服器認為是不可用,要等健康檢查包達到一定成功次數以後才會被認為是健康的。
type:健康檢查包的型別,現在支援以下多種型別:
     tcp:簡單的tcp連線,如果連線成功,就說明後端正常。
     ssl_hello:傳送一個初始的SSL hello包並接受伺服器的SSL hello包。
     http:傳送HTTP請求,通過後端的回覆包的狀態來判斷後端是否存活。
     mysql: 向mysql伺服器連線,通過接收伺服器的greeting包來判斷後端是否存活。
     ajp:向後端傳送AJP協議的Cping包,通過接收Cpong包來判斷後端是否存活。
     port: 指定後端伺服器的檢查埠。你可以指定不同於真實服務的後端伺服器的埠,比如後端提供的是443埠的應用,你可以去檢查80埠的狀態來判斷後端健康狀況。預設是0,表示跟後端server提供真實服務的埠一樣。該選項出現於Tengine-1.4.0。


Syntax: check_keepalive_requests request_num
Default: 1
Context: upstream
該指令可以配置一個連線傳送的請求數,其預設值為1,表示Tengine完成1次請求後即關閉連線。

Syntax: check_http_send http_packet
Default: "GET / HTTP/1.0\r\n\r\n"
Context: upstream
該指令可以配置http健康檢查包傳送的請求內容。為了減少傳輸資料量,推薦採用"HEAD"方法。

當採用長連線進行健康檢查時,需在該指令中新增keep-alive請求頭,如:"HEAD / HTTP/1.1\r\nConnection: keep-alive\r\n\r\n"。 同時,在採用"GET"方法的情況下,請求uri的size不宜過大,確保可以在1個interval內傳輸完成,否則會被健康檢查模組視為後端伺服器或網路異常。
Syntax: check_http_expect_alive [ http_2xx | http_3xx | http_4xx | http_5xx ]
Default: http_2xx | http_3xx
Context: upstream
該指令指定HTTP回覆的成功狀態,預設認為2XX和3XX的狀態是健康的。

Syntax: check_shm_size size
Default: 1M
Context: http
所有的後端伺服器健康檢查狀態都存於共享記憶體中,該指令可以設定共享記憶體的大小。預設是1M,如果你有1千臺以上的伺服器並在配置的時候出現了錯誤,就可能需要擴大該記憶體的大小。

Syntax: check_status [html|csv|json]
Default: check_status html
Context: location
顯示伺服器的健康狀態頁面。該指令需要在http塊中配置。

在Tengine-1.4.0以後,可以配置顯示頁面的格式。支援的格式有: html、csv、 json。預設型別是html。
也可以通過請求的引數來指定格式,假設‘/status’是你狀態頁面的URL, format引數改變頁面的格式,比如:
/status?format=html
/status?format=csv
/status?format=json

同時你也可以通過status引數來獲取相同伺服器狀態的列表,比如:
/status?format=html&status=down
/status?format=csv&status=up

下面是一個狀態配置的範例:
http {
      server {
	     location /nstatus {
		     check_status;
		     access_log off;
		     #allow IP;
		     #deny all;
	     }
      }
}

具體例項配置如下: 

1)下載nginx_upstream_check_module模組,並部署到nginx中。
[root@localhost ~]# cd /usr/local/src
[root@localhost src]# wget https://github.com/yaoweibin/nginx_upstream_check_module/archive/master.zip
[root@localhost src]# unzip unzip master.zip
[root@localhost src]# ls
master.zip  nginx_upstream_check_module-master
[root@localhost src]# ls nginx_upstream_check_module-master/
CHANGES              check_1.2.6+.patch   check.patch                ngx_http_upstream_check_module.c          upstream_fair.patch
check_1.11.1+.patch  check_1.5.12+.patch  config                     ngx_http_upstream_check_module.h          util
check_1.11.5+.patch  check_1.7.2+.patch   doc                        ngx_http_upstream_jvm_route_module.patch
check_1.2.1.patch    check_1.7.5+.patch   nginx-sticky-module.patch  README
check_1.2.2+.patch   check_1.9.2+.patch   nginx-tests                test
  
[root@localhost src]# wget http://nginx.org/download/nginx-1.8.0.tar.gz
[root@localhost src]# tar -zxvf nginx-1.8.0.tar.gz
[root@localhost src]# cd nginx-1.8.0
  
[root@localhost nginx-1.8.0]# patch -p1 < ../nginx_upstream_check_module-master/check_1.9.2+.patch
[root@localhost nginx-1.8.0]# ./configure --prefix=/usr/local/nginx --user=nginx --group=nginx --with-http_ssl_module --with-http_flv_module --with-http_stub_status_module --with-http_gzip_static_module --with-pcre --add-module=../nginx_upstream_check_module-master/
[root@node1 src]# make && make install
  
2)nginx配置
[root@master-node ~]# vim /usr/local/nginx/conf/vhosts/LB.conf
upstream LB-WWW {
      server 192.168.1.101:80;  
      server 192.168.1.102:80;
      check interval=3000 rise=2 fall=5 timeout=1000 type=http;
      check_keepalive_requests 100;
      check_http_send "HEAD / HTTP/1.1\r\nConnection: keep-alive\r\n\r\n";
      check_http_expect_alive http_2xx http_3xx;
    }
       
server {
     listen       80;
     server_name  www.wangshibo.com;
     
      access_log  /usr/local/nginx/logs/www-access.log main;
      error_log  /usr/local/nginx/logs/www-error.log;
     
     location / {
         proxy_pass http://LB-WWW;
         proxy_redirect off ;
         proxy_set_header Host $host;
         proxy_set_header X-Real-IP $remote_addr;
         proxy_set_header REMOTE-HOST $remote_addr;
         proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
         proxy_connect_timeout 300;          
         proxy_send_timeout 300;            
         proxy_read_timeout 600;              
         proxy_buffer_size 256k;              
         proxy_buffers 4 256k;               
         proxy_busy_buffers_size 256k;        
         proxy_temp_file_write_size 256k;     
         proxy_next_upstream error timeout invalid_header http_500 http_503 http_404;
         proxy_max_temp_file_size 128m;
         proxy_cache mycache;                             
         proxy_cache_valid 200 302 60m;                   
         proxy_cache_valid 404 1m;
        }
        
       location /nstatus {
         check_status;
         access_log off;
         #allow IP;
         #deny all;
       }
}

配置完畢後,重啟nginx。然後訪問http://localhost/nstatus這個頁面就可以看到當前兩臺realserver實時的健康狀態。
溫馨提示:在生產環境的實施應用中需要注意下面兩點

1)主要定義好type。由於預設的type是tcp型別,因此假設服務啟動,不管是否初始化完畢,它的埠都會起來,所以此時前段負載均衡器為認為該服務已經可用,其實是不可用狀態。
2)注意check_http_send值的設定。由於它的預設值是"GET / HTTP/1.0\r\n\r\n"。
   假設應用是通過http://ip/name訪問的,那麼這裡check_http_send值就需要更改為 "GET /name HTTP/1.0\r\n\r\n"才可以。
   針對採用長連線進行檢查的, 這裡增加 keep-alive請求 頭,即"HEAD /name HTTP/1.1\r\nConnection: keep-alive\r\n\r\n"。
   如果後端的tomcat是基於域名的多虛擬機器,此時你需要通過 check_http_send定義host,不然每次訪問都是失敗,範例:
   check_http_send "GET /mobileapi HTTP/1.0\r\n HOST  www.redhat.sx\r\n\r\n";

也可以參考http://www.cnblogs.com/kevingrace/p/5882006.html這篇文件裡的nginx後端節點健康檢查的方法 

三、利用ngx_http_healthcheck_module模組對後端節點做健康檢查
除了上面兩個模組,nginx官方在早期的時候還提供了一個ngx_http_healthcheck_module模組用來進行nginx後端節點的健康檢查。nginx_upstream_check_module模組就是參照該模組的設計理念進行開發的,因此在使用和效果上都大同小異。
但是需要注意的是,ngx_http_healthcheck_module模組僅僅支援nginx的1.0.0版本,1.1.0版本以後都不支援了!因此,對於目前常見的生產環境上基本都不會去用這個模組了~

相關文章