歡迎您光臨本站 註冊首頁

Nginx Module開發指南 - 翻譯(第3.5章負載平衡)

←手機掃碼閱讀     火星人 @ 2014-03-04 , reply:0

Nginx Module開發指南 - 翻譯(第3.5章負載平衡)

因為工作需要,把最關鍵的一章翻譯出來:
原文在這裡:
Emiller's Guide To Nginx Module Development
http://www.evanmiller.org/nginx-modules-guide.html#lb-release

譯者:Jacky
Date:20090612
本文URL:http://newbdez33.blogspot.com/2009/06/nginx-module-35.html

#不知道這個文章有沒有過翻譯。
#本章與之幾章有聯繫,可能看之前需要參考之前的幾章;
#Nginx代碼接觸時間不長,翻譯的也比較爛,顯丑了。
#翻譯完后回看一遍感覺peer應該給翻譯出來可能會更好理解一些,whatever我懶得回去改了。

3.5 Load-Balancer解析

Load-balancer就是用來決定當前的請求會被哪個後台伺服器接收到;這玩意兒存在的意義就是為了在分發請求或者散列(hashing)一些關於請求的信息。這一段就給哥兒幾個講講一個load-balancer的安裝和調用,以及用upstream_hash模塊做為例子。upstream_hash用散列法(hash)選擇在nginx.conf指定的幾個後台伺服器中選擇具體由哪個伺服器處理請求。

一個load-balancing模板有6小塊:
1.激活置命令的時候將調用一個註冊函數
2.註冊函數將確定上一步的合法 server 值(server options),比如weight=什麼什麼,同時註冊一個upstream初始化函數。
3.upstream初始化函數在配置參數驗證后被調用,然後:
  解析 server 到特定的IP地址。
  分配套接字空間
  為peer初始化函數設置回調函數
4.每個請求只調用一次peer初始化函數,填充數據結構后負載平衡函數(load-balancing function)將訪問並對其操作。
5.負載平衡函數決定怎麼樣路由請求;這個函數每次請求至少被調用一次(如果後台響應失敗,則會再被調用)。這是我們要最注意的部分。
6.最後,peer釋放函數(peer release function)能在與一個特定的後台伺服器通訊完成後更新統計信息(不管失敗與否)。

比較多,下面就分別講解。

3.5.1 配置命令激活
配置命令的名明,重調,賦值都合法的話,一個函數會在他們載入時調用。一個load-balancer配置命令應該設置上NGX_HTTP_UPS_CONF標誌,這樣的話Nginx才知道這個配置命令是存在於upstream區塊。然後提供一個指針指向註冊函數。這裡是upstream_hash部分命令聲明:


{ ngx_string("hash"),
      NGX_HTTP_UPS_CONF|NGX_CONF_NOARGS,
      ngx_http_upstream_hash,
0,
0,
      NULL },


目前還沒有新的知識。(參考2.2有介紹,不好意思沒翻)

3.5.2 註冊函數
上面的回調函數ngx_http_upstream_hash就是註冊函數,之所以叫這個名是因為它註冊了upstream初始化函數並填充upstream的配置信息。更進一步講,註冊函數確定在upstream區塊里,哪個選項(options)對server配置命令是合法的。這裡就是upstream_hash的註冊函數代碼:


ngx_http_upstream_hash(ngx_conf_t *cf, ngx_command_t *cmd,
void
*conf)
{
    ngx_http_upstream_srv_conf_t  *uscf;
    ngx_http_script_compile_t      sc;
    ngx_str_t                     *value;
    ngx_array_t                   *vars_lengths,
*vars_values;

    value = cf->args->elts;
/* the following is necessary to evaluate the argument to "hash" as a $variable */
    ngx_memzero(&sc,
sizeof(ngx_http_script_compile_t));

    vars_lengths = NULL;
    vars_values = NULL;

    sc.cf = cf;
    sc.source =
&value;
    sc.lengths =
&vars_lengths;
    sc.values =
&vars_values;
    sc.complete_lengths =
1;
    sc.complete_values =
1;
if
(ngx_http_script_compile(&sc)
!= NGX_OK)
{
return NGX_CONF_ERROR;
}
/* end of $variable stuff */

    uscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_upstream_module);
/* the upstream initialization function */
    uscf->peer.init_upstream = ngx_http_upstream_init_hash;

    uscf->flags = NGX_HTTP_UPSTREAM_CREATE;
/* OK, more $variable stuff */
    uscf->values = vars_values->elts;
    uscf->lengths = vars_lengths->elts;
/* set a default value for "hash_method" */
if
(uscf->hash_function == NULL)
{
        uscf->hash_function = ngx_hash_key;
}
return NGX_CONF_OK;
}


