負(fù)載均衡模塊用于從upstream
指令定義的后端主機(jī)列表中選取一臺(tái)主機(jī)。Nginx 先使用負(fù)載均衡模塊找到一臺(tái)主機(jī),再使用 upstream 模塊實(shí)現(xiàn)與這臺(tái)主機(jī)的交互。為了方便介紹負(fù)載均衡模塊,做到言之有物,以下選取 Nginx 內(nèi)置的 ip hash 模塊作為實(shí)際例子進(jìn)行分析。
要了解負(fù)載均衡模塊的開發(fā)方法,首先需要了解負(fù)載均衡模塊的使用方法。因?yàn)樨?fù)載均衡模塊與之前書中提到的模塊差別比較大,所以我們從配置入手比較容易理解。
在配置文件中,我們?nèi)绻枰褂?ip hash 的負(fù)載均衡算法。我們需要寫一個(gè)類似下面的配置:
upstream test {
ip_hash;
server 192.168.0.1;
server 192.168.0.2;
}
從配置我們可以看出負(fù)載均衡模塊的使用場(chǎng)景:
ip_hash
只能在 upstream {}中使用。這條指令用于通知 Nginx 使用 ip hash 負(fù)載均衡算法。如果沒加這條指令,Nginx 會(huì)使用默認(rèn)的 round robin 負(fù)載均衡模塊。請(qǐng)各位讀者對(duì)比 handler 模塊的配置,是不是有共同點(diǎn)?server
指令前,可能出現(xiàn)在server
指令后,也可能出現(xiàn)在兩條server
指令之間。各位讀者可能會(huì)有疑問,有什么差別么?那么請(qǐng)各位讀者嘗試下面這個(gè)配置: upstream test {
server 192.168.0.1 weight=5;
ip_hash;
server 192.168.0.2 weight=7;
}
神奇的事情出現(xiàn)了:
nginx: [emerg] invalid parameter "weight=7" in nginx.conf:103
configuration file nginx.conf test failed
可見 ip_hash 指令的確能影響到配置的解析。
配置決定指令系統(tǒng),現(xiàn)在就來看 ip_hash 的指令定義:
static ngx_command_t ngx_http_upstream_ip_hash_commands[] = {
{ ngx_string("ip_hash"),
NGX_HTTP_UPS_CONF|NGX_CONF_NOARGS,
ngx_http_upstream_ip_hash,
0,
0,
NULL },
ngx_null_command
};
沒有特別的東西,除了指令屬性是 NGX_HTTP_UPS_CONF。這個(gè)屬性表示該指令的適用范圍是 upstream{}。
以從前面的章節(jié)得到的經(jīng)驗(yàn),大家應(yīng)該知道這里就是模塊的切入點(diǎn)了。負(fù)載均衡模塊的鉤子代碼都是有規(guī)律的,這里通過 ip_hash 模塊來分析這個(gè)規(guī)律。
static char *
ngx_http_upstream_ip_hash(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_http_upstream_srv_conf_t *uscf;
uscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_upstream_module);
uscf->peer.init_upstream = ngx_http_upstream_init_ip_hash;
uscf->flags = NGX_HTTP_UPSTREAM_CREATE
|NGX_HTTP_UPSTREAM_MAX_FAILS
|NGX_HTTP_UPSTREAM_FAIL_TIMEOUT
|NGX_HTTP_UPSTREAM_DOWN;
return NGX_CONF_OK;
}
這段代碼中有兩點(diǎn)值得我們注意。一個(gè)是 uscf->flags 的設(shè)置,另一個(gè)是設(shè)置 init_upstream 回調(diào)。
NGX_HTTP_UPSTREAM_CREATE:創(chuàng)建標(biāo)志,如果含有創(chuàng)建標(biāo)志的話,Nginx 會(huì)檢查重復(fù)創(chuàng)建,以及必要參數(shù)是否填寫;
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_WEIGHT:可以在 server 中使用 weight 屬性;
聰明的讀者如果聯(lián)想到剛剛遇到的那個(gè)神奇的配置錯(cuò)誤,可以得出一個(gè)結(jié)論:在負(fù)載均衡模塊的指令處理函數(shù)中可以設(shè)置并修改 upstream{} 中server
指令支持的屬性。這是一個(gè)很重要的性質(zhì),因?yàn)椴煌呢?fù)載均衡模塊對(duì)各種屬性的支持情況都是不一樣的,那么就需要在解析配置文件的時(shí)候檢測(cè)出是否使用了不支持的負(fù)載均衡屬性并給出錯(cuò)誤提示,這對(duì)于提升系統(tǒng)維護(hù)性是很有意義的。但是,這種機(jī)制也存在缺陷,正如前面的例子所示,沒有機(jī)制能夠追加檢查在更新支持屬性之前已經(jīng)配置了不支持屬性的server
指令。
Nginx 初始化 upstream 時(shí),會(huì)在 ngx_http_upstream_init_main_conf 函數(shù)中調(diào)用設(shè)置的回調(diào)函數(shù)初始化負(fù)載均衡模塊。這里不太好理解的是 uscf 的具體位置。通過下面的示意圖,說明 upstream 負(fù)載均衡模塊的配置的內(nèi)存布局。
從圖上可以看出,MAIN_CONF 中 ngx_upstream_module 模塊的配置項(xiàng)中有一個(gè)指針數(shù)組 upstreams,數(shù)組中的每個(gè)元素對(duì)應(yīng)就是配置文件中每一個(gè) upstream{}的信息。更具體的將會(huì)在后面的原理篇討論。
init_upstream 回調(diào)函數(shù)執(zhí)行時(shí)需要初始化負(fù)載均衡模塊的配置,還要設(shè)置一個(gè)新鉤子,這個(gè)鉤子函數(shù)會(huì)在 Nginx 處理每個(gè)請(qǐng)求時(shí)作為初始化函數(shù)調(diào)用,關(guān)于這個(gè)新鉤子函數(shù)的功能,后面會(huì)有詳細(xì)的描述。這里,我們先分析 IP hash 模塊初始化配置的代碼:
ngx_http_upstream_init_round_robin(cf, us);
us->peer.init = ngx_http_upstream_init_ip_hash_peer;
這段代碼非常簡(jiǎn)單:IP hash 模塊首先調(diào)用另一個(gè)負(fù)載均衡模塊 Round Robin 的初始化函數(shù),然后再設(shè)置自己的處理請(qǐng)求階段初始化鉤子。實(shí)際上幾個(gè)負(fù)載均衡模塊可以組成一條鏈表,每次都是從鏈?zhǔn)椎哪K開始進(jìn)行處理。如果模塊決定不處理,可以將處理權(quán)交給鏈表中的下一個(gè)模塊。這里,IP hash 模塊指定 Round Robin 模塊作為自己的后繼負(fù)載均衡模塊,所以在自己的初始化配置函數(shù)中也對(duì) Round Robin 模塊進(jìn)行初始化。
Nginx 收到一個(gè)請(qǐng)求以后,如果發(fā)現(xiàn)需要訪問 upstream,就會(huì)執(zhí)行對(duì)應(yīng)的 peer.init 函數(shù)。這是在初始化配置時(shí)設(shè)置的回調(diào)函數(shù)。這個(gè)函數(shù)最重要的作用是構(gòu)造一張表,當(dāng)前請(qǐng)求可以使用的 upstream 服務(wù)器被依次添加到這張表中。之所以需要這張表,最重要的原因是如果 upstream 服務(wù)器出現(xiàn)異常,不能提供服務(wù)時(shí),可以從這張表中取得其他服務(wù)器進(jìn)行重試操作。此外,這張表也可以用于負(fù)載均衡的計(jì)算。之所以構(gòu)造這張表的行為放在這里而不是在前面初始化配置的階段,是因?yàn)閡pstream需要為每一個(gè)請(qǐng)求提供獨(dú)立隔離的環(huán)境。
為了討論 peer.init 的核心,我們還是看 IP hash 模塊的實(shí)現(xiàn):
r->upstream->peer.data = &iphp->rrp;
ngx_http_upstream_init_round_robin_peer(r, us);
r->upstream->peer.get = ngx_http_upstream_get_ip_hash_peer;
第一行是設(shè)置數(shù)據(jù)指針,這個(gè)指針就是指向前面提到的那張表;
第二行是調(diào)用 Round Robin 模塊的回調(diào)函數(shù)對(duì)該模塊進(jìn)行請(qǐng)求初始化。面前已經(jīng)提到,一個(gè)負(fù)載均衡模塊可以調(diào)用其他負(fù)載均衡模塊以提供功能的補(bǔ)充。
第三行是設(shè)置一個(gè)新的回調(diào)函數(shù)get。該函數(shù)負(fù)責(zé)從表中取出某個(gè)服務(wù)器。除了 get 回調(diào)函數(shù),還有另一個(gè)r->upstream->peer.free
的回調(diào)函數(shù)。該函數(shù)在 upstream 請(qǐng)求完成后調(diào)用,負(fù)責(zé)做一些善后工作。比如我們需要維護(hù)一個(gè) upstream 服務(wù)器訪問計(jì)數(shù)器,那么可以在 get 函數(shù)中對(duì)其加 1,在 free 中對(duì)其減 1。如果是 SSL 的話,Nginx 還提供兩個(gè)回調(diào)函數(shù) peer.set_session 和 peer.save_session。一般來說,有兩個(gè)切入點(diǎn)實(shí)現(xiàn)負(fù)載均衡算法,其一是在這里,其二是在 get 回調(diào)函數(shù)中。
這兩個(gè)函數(shù)是負(fù)載均衡模塊最底層的函數(shù),負(fù)責(zé)實(shí)際獲取一個(gè)連接和回收一個(gè)連接的預(yù)備操作。之所以說是預(yù)備操作,是因?yàn)樵谶@兩個(gè)函數(shù)中,并不實(shí)際進(jìn)行建立連接或者釋放連接的動(dòng)作,而只是執(zhí)行獲取連接的地址或維護(hù)連接狀態(tài)的操作。需要理解的清楚一點(diǎn),在 peer.get 函數(shù)中獲取連接的地址信息,并不代表這時(shí)連接一定沒有被建立,相反的,通過 get 函數(shù)的返回值,Nginx 可以了解是否存在可用連接,連接是否已經(jīng)建立。這些返回值總結(jié)如下:
返回值 | 說明 | Nginx 后續(xù)動(dòng)作 |
---|---|---|
NGX_DONE | 得到了連接地址信息,并且連接已經(jīng)建立。 | 直接使用連接,發(fā)送數(shù)據(jù)。 |
NGX_OK | 得到了連接地址信息,但連接并未建立。 | 建立連接,如連接不能立即建立,設(shè)置事件, |
暫停執(zhí)行本請(qǐng)求,執(zhí)行別的請(qǐng)求。 | ||
NGX_BUSY | 所有連接均不可用。 | 返回502錯(cuò)誤至客戶端。 |
各位讀者看到上面這張表,可能會(huì)有幾個(gè)問題浮現(xiàn)出來:
Q: 什么時(shí)候連接是已經(jīng)建立的?
A: 使用后端 keepalive 連接的時(shí)候,連接在使用完以后并不關(guān)閉,而是存放在一個(gè)隊(duì)列中,新的請(qǐng)求只需要從隊(duì)列中取出連接,這些連接都是已經(jīng)準(zhǔn)備好的。
Q: 什么叫所有連接均不可用?
A: 初始化請(qǐng)求的過程中,建立了一張表,get 函數(shù)負(fù)責(zé)每次從這張表中不重復(fù)的取出一個(gè)連接,當(dāng)無法從表中取得一個(gè)新的連接時(shí),即所有連接均不可用。
Q: 對(duì)于一個(gè)請(qǐng)求,peer.get 函數(shù)可能被調(diào)用多次么?
A: 正式如此。當(dāng)某次 peer.get 函數(shù)得到的連接地址連接不上,或者請(qǐng)求對(duì)應(yīng)的服務(wù)器得到異常響應(yīng),Nginx 會(huì)執(zhí)行 ngx_http_upstream_next,然后可能再次調(diào)用 peer.get 函數(shù)嘗試別的連接。upstream 整體流程如下:
這一節(jié)介紹了負(fù)載均衡模塊的基本組成。負(fù)載均衡模塊的配置區(qū)集中在 upstream{}塊中。負(fù)載均衡模塊的回調(diào)函數(shù)體系是以 init_upstream 為起點(diǎn),經(jīng)歷 init_peer,最終到達(dá) peer.get 和 peer.free。其中 init_peer 負(fù)責(zé)建立每個(gè)請(qǐng)求使用的 server 列表,peer.get 負(fù)責(zé)從 server 列表中選擇某個(gè) server(一般是不重復(fù)選擇),而 peer.free 負(fù)責(zé) server 釋放前的資源釋放工作。最后,這一節(jié)通過一張圖將 upstream 模塊和負(fù)載均衡模塊在請(qǐng)求處理過程中的相互關(guān)系展現(xiàn)出來。
更多建議: