1. 程式人生 > >Nginx學習之十三-負載均衡-IP雜湊策略剖析

Nginx學習之十三-負載均衡-IP雜湊策略剖析

static ngx_int_t
ngx_http_upstream_get_ip_hash_peer(ngx_peer_connection_t *pc, void *data)
{
    ngx_http_upstream_ip_hash_peer_data_t  *iphp = data;

    time_t                        now;
    ngx_int_t                     w;
    uintptr_t                     m;
    ngx_uint_t                    i, n, p, hash;
    ngx_http_upstream_rr_peer_t  *peer;

    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0,
                   "get ip hash peer, try: %ui", pc->tries);

    /* TODO: cached */
    //如果失敗次數太多,或者只有一個後端服務,那麼直接做RR選擇
    if (iphp->tries > 20 || iphp->rrp.peers->single) {
        return iphp->get_rr_peer(pc, &iphp->rrp);
    }

    now = ngx_time();

    pc->cached = 0;
    pc->connection = NULL;

    hash = iphp->hash;

    for ( ;; ) {
        //計算IP的hash值
        for (i = 0; i < iphp->addrlen; i++) {
            //113質數,可以讓雜湊結果更雜湊
            hash = (hash * 113 + iphp->addr[i]) % 6271;
        }

        //根據雜湊結果得到被選中的後端伺服器
        if (!iphp->rrp.peers->weighted) {
            p = hash % iphp->rrp.peers->number;

        } else {
            w = hash % iphp->rrp.peers->total_weight;

            for (i = 0; i < iphp->rrp.peers->number; i++) {
                w -= iphp->rrp.peers->peer[i].weight;
                if (w < 0) {
                    break;
                }
            }

            p = i;
        }

        //伺服器對應在點陣圖中的位置計算
        n = p / (8 * sizeof(uintptr_t));
        m = (uintptr_t) 1 << p % (8 * sizeof(uintptr_t));

        if (!(iphp->rrp.tried[n] & m)) {

            ngx_log_debug2(NGX_LOG_DEBUG_HTTP, pc->log, 0,
                           "get ip hash peer, hash: %ui %04XA", p, m);

            //獲取伺服器
            peer = &iphp->rrp.peers->peer[p];

            /* ngx_lock_mutex(iphp->rrp.peers->mutex); */

            //伺服器未掛掉
            if (!peer->down) {
                //失敗次數已達上限
                if (peer->max_fails == 0 || peer->fails < peer->max_fails) {
                    break;
                }

                if (now - peer->checked > peer->fail_timeout) {
                    peer->checked = now;
                    break;
                }
            }
            //更改點陣圖標記值
            iphp->rrp.tried[n] |= m;

            /* ngx_unlock_mutex(iphp->rrp.peers->mutex); */
            //在連線一個遠端伺服器時,當前連線異常失敗後可以嘗試的次數
            pc->tries--;
        }

        //已經嘗試的次數超過閾值,採用RR輪詢
        if (++iphp->tries >= 20) {
            return iphp->get_rr_peer(pc, &iphp->rrp);
        }
    }

    //當前服務索引
    iphp->rrp.current = p;
    //伺服器地址及名字儲存
    pc->sockaddr = peer->sockaddr;
    pc->socklen = peer->socklen;
    pc->name = &peer->name;

    /* ngx_unlock_mutex(iphp->rrp.peers->mutex); */
    //點陣圖更新
    iphp->rrp.tried[n] |= m;
    //保留種子,使下次get_ip_hash_peer的時候能夠選到同一個peer上
    iphp->hash = hash;

    return NGX_OK;
}

上述計算過程中,依據ip的hash值進行對映的時候,依據伺服器列表頭部結構中weighted欄位分了兩種不同的情況。 下面看看weighted的計算過程(ngx_http_upstream_round_robin.c):
        //指向伺服器列表指標        
        server = us->servers->elts;

        n = 0;
        w = 0;
        //遍歷伺服器列表,計算地址總數以及總的權值
        for (i = 0; i < us->servers->nelts; i++) {
            if (server[i].backup) {
                continue;
            }

            n += server[i].naddrs;
            w += server[i].naddrs * server[i].weight;
        }
        //weighted計算
        peers->weighted = (w != n);

server的型別是ngx_http_upstream_server_t。
typedef struct {
    ngx_addr_t                      *addrs;//指向儲存IP地址的陣列的指標,host資訊(對應的是 ngx_url_t->addrs )
    ngx_uint_t                       naddrs;//與第一個引數配合使用,陣列元素個數(對應的是 ngx_url_t->naddrs )
    ngx_uint_t                       weight;//權值
    ngx_uint_t                       max_fails;
    time_t                           fail_timeout;

    unsigned                         down:1;
    unsigned                         backup:1;
} ngx_http_upstream_server_t;

一個域名可能對應多個IP地址。 server wegiht 欄位,作為server權重,對應虛擬節點數目。 具體演算法,將每個server虛擬成n個節點,均勻分佈到hash環上,每次請求,根據配置的引數計算出一個hash值,在hash環上查詢離這個hash最近的虛擬節點,對應的server作為該次請求的後端機器。 因此,當weighted欄位等於0的時候,表示虛擬節點數和IP地址數是相等的,因此直接將hash值針對ip地址總數取模即可。如果weighted不等於0,表示虛擬節點數和IP地址數不等,因此需要按照虛擬節點數另外計算。查詢離這個hash最近的虛擬節點,作為該請求的後端機器。

整個IP雜湊選擇流程

流程圖如下:

輪詢策略和IP雜湊策略對比

加權輪詢策略 優點:適用性更強,不依賴於客戶端的任何資訊,完全依靠後端伺服器的情況來進行選擇。能把客戶端請求更合理更均勻地分配到各個後端伺服器處理。 缺點:同一個客戶端的多次請求可能會被分配到不同的後端伺服器進行處理,無法滿足做會話保持的應用的需求。 IP雜湊策略 優點:能較好地把同一個客戶端的多次請求分配到同一臺伺服器處理,避免了加權輪詢無法適用會話保持的需求。 缺點:當某個時刻來自某個IP地址的請求特別多,那麼將導致某臺後端伺服器的壓力可能非常大,而其他後端伺服器卻空閒的不均衡情況、

參考資料:


《深入剖析Nginx》