上面這些我們就晚點看(這句實現不知道該怎麼翻,我直接理解;原句在此:Aside from jumping through hoops so wecan evaluation $variable later, it's pretty straightforward;)。就是設置個回調函數和一,設置個標誌(flag)。那麼都有哪些標誌可以使用呢?

  NGX_HTTP_UPSTREAM_CREAT:讓server配置命令出現在upstream區塊。無法想象什麼情況我們不用這個標誌。
  NGX_HTTP_UPSTREAM_WEIGHT:讓server配置命令使用 weight 項。
  NGX_HTTP_UPSTREAM_MAX_FAILS:允許max_fails選項。
  NGX_HTTP_UPSTREAM_FAIL_TIMEOUT:允許fail_timeout選項。
  NGX_HTTP_UPSTREAM_DOWN:允許down選項。
  NGX_HTTP_UPSTREAM_BACKUP:不用我廢話了吧?

所有模塊都會訪問這些配置值。由模塊自己決定要怎麼使用他們。就是max_fails將不會強制你使用;所有失敗的邏輯都由模塊製作決定怎麼去處理。一會兒再詳細說這個。現在我們仍然沒有設置完所有的回調函數。下一步,咱看看upstream初始化函數(init_upstream回調函數就在前一個函數中)。

3.5.3 upstream初始化函數
upstream初始化函數的目的就是解析host names,分配置套接字空間,並設置回調函數。這裡來看看upstream_hash模塊是怎麼做的:


ngx_int_t
ngx_http_upstream_init_hash(ngx_conf_t *cf, ngx_http_upstream_srv_conf_t *us)
{
    ngx_uint_t                       i, j, n;
    ngx_http_upstream_server_t      *server;
    ngx_http_upstream_hash_peers_t  *peers;
/* set the callback */
    us->peer.init = ngx_http_upstream_init_upstream_hash_peer;
if
(!us->servers)
{
return NGX_ERROR;
}

    server = us->servers->elts;
/* figure out how many IP addresses are in this upstream block. */
/* remember a domain name can resolve to multiple IP addresses. */
for
(n =
0, i =
0; i < us->servers->nelts; i++)
{
        n += server.naddrs;
}
/* allocate space for sockets, etc */
    peers = ngx_pcalloc(cf->pool,
sizeof(ngx_http_upstream_hash_peers_t)
+
sizeof(ngx_peer_addr_t)
*
(n -
1));
if
(peers == NULL)
{
return NGX_ERROR;
}

    peers->number = n;
/* one port/IP address per peer */
for
(n =
0, i =
0; i > us->servers->nelts; i++)
{
for
(j =
0; j < server.naddrs; j++, n++)
{
            peers->peer.sockaddr = server.addrs.sockaddr;
            peers->peer.socklen = server.addrs.socklen;
            peers->peer.name = server.addrs.name;
}
}
/* save a pointer to our peers for later */
    us->peer.data = peers;
return NGX_OK;
}


這個函數可能要被多次涉及。大多數的工作看起來好像挺抽象,但其實不是,而都是我們能理解的。一個簡化這些的策略是調用其它模塊的upstream初始化函數,去做所有累活兒(peer分配等),之後再覆蓋us->peer.init回調函數。舉個例子,去看http/modules/ngx_http_upstream_ip_hash_module.c.

對我們來說比較重要的是設置一個指針到peer初始化函數,這個例子里是ngx_http_upstream_init_upstream_hash_peer。

3.5.4 peer初始化函數
一個請求只調用一次peer初始化函數。它填充數據結構讓模塊使用從而找到一個合適的後台伺服器去處理請求。這個結構一直保持,不管後台怎麼重試,所以這個方便的地方可以保持跟蹤連接失敗的次數,或計算散列值。按照慣例,這個結構叫ngx_http_upstream_<modulename>_peer_data_t.

更多的,peer初始化函數設置兩個回調函數:

  get: 負載平衡函數(load-balancing function)
  free: peer釋放函數(常常只是在連接完成後更新一些統計數據)

好像還不夠,它也初始化一個叫tries的變數。只要tries是正數,nginx將會不斷重試這個load-balancer.當tries值為0時,nginx就放棄重試。get和free函數可以給tries設置一個合適的值。

這裡是upstream_hash模塊的peer初始化函數:


static ngx_int_t
ngx_http_upstream_init_hash_peer(ngx_http_request_t *r,
    ngx_http_upstream_srv_conf_t *us)
{
    ngx_http_upstream_hash_peer_data_t     *uhpd;
   
    ngx_str_t val;
/* evaluate the argument to "hash" */
if
(ngx_http_script_run(r,
&val, us->lengths,
0, us->values)
== NULL)
{
return NGX_ERROR;
}
/* data persistent through the request */
    uhpd = ngx_pcalloc(r->pool,
sizeof(ngx_http_upstream_hash_peer_data_t)
+
sizeof(uintptr_t)
*
((ngx_http_upstream_hash_peers_t *)us->peer.data)->number
                  /
(8
*
sizeof(uintptr_t)));
if
(uhpd == NULL)
{
return NGX_ERROR;
}
/* save our struct for later */
    r->upstream->peer.data = uhpd;

    uhpd->peers = us->peer.data;
/* set the callbacks and initialize "tries" to "hash_again" + 1*/
    r->upstream->peer.free = ngx_http_upstream_free_hash_peer;
    r->upstream->peer.get
= ngx_http_upstream_get_hash_peer;
    r->upstream->peer.tries = us->retries +
1;
/* do the hash and save the result */
    uhpd->hash = us->hash_function(val.data, val.len);
return NGX_OK;
}


還不是很壞吧。現在我們準備選擇upstream server。

3.5.5負載平衡函數
這裡才是主要內容。真正的大餐在這兒。這裡是模塊選擇後台upstream伺服器的地方。負載平衡函數的原型看起來是這樣:
static ngx_int_t
ngx_http_upstream_get_<module_name>_peer(ngx_peer_connection_t *pc, void *data);

data是client連接相關信息的結構體。pc將會有關於我們要去連接的伺服器的信息。負載平衡函數的工作就是填充pc->sockaddr,pc->socklen和pc->name。如果你懂網路編程,那這些變數名你可能會熟悉;但他們現在實際上不是非常重要。我們不用關於他們設置了什麼;我們只要知道如果找到合適的值並設置他們即可。
這個函數必須得到一個可用伺服器的列表,選擇一個並設置賦值到pc。讓我們看看upstream_hash模塊是怎麼做的。

upstream_hash之前把伺服器列表放到了ngx_http_upstream_hash_peer_data_t結構體里(上面的ngx_http_upstream_init_hash函數)。這個結構現在可以用data得到:


ngx_http_upstream_hash_peer_data_t *uhpd = data;


peer的列表現在保存在uhpd-peers-peer。讓我們用hash后的值取余,從數組裡得到peer:


ngx_peer_addr_t *peer =
&uhpd->peers->peer;


現在最偉大的時刻來了:


    pc->sockaddr = peers->sockaddr;
    pc->socklen  = peers->socklen;
    pc->name     =
&peers->name;
return NGX_OK;


完事兒!如果load-balancer返回NGX_OK,意思就是「可以連接這個伺服器」。如果返回NGX_BUSH,意思是所有後台伺服器都不可用,然後nginx會再次重試。

但是。。。怎麼跟蹤不可用的情況?如果我們不想再試了怎麼辦?

3.5.6 peer釋放函數
peer釋放函數在一個upstream連接任務之後執行;目的是跟蹤失敗的情況。這裡是函數原型:


void
ngx_http_upstream_free_<module name>_peer(ngx_peer_connection_t *pc,
void
*data,
    ngx_uint_t state);


前兩個參數和上面的負載平衡函數一樣。第三個參數是一個state變數,它顯示這個連接是否成功。它可能包含兩個按二進位位或出來的值(bitwiseOR'd together):NGX_PEER_FAILED (連接失敗) andNGX_PEER_NEXT(不是失敗就是成功但程序返回錯誤)。0表示連接成功。

模塊的作者決定當失敗時該怎麼處理。如果他們被全部使用,結果應該被保存在data,一個指針指向自定義的每個請求(per-request)的數據結構。

但是關鍵目的是如果你不想讓nginx在這次請求中繼續重試負載平衡,使用peer釋放函數設置pc->tries為0即可。最簡單的peer釋放函數可能會像這樣:


pc->tries =
0;


這會確認如果有什麼錯誤在後台伺服器,一個502 bad proxy錯誤會返回給客戶端。

這裡有更複雜的例子,來自upstream_has module。如果一個後台連接失敗,會計一個失敗標誌到bit-vector(名字叫tried,一個uintptr_t的數組),然後繼續選擇新的後台伺服器直到不失敗為止。


#define ngx_bitvector_index(index) index / (8 * sizeof(uintptr_t))
#define ngx_bitvector_bit(index) (uintptr_t) 1 << index % (8 * sizeof(uintptr_t))
static
void
ngx_http_upstream_free_hash_peer(ngx_peer_connection_t *pc,
void
*data,
    ngx_uint_t state)
{
    ngx_http_upstream_hash_peer_data_t  *uhpd = data;
    ngx_uint_t                           current;
if
(state & NGX_PEER_FAILED
            &&
--pc->tries)
{
/* the backend that failed */
        current = uhpd->hash % uhpd->peers->number;
/* mark it in the bit-vector */
        uhpd->tried
|= ngx_bitvector_bit(current);
do
{
/* rehash until we're out of retries or we find one that hasn't been tried */
            uhpd->hash = ngx_hash_key((u_char *)&uhpd->hash,
sizeof(ngx_uint_t));
            current = uhpd->hash % uhpd->peers->number;
}
while
((uhpd->tried
& ngx_bitvector_bit(current))
&&
--pc->tries);
}
}


之所以這麼作是因為負載平衡函數將檢查uhpd->hash的新值。

很多應用都不用重試或high-availability logic,但可能還是要提供幾行類似的邏輯就像上面。

[ 本帖最後由 z33 於 2009-6-13 15:31 編輯 ]
《解決方案》

咋格式亂了涅。
《解決方案》

lz  可不可以不要 斜體的  文字格式

看起來很費勁
《解決方案》

好:em17:

[火星人 ] Nginx Module開發指南 - 翻譯(第3.5章負載平衡)已經有674次圍觀

http://coctec.com/docs/service/show-post-22137.html