Nginx版本:1.9.1
我的博客:[http://blog.csdn.net/zhangskd](http://blog.csdn.net/zhangskd)
?
**算法介紹**
?
我們知道輪詢算法是把請求平均的轉發給各個后端,使它們的負載大致相同。
這有個前提,就是每個請求所占用的后端時間要差不多,如果有些請求占用的時間很長,會導致其所在的后端
負載較高。在這種場景下,把請求轉發給連接數較少的后端,能夠達到更好的負載均衡效果,這就是least_conn算法。
?
least_conn算法很簡單,首選遍歷后端集群,比較每個后端的conns/weight,選取該值最小的后端。
如果有多個后端的conns/weight值同為最小的,那么對它們采用加權輪詢算法。
?
**指令的解析函數**
?
在一個upstream配置塊中,如果有least_conn指令,表示使用least connected負載均衡算法。
least_conn指令的解析函數為ngx_http_upstream_least_conn,主要做了:
指定初始化此upstream塊的函數uscf->peer.init_upstream
指定此upstream塊中server指令支持的屬性
~~~
static char *ngx_http_upstream_least_conn(ngx_conf_t *cf, ngx_command_t *cmd, void *ctx)
{
ngx_http_upstream_srv_conf_t *uscf;
/* 獲取所在的upstream{}塊 */
uscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_upstream_module);
if (uscf->peer.init_upstream)
ngx_conf_log_error(NGX_LOG_WARN, cf, 0, "load balancing method redefined");
/* 此upstream塊的初始化函數 */
uscf->peer.init_upstream = ngx_http_upstream_init_least_conn;
/* 指定此upstream塊中server指令支持的屬性 */
uscf->flags = NGX_HTTP_UPSTREAM_CREATE
| NGX_HTTP_UPSTREAM_WEIGHT
| NGX_HTTP_UPSTREAM_MAX_FAILS
| NGX_HTTP_UPSTREAM_FAIL_TIMEOUT
| NGX_HTTP_UPSTREAM_DOWN
| NGX_HTTP_UPSTREAM_BACKUP;
return NGX_CONF_OK;
}
~~~
以下是upstream塊中server指令可支持的屬性
NGX_HTTP_UPSTREAM_CREATE:檢查是否重復創建,以及必要的參數是否填寫
NGX_HTTP_UPSTREAM_WEIGHT:server指令支持weight屬性
NGX_HTTP_UPSTREAM_MAX_FAILS:server指令支持max_fails屬性
NGX_HTTP_UPSTREAM_FAIL_TIMEOUT:server指令支持fail_timeout屬性
NGX_HTTP_UPSTREAM_DOWN:server指令支持down屬性
NGX_HTTP_UPSTREAM_BACKUP:server指令支持backup屬性
?
**初始化upstream塊**
?
執行完指令的解析函數后,緊接著會調用所有HTTP模塊的init main conf函數。
在執行ngx_http_upstream_module的init main conf函數時,會調用所有upstream塊的初始化函數。
對于使用least_conn的upstream塊,其初始化函數(peer.init_upstream)就是上一步中指定
ngx_http_upstream_init_least_conn,它主要做了:
調用round robin的upstream塊初始化函數來創建和初始化后端集群,保存該upstream塊的數據
指定per request的負載均衡初始化函數peer.init
?
因為臟活累活都讓round robin的upstream塊初始化函數給干了,所以ngx_http_upstream_init_least_conn很簡單。
~~~
static ngx_int_t ngx_http_upstream_init_least_conn(ngx_conf_t *cf, ngx_http_upstream_srv_conf_t *us)
{
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, cf->log, 0, "init least conn");
/* 使用round robin的upstream塊初始化函數,創建和初始化后端集群 */
if (ngx_http_upstream_init_round_robin(cf, us) != NGX_OK)
return NGX_ERROR;
/* 重新設置per request的負載均衡初始化函數 */
us->peer.init = ngx_http_upstream_init_least_conn_peer;
return NGX_OK;
}
~~~
?
**初始化請求的負載均衡數據**?
?
收到一個請求后,一般使用的反向代理模塊(upstream模塊)為ngx_http_proxy_module,
其NGX_HTTP_CONTENT_PHASE階段的處理函數為ngx_http_proxy_handler,在初始化upstream機制的
ngx_http_upstream_init_request函數中,調用在第二步中指定的peer.init,主要用于初始化請求的負載均衡數據。
對于least_conn,peer.init實例為ngx_http_upstream_init_least_conn_peer,主要做了:
調用round robin的peer.init來初始化請求的負載均衡數據
重新指定peer.get,用于從集群中選取一臺后端服務器
?
least_conn的per request負載均衡數據和round robin的完全一樣,都是一個ngx_http_upstream_rr_peer_data_t實例。
~~~
static ngx_int_t ngx_http_upstream_init_least_conn_peer(ngx_http_request_t *r, ngx_http_upstream_srv_conf_t *us)
{
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "init least conn peer");
/* 調用round robin的per request負載均衡初始化函數 */
if (ngx_http_upstream_init_round_robin_peer(r, us) != NGX_OK)
return NGX_ERROR;
/* 指定peer.get,用于從集群中選取一臺后端 */
r->upstream->peer.get = ngx_http_upstream_get_least_conn_peer;
return NGX_OK;
}
~~~
?
**選取一臺后端服務器**
?
一般upstream塊中會有多臺后端,那么對于本次請求,要選定哪一臺后端呢?
這時候第三步中r->upstream->peer.get指向的函數就派上用場了:
采用least connected算法,從集群中選出一臺后端來處理本次請求。 選定后端的地址保存在pc->sockaddr,pc為主動連接。
函數的返回值:
NGX_DONE:選定一個后端,和該后端的連接已經建立。之后會直接發送請求。
NGX_OK:選定一個后端,和該后端的連接尚未建立。之后會和后端建立連接。
NGX_BUSY:所有的后端(包括備份集群)都不可用。之后會給客戶端發送502(Bad Gateway)。
~~~
static ngx_int_t ngx_http_upstream_get_least_conn_peer(ngx_peer_connection_t *pc, void *data)
{
ngx_http_upstream_rr_peer_data_t *rrp = data; /* 請求的負載均衡數據 */
time_t now;
uintptr_t m;
ngx_int_t rc, total;
ngx_uint_t i, n, p, many;
ngx_http_upstream_rr_peer_t *peer, *best;
ngx_http_upstream_rr_peers_t *peers;
...
/* 如果集群只包含一臺后端,那么就不用選了 */
if (rrp->peers->single)
return ngx_http_upstream_get_round_robin_peer(pc, rrp);
pc->cached = 0;
pc->connection = NULL;
now = ngx_time();
peers = rrp->peers; /* 后端集群 */
best = NULL;
total = 0;
...
/* 遍歷后端集群 */
for (peer = peers->peer, i = 0; peer; peer = peer->next, i++)
{
/* 檢查此后端在狀態位圖中對應的位,為1時表示不可用 */
n = i / (8 * sizeof(uintptr_t));
m = (uintptr_t) 1 << i % (8 * sizeof(uintptr_t));
if (rrp->tried[n] & m)
continue;
/* server指令中攜帶了down屬性,表示后端永久不可用 */
if (peer->down)
continue;
/* 在一段時間內,如果此后端服務器的失敗次數,超過了允許的最大值,那么不允許使用此后端了 */
if (peer->max_fails && peer->fails >= peer->max_fails
&& now - peer->checked <= peer->fail_timeout)
continue;
/* select peer with least number of connections; if there are multiple peers
* with the same number of connections, select based on round-robin.
*/
/* 比較各個后端的conns/weight,選取最小者;
* 如果有多個最小者,記錄第一個的序號p,且設置many標志。
*/
if (best == NULL || peer->conns * best->weight < best->conns * peer->weight)
{
best = peer;
many = 0;
p = i;
} else if (peer->conns * best->weight == best->conns * peer->weight)
many = 1;
}
/* 找不到可用的后端 */
if (best == NULL)
goto failed;
/* 如果有多個后端的conns/weight同為最小者,則對它們使用輪詢算法 */
if (many) {
for (peer = best, i = p; peer; peer->peer->next, i++)
{
/* 檢查此后端在狀態位圖中對應的位,為1時表示不可用 */
n = i / (8 * sizeof(uintptr_t));
m = (uintptr_t) 1 << i % (8 * sizeof(uintptr_t));
/* server指令中攜帶了down屬性,表示后端永久不可用 */
if (peer->down)
continue;
/* conns/weight必須為最小的 */
if (peer->conns * best->weight != best->conns * peer->weight)
continue;
/* 在一段時間內,如果此后端服務器的失敗次數,超過了允許的最大值,那么不允許使用此后端了 */
if (peer->max_fails && peer->fails >= peer->max_fails
&& now - peer->checked <= peer->fail_timeout)
continue;
peer->current_weight += peer->effective_weight; /* 對每個后端,增加其當前權重 */
total += peer->effective_weight; /* 累加所有后端的有效權重 */
/* 如果之前此后端發生了失敗,會減小其effective_weight來降低它的權重。
* 此后在選取后端的過程中,又通過增加其effective_weight來恢復它的權重。
*/
if (peer->effective_weight < peer->weight)
peer->effective_weight++;
/* 選取當前權重最大者,作為本次選定的后端 */
if (best == NULL || peer->current_weight > best->current_weight) {
best = peer;
p = i;
}
}
}
best->current_weight -= total; /* 如果使用輪詢,要降低選定后端的當前權重 */
/* 更新checked時間 */
if (now - best->checked > best->fail_timeout)
best->checked = now;
/* 保存選定的后端服務器的地址,之后會向這個地址發起連接 */
pc->sockaddr = best->sockaddr;
pc->socklen = best->socklen;
pc->name = &best->name;
best->conns++; /* 增加選定后端的當前連接數 */
n = p / (8 * sizeof(uintptr_t));
m = (uintptr_t) 1 << p % (8 * sizeof(uintptr_t));
rrp->tried[n] |= m; /* 對于此請求,如果之后需要再次選取后端,不能再選取這個后端了 */
return NGX_OK;
failed:
/* 如果不能從集群中選取一臺后端,那么嘗試備用集群 */
if (peers->next) {
...
rrp->peers = peers->next;
n = (rrp->peers->number + (8 * sizeof(uintptr_t) - 1))
/ (8 * sizeof(uintptr_t));
for (i = 0; i < n; i++)
rrp->tried[i] = 0;
/* 重新調用本函數 */
rc = ngx_http_upstream_get_least_conn_peer(pc, rrp);
if (rc != NGX_BUSY)
return rc;
}
/* all peers failed, mark them as live for quick recovery */
for (peer = peers->peer; peer; peer = peer->next) {
peer->fails = 0;
}
pc->name = peers->name;
return NGX_BUSY;
}
~~~
?
?