2019獨(dú)角獸企業(yè)重金招聘Python工程師標(biāo)準(zhǔn)>>>
算法介紹
?
ip_hash算法的原理很簡(jiǎn)單,根據(jù)請(qǐng)求所屬的客戶(hù)端IP計(jì)算得到一個(gè)數(shù)值,然后把請(qǐng)求發(fā)往該數(shù)值對(duì)應(yīng)的后端。
所以同一個(gè)客戶(hù)端的請(qǐng)求,都會(huì)發(fā)往同一臺(tái)后端,除非該后端不可用了。ip_hash能夠達(dá)到保持會(huì)話(huà)的效果。
ip_hash是基于round robin的,判斷后端是否可用的方法是一樣的。
?
第一步,根據(jù)客戶(hù)端IP計(jì)算得到一個(gè)數(shù)值。
hash1 = (hash0 * 113 + addr[0]) % 6271;
hash2 = (hash1 * 113 + addr[1]) % 6271;
hash3 = (hash2 * 113 + addr[2]) % 6271;
hash3就是計(jì)算所得的數(shù)值,它只和初始數(shù)值hash0以及客戶(hù)端的IP有關(guān)。
?
第二步,根據(jù)計(jì)算所得數(shù)值,找到對(duì)應(yīng)的后端。
w = hash3 % total_weight;
while (w >= peer->weight) {
??? w -= peer->weight;
??? peer = peer->next;
??? p++;
}
total_weight為所有后端權(quán)重之和。遍歷后端鏈表時(shí),依次減去每個(gè)后端的權(quán)重,直到w小于某個(gè)后端的權(quán)重。
選定的后端在鏈表中的序號(hào)為p。因?yàn)閠otal_weight和每個(gè)后端的weight都是固定的,所以如果hash3值相同,
則找到的后端相同。
?
指令的解析函數(shù)
?
在一個(gè)upstream配置塊中,如果有ip_hash指令,表示使用ip_hash負(fù)載均衡算法。
ip_hash指令的解析函數(shù)為ngx_http_upstream_ip_hash,主要做了:
指定初始化此upstream塊的函數(shù)peer.init_upstream
指定此upstream塊中server指令支持的屬性
[java] view plain copy
static?char?*ngx_http_upstream_ip_hash?(ngx_conf_t?*cf,?ngx_command_t?*cmd,?void?*conf)?? {?? ????ngx_http_upstream_srv_conf_t?*uscf;?? ?? ????/*?獲取對(duì)應(yīng)的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塊的函數(shù)?*/?? ????uscf->peer.init_upstream?=?ngx_http_upstream_init_ip_hash;?? ?? ????/*?指定此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;?? ?? ????return?NGX_CONF_OK;?? }?? 以下是upstream塊中server指令可支持的屬性
NGX_HTTP_UPSTREAM_CREATE:檢查是否重復(fù)創(chuàng)建,以及必要的參數(shù)是否填寫(xiě)
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塊
?
執(zhí)行完指令的解析函數(shù)后,緊接著會(huì)調(diào)用所有HTTP模塊的init main conf函數(shù)。
在執(zhí)行ngx_http_upstream_module的init main conf函數(shù)時(shí),會(huì)調(diào)用所有upstream塊的初始化函數(shù)。
對(duì)于使用ip_hash的upstream塊,其初始化函數(shù)(peer.init_upstream)就是上一步中指定的
ngx_http_upstream_init_ip_hash。它主要做了:
調(diào)用默認(rèn)的初始化函數(shù)ngx_http_upstream_init_round_robin來(lái)創(chuàng)建和初始化后端集群,保存該upstream塊的數(shù)據(jù)
指定初始化請(qǐng)求的負(fù)載均衡數(shù)據(jù)的函數(shù)peer.init
?
因?yàn)榕K活累活都讓默認(rèn)的函數(shù)給干了,所以ngx_http_upstream_init_ip_hash的代碼就幾行:)
[java] view plain copy
static?ngx_int_t?ngx_http_upstream_init_ip_hash?(ngx_conf_t?*cf,?ngx_http_upstream_srv_conf_t?*us)?? {?? ????if?(ngx_http_upstream_init_round_robin(cf,?us)?!=?NGX_OK)?? ????????return?NGX_ERROR;?? ?? ????us->peer.init?=?ngx_http_upstream_init_ip_hash_peer;?/*?初始化請(qǐng)求負(fù)載均衡數(shù)據(jù)的函數(shù)?*/?? ????return?NGX_OK;?? }?? ?
初始化請(qǐng)求的負(fù)載均衡數(shù)據(jù)?
?
收到一個(gè)請(qǐng)求后,一般使用的反向代理模塊(upstream模塊)為ngx_http_proxy_module,
其N(xiāo)GX_HTTP_CONTENT_PHASE階段的處理函數(shù)為ngx_http_proxy_handler,在初始化upstream機(jī)制的
ngx_http_upstream_init_request函數(shù)中,調(diào)用在第二步中指定的peer.init,主要用于初始化請(qǐng)求的負(fù)載均衡數(shù)據(jù)。
對(duì)于ip_hash,peer.init實(shí)例為ngx_http_upstream_init_ip_hash_peer,主要做了:
調(diào)用round robin的per request負(fù)載均衡初始化函數(shù),創(chuàng)建和初始化其per request負(fù)載均衡數(shù)據(jù),即iphp->rrp。
重新指定peer.get,用于從集群中選取一臺(tái)后端服務(wù)器。
保存客戶(hù)端的地址,初始化ip_hash的per request負(fù)載均衡數(shù)據(jù)。
?
ip_hash的per request負(fù)載均衡數(shù)據(jù)的結(jié)構(gòu)體為ngx_http_upstream_ip_hash_peer_data_t。
[java] view plain copy
typedef?struct?{?? ????ngx_http_upstream_rr_peer_data_t?rrp;?/*?round?robin的per?request負(fù)載均衡數(shù)據(jù)?*/?? ????ngx_uint_t?hash;?/*?根據(jù)客戶(hù)端IP計(jì)算所得的hash值?*/?? ????u_char?addrlen;?/*?使用客戶(hù)端IP的后三個(gè)字節(jié)來(lái)計(jì)算hash值?*/?? ????u_char?*addr;?/*?客戶(hù)端的IP?*/?? ????u_char?tries;?/*?已經(jīng)嘗試了多少次?*/?? ????ngx_event_get_peer_pt?get_rr_peer;?/*?round?robin算法的peer.get函數(shù)?*/?? }?ngx_http_upstream_ip_hash_peer_data_t;?? [java] view plain copy
static?ngx_int_t?ngx_http_upstream_init_ip_hash_peer?(ngx_http_request_t?*r,?ngx_http_upstream_srv_conf_t?*us)?? {?? ????struct?sockaddr_in?*sin;?? ????...?? ????ngx_http_upstream_ip_hash_peer_data_t?*iphp;?? ?? ????/*?創(chuàng)建ip_hash的per?request負(fù)載均衡數(shù)據(jù)的實(shí)例?*/?? ????iphp?=?ngx_palloc(r->pool,?sizeof(ngx_http_upstream_ip_hash_peer_data_t));?? ????if?(iphp?==?NULL)?? ????????return?NGX_ERROR;?? ??? ???/*?首先調(diào)用round?robin的per?request負(fù)載均衡數(shù)據(jù)的初始化函數(shù),? ????*?創(chuàng)建和初始化round?robin的per?request負(fù)載均衡數(shù)據(jù)實(shí)例,即iphp->rrp。? ????*/?? ???r->upstream->peer.data?=?&iphp->rrp;??? ???if?(ngx_http_upstream_init_round_robin_peer(r,?us)?!=?NGX_OK)?? ????????return?NGX_ERROR:?? ??? ????/*?重新指定peer.get,用于從集群中選取一臺(tái)后端服務(wù)器?*/?? ????r->upstream->peer.get?=?ngx_http_upstream_get_ip_hash_peer;?? ?? ????/*?客戶(hù)端的地址類(lèi)型?*/?? ????switch(r->connection->sockaddr->sa_family)?{?? ????case?AF_INET:?? ????????sin?=?(struct?sockaddr_in?*)?r->connection->sockaddr;?? ????????iphp->addr?=?(u_char?*)?&sin->sin_addr.s_addr;?/*?客戶(hù)端的IP?*/?? ????????iphp->addrlen?=?3;?/*?使用客戶(hù)端IP的后三個(gè)字節(jié)來(lái)計(jì)算hash值?*/?? ????????break;?? ?? #if?(NGX_HAVE_INET6)?? ????...?? #endif?? ?? ????default:?? ????????iphp->addr?=?ngx_http_upstream_ip_hash_pseudo_addr;?? ????????iphp->addrlen?=?3;?? ????}?? ?? ????iphp->hash?=?89;?? ????iphp->tries?=?0;?? ????iphp->get_rr_peer?=?ngx_http_upstream_get_round_robin_peer;?/*?保存round?robin的peer.get函數(shù)?*/?? }?? ?
選取一臺(tái)后端服務(wù)器
?
一般upstream塊中會(huì)有多臺(tái)后端,那么對(duì)于本次請(qǐng)求,要選定哪一臺(tái)后端呢?
這時(shí)候第三步中r->upstream->peer.get指向的函數(shù)就派上用場(chǎng)了:
采用ip_hash算法,從集群中選出一臺(tái)后端來(lái)處理本次請(qǐng)求。 選定后端的地址保存在pc->sockaddr,pc為主動(dòng)連接。
函數(shù)的返回值:
NGX_DONE:選定一個(gè)后端,和該后端的連接已經(jīng)建立。之后會(huì)直接發(fā)送請(qǐng)求。
NGX_OK:選定一個(gè)后端,和該后端的連接尚未建立。之后會(huì)和后端建立連接。
NGX_BUSY:所有的后端(包括備份集群)都不可用。之后會(huì)給客戶(hù)端發(fā)送502(Bad Gateway)。
[java] view plain copy
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;?/*?請(qǐng)求的負(fù)載均衡數(shù)據(jù)?*/?? ????time_t?now;?? ????ngx_int_t?w;?? ????uintptr_t?m;?? ????ngx_uint_t?i,?n,?p,?hash;?? ????ngx_http_upstream_rr_peer_t?*peer;?? ????...?? ????/*?如果只有一臺(tái)后端,或者嘗試次數(shù)超過(guò)20次,則使用輪詢(xún)的方式來(lái)選取后端?*/?? ????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;?/*?本次選取的初始hash值?*/?? ?? ????for?(?;?;?)?{?? ????????/*?根據(jù)客戶(hù)端IP、本次選取的初始hash值,計(jì)算得到本次最終的hash值?*/?? ????????for?(i?=?0;?i?<?(ngx_uint_t)?iphp->addrlen;?i++)?? ????????????hash?=?(hash?*?113?+?iphp->addr[i])?%?6271;?? ?? ????????/*?total_weight和weight都是固定值?*/?? ????????w?=?hash?%?iphp->rrp.peers->total_weight;?? ????????peer?=?iphp->rrp.peers->peer;?/*?第一臺(tái)后端?*/?? ????????p?=?0;?? ?? ????????while?(w?>=?peer->weight)?{?? ????????????w?-=?peer->weight;?? ????????????peer?=?peer->next;?? ????????????p++;?? ????????}?? ?? ????????/*?檢查第此后端在狀態(tài)位圖中對(duì)應(yīng)的位,為1時(shí)表示不可用?*/?? ????????n?=?p?/?(8?*?sizeof(uintptr_t));?? ????????m?=?(uintptr_t)?1?<<?p?%?(8?*?sizeof(uintptr_t));?? ?? ????????if?(iphp->rrp.tried[n]?&?m)?? ????????????goto?next;?? ?? ????????/*?檢查后端是否永久不可用?*/?? ????????if?(peer->down)?? ????????????goto?next;?? ?? ????????/*?在一段時(shí)間內(nèi),如果此后端服務(wù)器的失敗次數(shù),超過(guò)了允許的最大值,那么不允許使用此后端了?*/?? ????????if?(peer->max_fails?&&?peer->fails?>=?peer->max_fails?&&?? ????????????now?-?peer->checked?<=?peer->fail_timeout)?? ????????????goto?next;?? ?? ????????break;?? ?? ????next:?? ????????/*?增加已嘗試的次數(shù),如果超過(guò)20次,則使用輪詢(xún)的方式來(lái)選取后端?*/?? ????????if?(++iphp->tries?>?20)?? ????????????return?iphp->get_rr_peer(pc,?&iphp->rrp);?? ????}?? ?? ????iphp->rrp.current?=?peer;?/*?選定的可用后端?*/?? ?? ????/*?保存選定的后端服務(wù)器的地址,之后會(huì)向這個(gè)地址發(fā)起連接?*/?? ????pc->sockaddr?=?peer->sockaddr;?? ????pc->socklen?=?peer->socklen;?? ????pc->name?=?&peer->name;?? ?? ????peer->conns++;?? ?? ????/*?更新checked時(shí)間?*/?? ????if?(now?-?peer->checked?>?peer->fail_timeout)?? ????????peer->checked?=?now;?? ?? ????iphp->rrp.tried[n]?|=?m;?/*?對(duì)于此請(qǐng)求,如果之后需要再次選取后端,不能再選取這個(gè)后端了?*/?? ????iphp->hash?=?hash;?/*?保存hash值,下次可能還會(huì)用到?*/?? ?? ????return?NGX_OK:?? }??
轉(zhuǎn)載于:https://my.oschina.net/zhangjie830621/blog/653088
《新程序員》:云原生和全面數(shù)字化實(shí)踐50位技術(shù)專(zhuān)家共同創(chuàng)作,文字、視頻、音頻交互閱讀
總結(jié)
以上是生活随笔為你收集整理的Nginx的负载均衡 - 保持会话 (ip_hash)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。