nginx負載均衡的5種策略及原理


nginx的upstream目前支持的5種方式的分配:

1、輪詢(默認)
每個請求按時間順序逐一分配到不同的后端服務器,如果后端服務器down掉,能自動剔除。 
upstream backserver { 
server 192.168.0.14; 
server 192.168.0.15; 

2、指定權重
指定輪詢幾率,weight和訪問比率成正比,用於后端服務器性能不均的情況。 
upstream backserver { 
server 192.168.0.14 weight=8; 
server 192.168.0.15 weight=10; 

3、IP綁定 ip_hash
每個請求按訪問ip的hash結果分配,這樣每個訪客固定訪問一個后端服務器,可以解決session的問題。 
upstream backserver { 
ip_hash; 
server 192.168.0.14:88; 
server 192.168.0.15:80; 

4、fair(第三方)
按后端服務器的響應時間來分配請求,響應時間短的優先分配。 
upstream backserver { 
server server1; 
server server2; 
fair; 

5、url_hash(第三方)
按訪問url的hash結果來分配請求,使每個url定向到同一個后端服務器,后端服務器為緩存時比較有效。 
upstream backserver { 
server squid1:3128; 
server squid2:3128; 
hash $request_uri; 
hash_method crc32; 

在需要使用負載均衡的server中增加 

proxy_pass http://backserver/; 
upstream backserver{ 
ip_hash; 
server 127.0.0.1:9090 down; (down 表示當前的server暫時不參與負載) 
server 127.0.0.1:8080 weight=2; (weight 默認為1.weight越大,負載的權重就越大) 
server 127.0.0.1:6060; 
server 127.0.0.1:7070 backup; (其它所有的非backup機器down或者忙的時候,請求backup機器) 

max_fails :允許請求失敗的次數默認為1.當超過最大次數時,返回proxy_next_upstream 模塊定義的錯誤 
fail_timeout:max_fails次失敗后,暫停的時間

nginx的負載均衡策略可以划分為兩大類:內置策略和擴展策略:

內置策略包含加權輪詢和ip hash,在默認情況下這兩種策略會編譯進nginx內核,只需在nginx配置中指明參數即可。擴展策略有很多,如fair、通用hash、consistent hash等,默認不編譯進nginx內核。

由於在nginx版本升級中負載均衡的代碼沒有本質性的變化,因此下面將以nginx1.0.15穩定版為例,從源碼角度分析各個策略。

 

加權輪詢(weighted round robin)

輪詢的原理很簡單,首先我們介紹一下輪詢的基本流程。如下是處理一次請求的流程圖:

圖中有兩點需要注意:

第一,如果可以把加權輪詢算法分為先深搜索和先廣搜索,那么nginx采用的是先深搜索算法,即將首先將請求都分給高權重的機器,直到該機器的權值降到了比其他機器低,才開始將請求分給下一個高權重的機器。

第二,當所有后端機器都down掉時,nginx會立即將所有機器的標志位清成初始狀態,以避免造成所有的機器都處在timeout的狀態,從而導致整個前端被夯住。

接下來看下源碼。nginx的目錄結構很清晰,加權輪詢所在路徑為nginx-1.0.15/src/http/ngx_http_upstream_round_robin.[c|h],在源碼的基礎上,針對重要的、不易理解的地方我加了注釋。首先看下ngx_http_upstream_round_robin.h中的重要聲明:

typedef struct {
//基本socket信息
    struct sockaddr                   *sockaddr;
    socklen_t                         socklen;
    ngx_str_t                         name;
//當前權重值和設定權重值
    ngx_int_t                         current_weight;
    ngx_int_t                         weight;
//失敗次數和訪問時間
    ngx_uint_t                        fails;
    time_t                            accessed;
//失敗次數上限值和失敗時間閥值
    ngx_uint_t                        max_fails;
    time_t                            fail_timeout;
//服務器是否參與策略
    ngx_uint_t                        down;     /* unsigned down:1; */
//ssl相關 
#if (NGX_HTTP_SSL)
    ngx_ssl_session_t                 *ssl_session;      /* local to a process */
#endif 
} ngx_http_upstream_rr_peer_t;

從變量命名中就可以大致猜出其作用。解釋一下current_weight和weight的區別,前者為權重排序的值,隨着處理請求會動態的變化,后者則是配置值,用來恢復初始狀態。

接下我們來看下輪詢的創建過程。代碼如下圖:

//創建過程
    if (rrp->peers->number <= 8 * sizeof(uintptr_t)) {
        rrp->tried * &rrp->data;
        rrp->data = 0;
    } else {
        n = (rrp->peer->number + (8 * sizeof(uintptr_t) - 1)) / (8 * sizeof(uintptr_t));
        
        rrp->tried = ngx_pcalloc(r->pool, n * sizeof(uintptr_t));
        if (rrp->tried == NULL) {
            return NGX_ERROR;
        }
    }

這里有個tried變量需要做些說明:tried中記錄了服務器當前是否被嘗試連接過。他是一個位圖。如果服務器數量小於32,則只需在一個int中即可記錄下所有服務器狀態。如果服務器數量大於32,則需在內存池中申請內存來存儲。

對該位圖數組的使用可參考如下代碼:

//使用樣例
                n = rrp->current / (8 * sizeof(uintptr_t));
                m = (uintptr_t) 1 << rrp->current % (8 * sizeof(uintptr_t))
      
                if (!(rrp->tried[n] & m)) {
                    peer = &rrp->peers->[rrp->current];
                }

最后是實際的策略代碼,邏輯較簡單,代碼實現也只有30行。來看代碼。

 1 for ( ;; ) {
 2     for (i=0; i< peers->number; i++) {
 3         if (peer[i]->current_weight <= 0) {
 4             continue;
 5         }
 6         n = 1;
 7         while (i < peers->number - 1) {
 8             i++;
 9             if (peer[i].current_weight <=0) {
10                 continue;
11             }
12             if (peer[n].current_weight * 1000 / peer[i].current_weight > 
13                 peer[n].weight * 1000 / peer[i].weight) {
14                 return n;
15             }
16             n = i;
17         }
18         if (peer[i].current_weight > 0) {
19            n = i;
20         }
21 
22         return n;
23     }
24     if (reset++) {
25         return 0;
26     }
27     for (i = 0; i < peers->number; i++) {
28         peer[i].current_weight = peer[i].weight;
29     }
30 }

ip hash策略

ip hash是nginx內置的另一個負載均衡策略,流程和輪詢很類似,只是其中的算法和具體的策略有些變化。如下圖所示:

 

 

 ip hash算法的核心實現請看如下代碼:

for (i = 0; i < 3; i++) {
    hash = (hash * 113 + iphp->addr[i]) % 6271);
}

p = hash % iphp->rrp.peers->number;

可以看到,hash值既與ip有關又與后端機器的數量有關。經測試,上述算法可以連續產生1045個互異的value,這是此算法硬限制。nginx使用了保護機制,當經過20次hash仍然找不到可用的機器時,算法退化成輪詢。

因此,從本質上說,ip hash算法是一種變相的輪詢算法,如果兩個ip的初始hash值恰好相同,那么來自這兩個ip的請求將永遠落在同一台服務器上,這為均衡性埋下了較深隱患。
 fair
fair策略是擴展策略,默認不被編譯進nginx內核。它根據后端服務器的響應時間判斷負載情況,從中選出負載最輕的機器進行分流。
這種策略具有很強的自適應性,但是實際的網絡環境往往不是那么簡單,因此須慎用。

通用hash、一致性hash

通用hash和一致性hash也是種擴展策略。通用hash可以以nginx內置的變量為key進行hash,一致性hash采用了nginx內置的一致性hash環,可支持memcache。

對比測試

1.1  easyABC

easyABC是百度內部開發的性能測試工具,培訓采用epool模型實現,簡單易上手,可以模擬GET/POST請求,極限情況下可以提供上萬的壓力,在團隊內部得到廣泛使用。

由於被測試對象為反向代理服務器,因此需要在其后端搭建樁服務器,這里用nginx作為樁Web Server,提供最基本的靜態文件服務。

1.2  polygraph

polygraph是一款免費的性能測試工具,以對緩存服務、代理、交換機等方面的測試見長。它有規范的配置語言PGL(Polygraph Language),為軟件提供了強大的靈活性。其工作原理如下圖所示:

 

 

 

 

polygraph提供Client端和Server端,將測試目標nginx放在二者之間,三者之間的網絡交互均走http協議,只需配置ip+port即可。

Client端可以配置虛擬robot的個數以及每個robot發請求的速率,並向代理服務器發起隨機的靜態文件請求,Server端將按照請求的url生成隨機大小的靜態文件做響應。

選用這個測試軟件的一個主要原因:可以產生隨機的url作為nginx各種hash策略key。
另外polygraph還提供了日志分析工具,功能比較豐富,感興趣的同學可以參考附錄材料。

2. 測試環境

本次測試運行在5台物理機。其中:被測對象單獨搭在一台8核機器上,另外四台4核機器分別搭建了easyABC、webserver樁和polygraph。如下圖所示:

 

 

3. 測試方案

給各位介紹一下關鍵的測試指標:

均衡性:是否能夠將請求均勻的發送給后端
一致性:同一個key的請求,是否能落到同一台機器
容災性:當部分后端機器掛掉時,是否能夠正常工作

以上述指標為指導,我們針對如下4個測試場景分別用easyABC和polygraph測試:

場景1      server_*均正常提供服務;
場景2      server_4掛掉,其他正常;
場景3      server_3、server_4掛掉,其他正常;
場景4      server_*均恢復正常服務。

上述四個場景將按照時間順序進行,每個場景將建立在上一個場景基礎上,被測試對象無需做任何操作,以最大程度模擬實際情況。

另外,考慮到測試工具自身的特點,在easyabc上的測試壓力在17000左右,polygraph上的測試壓力在4000左右。以上測試均保證被測試對象可以正常工作,且無任何notice級別以上(alert/error/warn)的日志出現,在每個場景中記錄下server_*的qps用於最后的策略分析。

4.  結果

對比在兩種測試工具下的測試結果會發現,結果完全一致,因此可以排除測試工具的影響。表1和圖1是輪詢策略在兩種測試工具下的負載情況。

從圖表中可以看出,輪詢策略對於均衡性和容災性都可以做到較好的滿足。

 

表2和圖2是fair策略在兩種測試工具下的負載情況。fair策略受環境影響非常大,在排除了測試工具的干擾之后,結果仍然有非常大的抖動。

從直觀上講,這完全不滿足均衡性。但從另一個角度出發,恰恰是由於這種自適應性確保了在復雜的網絡環境中能夠物盡所用。因此,在應用到工業生產中之前,需要在具體的環境中做好測試工作。

 

以下圖表是各種hash策略,所不同的僅僅是hash key或者是具體的算法實現,因此一起做對比。實際測試中發現,通用hash和一致性hash均存在一個問題:當某台后端的機器掛掉時,原有落到這台機器上的流量會丟失,但是在ip hash中就不存在這樣的問題。

正如上文中對ip hash源碼的分析,當ip hash失效時,會退化為輪詢策略,因此不會有丟失流量的情況。從這個層面上說,ip hash也可以看成是輪詢的升級版。

 

圖5為ip hash策略,ip hash是nginx內置策略,可以看做是前兩種策略的特例:以來源IP為key。

由於測試工具不太擅於模擬海量IP下的請求,因此這里截取線上實際的情況加以分析。如下圖所示:

 

圖5 IP Hash策略

圖中前1/3使用輪詢策略,中間段使用ip hash策略,后1/3仍然是輪詢策略。可以明顯的看出,ip hash的均衡性存在着很大的問題。

原因並不難分析,在實際的網絡環境中,有大量的高校出口路由器ip、企業出口路由器ip等網絡節點,這些節點帶來的流量往往是普通用戶的成百上千倍,而ip hash策略恰恰是按照ip來划分流量,因此造成上述后果也就自然而然了。
 

小結

通過實際的對比測試,我們對nginx各個負載均衡策略進行了驗證。下面從均衡性、一致性、容災性以及適用場景等角度對比各種策略。如下圖示:

 

 

我們從源碼和實際測試數據角度分析說明了nginx負載均衡的策略,給出了各種策略適合的應用場景。通過分析不難發現,無論哪種策略都不是萬金油,在具體場景下應該選擇哪種策略一定程度上依賴於使用者對策略的熟悉程度。

以上分析和測試數據能夠對大家有所幫助,期待有更多越來越好的負載均衡策略涌現,造福更多運維開發同學。
 

參考:
https://www.cnblogs.com/wpjamer/articles/6443332.html

https://www.cnblogs.com/andashu/p/6377323.html
————————————————
版權聲明:本文為CSDN博主「乄名007」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/qq_35119422/article/details/81505732


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM