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