Nginx版本:1.9.1
我的博客:[http://blog.csdn.net/zhangskd](http://blog.csdn.net/zhangskd)
**什么是負載均衡**
我們知道單臺服務器的性能是有上限的,當流量很大時,就需要使用多臺服務器來共同提供服務,這就是所謂的集群。
負載均衡服務器,就是用來把經過它的流量,按照某種方法,分配到集群中的各臺服務器上。這樣一來不僅可以承擔
更大的流量、降低服務的延遲,還可以避免單點故障造成服務不可用。一般的反向代理服務器,都具備負載均衡的功能。
負載均衡功能可以由硬件來提供,比如以前的F5設備。也可以由軟件來提供,LVS可以提供四層的負載均衡(利用IP和端口),
Haproxy和Nginx可以提供七層的負載均衡(利用應用層信息)。
?
來看一個最簡單的Nginx負載均衡配置。
~~~
http {
upstream cluster {
server srv1;
server srv2;
server srv3;
}
server {
listen 80;
location / {
proxy_pass http://cluster;
}
}
}
~~~
通過上述配置,Nginx會作為HTTP反向代理,把訪問本機的HTTP請求,均分到后端集群的3臺服務器上。
此時使用的HTTP反向代理模塊是ngx_http_proxy_module。
一般在upstream配置塊中要指明使用的負載均衡算法,比如hash、ip_hash、least_conn。
這里沒有指定,所以使用了默認的HTTP負載均衡算法 - 加權輪詢。
?
**負載均衡流程圖**
?
在描述負載均衡模塊的具體實現前,先來看下它的大致流程:
?

?
**負載均衡模塊**
?
Nginx目前提供的負載均衡模塊:
ngx_http_upstream_round_robin,加權輪詢,可均分請求,是默認的HTTP負載均衡算法,集成在框架中。
ngx_http_upstream_ip_hash_module,IP哈希,可保持會話。
ngx_http_upstream_least_conn_module,最少連接數,可均分連接。
ngx_http_upstream_hash_module,一致性哈希,可減少緩存數據的失效。
?
以上負載均衡模塊的實現,基本上都遵循一套相似的流程。
?
**1. 指令的解析函數**
比如least_conn、ip_hash、hash指令的解析函數。
這些函數在解析配置文件時調用,主要用于:
檢查指令參數的合法性
指定peer.init_upstream函數指針的值,此函數用于初始化upstream塊。
?
**2. 初始化upstream塊**
在執行完指令的解析函數后,緊接著會調用所有HTTP模塊的init main conf函數。
在執行ngx_http_upstream_module的init main conf函數時,會調用所有upstream塊的初始化函數,
即在第一步中指定的peer.init_upstream,主要用于:
創建和初始化后端集群,保存該upstream塊的數據
指定peer.init,此函數用于初始化請求的負載均衡數據
?
來看下ngx_http_upstream_module。
~~~
ngx_http_module_t ngx_http_upstream_module_ctx = {
...
ngx_http_upstream_init_main_conf, /* init main configuration */
...
};
~~~
~~~
static char *ngx_http_upstream_init_main_conf(ngx_conf_t *cf, void *conf)
{
...
/* 數組的元素類型是ngx_http_upstream_srv_conf_t */
for (i = 0; i < umcf->upstreams.nelts; i++) {
/* 如果沒有指定upstream塊的初始化函數,默認使用round robin的 */
init = uscfp[i]->peer.init_upstream ? uscfp[i]->peer.init_upstream :
ngx_http_upstream_init_round_robin;
if (init(cf, uscfp[i] != NGX_OK) {
return NGX_CONF_ERROR;
}
}
...
}
~~~
**3. 初始化請求的負載均衡數據塊**
當收到一個請求后,一般使用的反向代理模塊(upstream模塊)為ngx_http_proxy_module,
其NGX_HTTP_CONTENT_PHASE階段的處理函數為ngx_http_proxy_handler,在初始化upstream機制的
函數ngx_http_upstream_init_request中,調用在第二步中指定的peer.init,主要用于:
創建和初始化該請求的負載均衡數據塊
指定r->upstream->peer.get,用于從集群中選取一臺后端服務器(這是我們最為關心的)
指定r->upstream->peer.free,當不用該后端時,進行數據的更新(不管成功或失敗都調用)
?
請求的負載均衡數據塊中,一般會有一個成員指向對應upstream塊的數據,除此之外還會有自己獨有的成員。
"The peer initialization function is called once per request.
It sets up a data structure that the module will use as it tries to find an appropriate
backend server to service that request; this structure is persistent across backend re-tries,
so it's a convenient place to keep track of the number of connection failures, or a computed
hash value. By convention, this struct is called ngx_http_upstream_<module_name>_peer_data_t."
**4. 選取一臺后端服務器**
一般upstream塊中會有多臺后端,那么對于本次請求,要選定哪一臺后端呢?
這時候第三步中r->upstream->peer.get指向的函數就派上用場了:
采用特定的算法,比如加權輪詢或一致性哈希,從集群中選出一臺后端,處理本次請求。?
選定后端的地址保存在pc->sockaddr,pc為主動連接。
函數的返回值:
NGX_DONE:選定一個后端,和該后端的連接已經建立。之后會直接發送請求。
NGX_OK:選定一個后端,和該后端的連接尚未建立。之后會和后端建立連接。
NGX_BUSY:所有的后端(包括備份集群)都不可用。之后會給客戶端發送502(Bad Gateway)。
?
**5. 釋放一臺后端服務器**
當不再使用一臺后端時,需要進行收尾處理,比如統計失敗的次數。
這時候會調用第三步中r->upstream->peer.get指向的函數。
函數參數state的取值:
0,請求被成功處理
NGX_PEER_FAILED,連接失敗
NGX_PEER_NEXT,連接失敗,或者連接成功但后端未能成功處理請求
?
一個請求允許嘗試的后端數為pc->tries,在第三步中指定。當state為后兩個值時:
如果pc->tries不為0,需要重新選取一個后端,繼續嘗試,此后會重復調用r->upstream->peer.get。
如果pc->tries為0,便不再嘗試,給客戶端返回502錯誤碼(Bad Gateway)。
?
**在upstream模塊的回調**
?
負載均衡模塊的功能是從后端集群中選取一臺后端服務器,而具體的反向代理功能是由upstream模塊實現的,
比如和后端服務器建立連接、向后端服務器發送請求、處理后端服務器的響應等。
我們來看下負載均衡模塊提供的幾個鉤子函數,是在upstream模塊的什么地方回調的。
?
Nginx的HTTP反向代理模塊為ngx_http_proxy_module,其NGX_HTTP_CONTENT_PHASE階段的處理函數為
ngx_http_proxy_handler,每個請求的upstream機制是從這里開始的。
~~~
ngx_http_proxy_handler
ngx_http_upstream_create /* 創建請求的upstream實例 */
ngx_http_upstream_init /* 啟動upstream機制 */
ngx_htp_upstream_init_request /* 負載均衡模塊的入口 */
uscf->peer.init(r, uscf) /* 第三步,初始化請求的負載均衡數據塊 */
...
ngx_http_upstream_connect /* 可能會被ngx_http_upstream_next重復調用 */
ngx_event_connect_peer(&u->peer); /* 連接后端 */
pc->get(pc, pc->data); /* 第四步,從集群中選取一臺后端 */
...
/* 和后端建連成功后 */
c = u->peer.connection;
c->data = r;
c->write->handler = ngx_http_upstream_handler; /* 注冊的連接的讀事件處理函數 */
c->read->handler = ngx_http_upstream_handler; /* 注冊的連接的寫事件處理函數 */
u->write_event_handler = ngx_http_upstream_send_request_handler; /* 寫事件的真正處理函數 */
u->read_event_handler = ngx_http_upstream_process_header; /* 讀事件的真正處理函數 */
~~~
選定后端之后,在和后端通信的過程中如果發生了錯誤,會調用ngx_http_upstream_next來繼續嘗試其它的后端。
~~~
ngx_http_upstream_next
if (u->peer.sockaddr) {
if (ft_type == NGX_HTTP_UPSTREAM_FT_HTTP_403 ||
ft_type == NGX_HTTP_UPSTREAM_FT_HTTP_404)
state = NGX_PEER_NEXT;
else
state = NGX_PEER_FAILED;
/* 第五步,釋放后端服務器 */
u->peer.free(&u->peer, u->peer.data, state);
u->peer.sockaddr = NULL;
}
~~~
?
**Reference**
?
[1]. [http://www.evanmiller.org/nginx-modules-guide.html#proxying](http://www.evanmiller.org/nginx-modules-guide.html#proxying)
[2]. [http://tengine.taobao.org/book/chapter_05.html#id5](http://tengine.taobao.org/book/chapter_05.html#id5)
?