負載均衡模塊簡介
回調指針 |
函數功能 |
round_robin模塊 |
IP_hash模塊 |
uscf->peer.init_upstream |
解析配置文件過程中調用,根據upstream里各個server配置項做初始准備工作,另外的核心工作是設置回調指針us->peer.init。配置文件解析完后不再被調用 |
ngx_http_upstream_init_round_robin 設置:us->peer.init = ngx_http_upstream_init_round_robin_peer; |
ngx_http_upstream_init_ip_hash 設置:us->peer.init = ngx_http_upstream_init_ip_hash_peer; |
us->peer.init |
在每一次Nginx准備轉發客戶端請求到后端服務器前都會調用該函數。該函數為本次轉發選擇合適的后端服務器做初始准備工作,另外的核心工作是設置回調指針r->upstream->peer.get和r->upstream->peer.free等 |
ngx_http_upstream_init_round_robin_peer 設置:r->upstream->peer.get = ngx_http_upstream_get_round_robin_peer; r->upstream->peer.free = ngx_http_upstream_free_round_robin_peer; |
ngx_http_upstream_init_ip_hash_peer 設置:r->upstream->peer.get = ngx_http_upstream_get_ip_hash_peer; r->upstream->peer.free為空 |
r->upstream->peer.get |
在每一次Nginx准備轉發客戶端請求到后端服務器前都會調用該函數。該函數實現具體的位本次轉發選擇合適的后端服務器的算法邏輯,即完成選擇獲取合適后端服務器的功能 |
ngx_http_upstream_get_round_robin_peer 加權選擇當前權值最高的后端服務器 |
ngx_http_upstream_get_ip_hash_peer 根據IP哈希值選擇后端服務器 |
r->upstream->peer.free |
在每一次Nginx完成與后端服務器之間的交互后都會調用該函數。 |
ngx_http_upstream_free_round_robin_peer 更新相關數值,比如rrp->current |
空 |
負載均衡配置指令
upstream backend { server backend1.example.com weight=5; server backend2.example.com:8080; server unix:/tmp/backend3; server backup1.example.com:8080 backup; server backup2.example.com:8080 backup; } server { location / { proxy_pass http://backend; } }
upstream backend { server backend1.example.com weight=5; server 127.0.0.1:8080 max_fails=3 fail_timeout=30s; server unix:/tmp/backend3; }
upstream backend { server backend1.example.com weight=5; server 127.0.0.1:8080 max_fails=3 fail_timeout=30s; server unix:/tmp/backend3; server backup1.example.com:8080 backup; }
upstream backend { ip_hash; server backend1.example.com; server backend2.example.com; server backend3.example.com down; server backend4.example.com; }
upstream memcached_backend { server 127.0.0.1:11211; server 10.0.0.2:11211; keepalive 32; } server { ... location /memcached/ { set $memcached_key $uri; memcached_pass memcached_backend; } }
負載均衡策略
nginx的負載均衡策略可以划分為兩大類:內置策略和擴展策略。內置策略包含加權輪詢和ip hash,在默認情況下這兩種策略會編譯進nginx內核,只需在nginx配置中指明參數即可。擴展策略有很多,如fair、通用hash、consistent hash等,默認不編譯進nginx內核,是第三方模塊。
nginx 的 upstream目前支持 4 種方式的分配 :
1)輪詢(默認)
每個請求按時間順序逐一分配到不同的后端服務器,如果后端服務器down掉,能自動剔除。
2)weight
指定輪詢幾率,weight和訪問比率成正比,用於后端服務器性能不均的情況。
2)ip_hash
每個請求按訪問ip的hash結果分配,這樣每個訪客固定訪問一個后端服務器,可以解決session的問題。
3)fair(第三方)
按后端服務器的響應時間來分配請求,響應時間短的優先分配。
4)url_hash(第三方)
Nginx默認采用round_robin加權算法。如果要選擇其他的負載均衡算法,必須在upstream的配置上下文中通過配置指令ip_hash明確指定(該配置項最好放在其他server指令等的前面,以便檢查server的配置選項是否合理)。比如采用Ip_hash的upstream配置如下所示:
upstream load_balance{ ip_hash; server localhost:8001; server localhost:8002; }當整個http配置塊被Nginx解析完畢之后,會調用各個http模塊對應的初始函數。對於模塊ngx_http_upstream_module而言,對應的main配置初始函數是ngx_http_upstream_init_main_conf(),在這個函數中有這樣一段代碼:
for (i = 0; i < umcf->upstreams.nelts; i++) { 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; } }
加權輪詢策略
全局准備工作
typedef struct { ngx_addr_t *addrs;//指向存儲IP地址的數組的指針,host信息(對應的是 ngx_url_t->addrs ) ngx_uint_t naddrs;//與第一個參數配合使用,數組元素個數(對應的是 ngx_url_t->naddrs ) ngx_uint_t weight; ngx_uint_t max_fails; time_t fail_timeout; unsigned down:1; unsigned backup:1; } ngx_http_upstream_server_t;
us->peer.init = ngx_http_upstream_init_round_robin_peer;
us類型是ngx_http_upstream_srv_conf_t:
typedef struct ngx_http_upstream_srv_conf_s ngx_http_upstream_srv_conf_t; struct ngx_http_upstream_srv_conf_s { ngx_http_upstream_peer_t peer; void **srv_conf;//在 ngx_http_upstream()函數中被設置,指向的是本層的srv_conf ngx_array_t *servers; /*array of ngx_http_upstream_server_t */ ngx_uint_t flags;//調用函數時ngx_http_upstream_add() 指定的標記 ngx_str_t host;//在函數 ngx_http_upstream_add() 中設置(e.g. upstream backend中的backend) u_char *file_name;//"/usr/local/nginx/conf/nginx.conf" ngx_uint_t line;//proxy在配置文件中的行號 in_port_t port;//使用的端口號(ngx_http_upstream_add()函數中添加, 指向ngx_url_t-->port,通常在函數ngx_parse_inet_url()中解析) in_port_t default_port;//默認使用的端口號(ngx_http_upstream_add()函數中添加, 指向ngx_url_t-->default_port) ngx_uint_t no_port; /* unsigned no_port:1 */ };
typedef struct { //使用負載均衡的類型,默認采用 ngx_http_upstream_init_round_robin() ngx_http_upstream_init_pt init_upstream; //使用的負載均衡類型的初始化函數 ngx_http_upstream_init_peer_pt init; //us->peer.data = peers; 指向的是 ngx_http_upstream_rr_peers_t(函數 ngx_http_upstream_init_round_robin()中設置) void *data; } ngx_http_upstream_peer_t;
ngx_http_upstream_init_peer_pt 是函數指針類型:
typedef ngx_int_t (*ngx_http_upstream_init_peer_pt)(ngx_http_request_t *r, ngx_http_upstream_srv_conf_t *us);
typedef struct { ngx_str_t url; //保存IP地址+端口信息(e.g. 192.168.124.129:8011 或 money.163.com) ngx_str_t host; //保存IP地址信息 ngx_str_t port_text; //保存port字符串 ngx_str_t uri; //uri部分,在函數ngx_parse_inet_url()中設置 in_port_t port; //端口,e.g. listen指令中指定的端口(listen 192.168.124.129:8011) in_port_t default_port; //默認端口(當no_port字段為真時,將默認端口賦值給port字段, 默認端口通常是80) int family; //address family, AF_xxx unsigned listen:1; //是否為指監聽類的設置 unsigned uri_part:1; unsigned no_resolve:1; //根據情況決定是否解析域名(將域名解析到IP地址) unsigned one_addr:1; //等於1時,僅有一個IP地址 unsigned no_port:1; //標識url中沒有顯示指定端口(為1時沒有指定) unsigned wildcard:1; //標識是否使用通配符(e.g. listen *:8000;) socklen_t socklen; //sizeof(struct sockaddr_in) u_char sockaddr[NGX_SOCKADDRLEN]; //sockaddr_in結構指向它 ngx_addr_t *addrs; //數組大小是naddrs字段;每個元素對應域名的IP地址信息(struct sockaddr_in),在函數中賦值(ngx_inet_resolve_host()) ngx_uint_t naddrs; //url對應的IP地址個數,IP格式的地址將默認為1 char *err; //錯誤信息字符串 } ngx_url_t;
此函數會創建后端服務器列表,並且將非后備服務器與后備服務器分開進行各自單獨的鏈表。每一個后端服務器用一個結構體ngx_http_upstream_rr_peer_t與之對應(ngx_http_upstream_round_robin.h):
typedef struct { struct sockaddr *sockaddr;//后端服務器地址 socklen_t socklen;//后端服務器地址長度 ngx_str_t name;//后端名稱 ngx_int_t current_weight;//當前權重,nginx會在運行過程中調整此權重 ngx_int_t effective_weight; ngx_int_t weight;//配置的權重 ngx_uint_t fails;//已嘗試失敗次數 time_t accessed;//檢測失敗時間,用於計算超時 time_t checked; ngx_uint_t max_fails;//最大失敗次數 time_t fail_timeout;//多長時間內出現max_fails次失敗便認為后端down掉了 ngx_uint_t down; /* unsigned down:1; *///指定某后端是否掛了 #if (NGX_HTTP_SSL) ngx_ssl_session_t *ssl_session; /* local to a process */ #endif } ngx_http_upstream_rr_peer_t;
列表最前面需要帶有一些head信息,用結構體ngx_http_upstream_rr_peers_t與之對應:
typedef struct ngx_http_upstream_rr_peers_s ngx_http_upstream_rr_peers_t; struct ngx_http_upstream_rr_peers_s { ngx_uint_t number;//隊列中服務器數量 /* ngx_mutex_t *mutex; */ ngx_uint_t total_weight;//所有服務器總權重 unsigned single:1;//為1表示后端服務器總共只有一台,用於優化,此時不需要再做選擇 unsigned weighted:1;//為1表示總的權重值等於服務器數量 ngx_str_t *name; ngx_http_upstream_rr_peers_t *next;//后備服務器列表掛載在這個字段下 ngx_http_upstream_rr_peer_t peer[1]; };
//函數:初始化服務器負載均衡表 //參數: //us:ngx_http_upstream_main_conf_t結構體中upstreams數組元素 ngx_int_t ngx_http_upstream_init_round_robin(ngx_conf_t *cf, ngx_http_upstream_srv_conf_t *us) { ngx_url_t u; ngx_uint_t i, j, n, w; ngx_http_upstream_server_t *server; ngx_http_upstream_rr_peers_t *peers, *backup; //回調指針設置 us->peer.init = ngx_http_upstream_init_round_robin_peer; //服務器數組指針不為空 if (us->servers) { server = us->servers->elts; n = 0; w = 0; //遍歷所有服務器 for (i = 0; i < us->servers->nelts; i++) { //是后備服務器,跳過 if (server[i].backup) { continue; } //服務器地址數量統計 n += server[i].naddrs; //總的權重計算 w += server[i].naddrs * server[i].weight; } if (n == 0) { ngx_log_error(NGX_LOG_EMERG, cf->log, 0, "no servers in upstream \"%V\" in %s:%ui", &us->host, us->file_name, us->line); return NGX_ERROR; } //為非后備服務器分配空間 peers = ngx_pcalloc(cf->pool, sizeof(ngx_http_upstream_rr_peers_t) + sizeof(ngx_http_upstream_rr_peer_t) * (n - 1)); if (peers == NULL) { return NGX_ERROR; } //非后備服務器列表頭中各屬性設置 peers->single = (n == 1); peers->number = n; peers->weighted = (w != n); peers->total_weight = w; peers->name = &us->host; n = 0; //后備服務器列表中各服務器項設置 for (i = 0; i < us->servers->nelts; i++) { for (j = 0; j < server[i].naddrs; j++) { if (server[i].backup) { continue; } peers->peer[n].sockaddr = server[i].addrs[j].sockaddr; peers->peer[n].socklen = server[i].addrs[j].socklen; peers->peer[n].name = server[i].addrs[j].name; peers->peer[n].max_fails = server[i].max_fails; peers->peer[n].fail_timeout = server[i].fail_timeout; peers->peer[n].down = server[i].down; peers->peer[n].weight = server[i].weight; peers->peer[n].effective_weight = server[i].weight; peers->peer[n].current_weight = 0; n++; } } //非后備服務器列表掛載的位置 us->peer.data = peers; /* backup servers */ //后備服務器 n = 0; w = 0; for (i = 0; i < us->servers->nelts; i++) { if (!server[i].backup) { continue; } //后備服務器地址數量統計 n += server[i].naddrs; //后備服務器總權重計算 w += server[i].naddrs * server[i].weight; } if (n == 0) { return NGX_OK; } //后備服務器列表地址空間分配 backup = ngx_pcalloc(cf->pool, sizeof(ngx_http_upstream_rr_peers_t) + sizeof(ngx_http_upstream_rr_peer_t) * (n - 1)); if (backup == NULL) { return NGX_ERROR; } peers->single = 0; //后備服務器列表頭中各屬性設置 backup->single = 0; backup->number = n; backup->weighted = (w != n); backup->total_weight = w; backup->name = &us->host; n = 0; //后備服務器列表中各服務器項設置 for (i = 0; i < us->servers->nelts; i++) { for (j = 0; j < server[i].naddrs; j++) { if (!server[i].backup) { continue; } backup->peer[n].sockaddr = server[i].addrs[j].sockaddr; backup->peer[n].socklen = server[i].addrs[j].socklen; backup->peer[n].name = server[i].addrs[j].name; backup->peer[n].weight = server[i].weight; backup->peer[n].effective_weight = server[i].weight; backup->peer[n].current_weight = 0; backup->peer[n].max_fails = server[i].max_fails; backup->peer[n].fail_timeout = server[i].fail_timeout; backup->peer[n].down = server[i].down; n++; } } //后備服務器掛載 peers->next = backup; return NGX_OK; } //us參數中服務器指針為空,例如用戶直接在proxy_pass等指令后配置后端服務器地址 /* an upstream implicitly defined by proxy_pass, etc. */ if (us->port == 0) { ngx_log_error(NGX_LOG_EMERG, cf->log, 0, "no port in upstream \"%V\" in %s:%ui", &us->host, us->file_name, us->line); return NGX_ERROR; } ngx_memzero(&u, sizeof(ngx_url_t)); u.host = us->host; u.port = us->port; //IP地址解析 if (ngx_inet_resolve_host(cf->pool, &u) != NGX_OK) { if (u.err) { ngx_log_error(NGX_LOG_EMERG, cf->log, 0, "%s in upstream \"%V\" in %s:%ui", u.err, &us->host, us->file_name, us->line); } return NGX_ERROR; } n = u.naddrs; peers = ngx_pcalloc(cf->pool, sizeof(ngx_http_upstream_rr_peers_t) + sizeof(ngx_http_upstream_rr_peer_t) * (n - 1)); if (peers == NULL) { return NGX_ERROR; } peers->single = (n == 1); peers->number = n; peers->weighted = 0; peers->total_weight = n; peers->name = &us->host; for (i = 0; i < u.naddrs; i++) { peers->peer[i].sockaddr = u.addrs[i].sockaddr; peers->peer[i].socklen = u.addrs[i].socklen; peers->peer[i].name = u.addrs[i].name; peers->peer[i].weight = 1; peers->peer[i].effective_weight = 1; peers->peer[i].current_weight = 0; peers->peer[i].max_fails = 1; peers->peer[i].fail_timeout = 10; } us->peer.data = peers; /* implicitly defined upstream has no backup servers */ return NGX_OK; }
選擇后端服務器
static void ngx_http_upstream_init_request(ngx_http_request_t *r) { ... if (uscf->peer.init(r, uscf) != NGX_OK) { ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } ngx_http_upstream_connect(r, u); }
//函數: //功能:針對每個請求選擇后端服務器前做一些初始化工作 ngx_int_t ngx_http_upstream_init_round_robin_peer(ngx_http_request_t *r, ngx_http_upstream_srv_conf_t *us) { ngx_uint_t n; ngx_http_upstream_rr_peer_data_t *rrp; rrp = r->upstream->peer.data; if (rrp == NULL) { rrp = ngx_palloc(r->pool, sizeof(ngx_http_upstream_rr_peer_data_t)); if (rrp == NULL) { return NGX_ERROR; } r->upstream->peer.data = rrp; } rrp->peers = us->peer.data; rrp->current = 0; //n取值為:非后備服務器和后備服務器列表中個數較大的那個值 n = rrp->peers->number; if (rrp->peers->next && rrp->peers->next->number > n) { n = rrp->peers->next->number; } //如果n小於一個指針變量所能表示的范圍 if (n <= 8 * sizeof(uintptr_t)) { //直接使用已有的指針類型的data變量做位圖(tried是位圖,用來標識在一輪選擇中,各個后端服務器是否已經被選擇過) rrp->tried = &rrp->data; rrp->data = 0; } else { //否則從內存池中申請空間 n = (n + (8 * sizeof(uintptr_t) - 1)) / (8 * sizeof(uintptr_t)); rrp->tried = ngx_pcalloc(r->pool, n * sizeof(uintptr_t)); if (rrp->tried == NULL) { return NGX_ERROR; } } //回調函數設置 r->upstream->peer.get = ngx_http_upstream_get_round_robin_peer; r->upstream->peer.free = ngx_http_upstream_free_round_robin_peer; r->upstream->peer.tries = rrp->peers->number; #if (NGX_HTTP_SSL) r->upstream->peer.set_session = ngx_http_upstream_set_round_robin_peer_session; r->upstream->peer.save_session = ngx_http_upstream_save_round_robin_peer_session; #endif return NGX_OK; }

//函數: //功能:對后端服務器做一次選擇 ngx_int_t ngx_http_upstream_get_round_robin_peer(ngx_peer_connection_t *pc, void *data) { ngx_http_upstream_rr_peer_data_t *rrp = data; ngx_int_t rc; ngx_uint_t i, n; ngx_http_upstream_rr_peer_t *peer; ngx_http_upstream_rr_peers_t *peers; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0, "get rr peer, try: %ui", pc->tries); /* ngx_lock_mutex(rrp->peers->mutex); */ pc->cached = 0; pc->connection = NULL; //如果只有一台后端服務器,Nginx直接選擇並返回 if (rrp->peers->single) { peer = &rrp->peers->peer[0]; if (peer->down) { goto failed; } } else { //有多台后端服務器 /* there are several peers */ //按照各台服務器的當前權值進行選擇 peer = ngx_http_upstream_get_peer(rrp); if (peer == NULL) { goto failed; } ngx_log_debug2(NGX_LOG_DEBUG_HTTP, pc->log, 0, "get rr peer, current: %ui %i", rrp->current, peer->current_weight); } //設置連接的相關屬性 pc->sockaddr = peer->sockaddr; pc->socklen = peer->socklen; pc->name = &peer->name; /* ngx_unlock_mutex(rrp->peers->mutex); */ if (pc->tries == 1 && rrp->peers->next) { pc->tries += rrp->peers->next->number; } return NGX_OK; //選擇失敗,轉向后備服務器 failed: peers = rrp->peers; if (peers->next) { /* ngx_unlock_mutex(peers->mutex); */ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, pc->log, 0, "backup servers"); rrp->peers = peers->next; pc->tries = rrp->peers->number; 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_round_robin_peer(pc, rrp); if (rc != NGX_BUSY) { return rc; } /* ngx_lock_mutex(peers->mutex); */ } /* all peers failed, mark them as live for quick recovery */ for (i = 0; i < peers->number; i++) { peers->peer[i].fails = 0; } /* ngx_unlock_mutex(peers->mutex); */ pc->name = peers->name; //如果后備服務器也選擇失敗,則返回NGX_BUSY return NGX_BUSY; }
后端服務器權值計算在函數ngx_http_upstream_get_peer中。
//按照當前各服務器權值進行選擇 static ngx_http_upstream_rr_peer_t * ngx_http_upstream_get_peer(ngx_http_upstream_rr_peer_data_t *rrp) { time_t now; uintptr_t m; ngx_int_t total; ngx_uint_t i, n; ngx_http_upstream_rr_peer_t *peer, *best; now = ngx_time(); best = NULL; total = 0; for (i = 0; i < rrp->peers->number; i++) { //計算當前服務器的標記位在位圖中的位置 n = i / (8 * sizeof(uintptr_t)); m = (uintptr_t) 1 << i % (8 * sizeof(uintptr_t)); //已經選擇過,跳過 if (rrp->tried[n] & m) { continue; } //當前服務器對象 peer = &rrp->peers->peer[i]; //當前服務器已宕機,排除 if (peer->down) { 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; if (peer->effective_weight < peer->weight) { peer->effective_weight++; } if (best == NULL || peer->current_weight > best->current_weight) { best = peer; } } if (best == NULL) { return NULL; } //所選擇的服務器在服務器列表中的位置 i = best - &rrp->peers->peer[0]; rrp->current = i; n = i / (8 * sizeof(uintptr_t)); m = (uintptr_t) 1 << i % (8 * sizeof(uintptr_t)); //位圖相應位置置位 rrp->tried[n] |= m; best->current_weight -= total; best->checked = now; return best; }
ngx_int_t current_weight; ngx_int_t effective_weight; ngx_int_t weight;
for (i = 0; i < us->servers->nelts; i++) { for (j = 0; j < server[i].naddrs; j++) { if (server[i].backup) { continue; } peers->peer[n].weight = server[i].weight; peers->peer[n].effective_weight = server[i].weight; peers->peer[n].current_weight = 0; n++; } } /* backup servers */ for (i = 0; i < us->servers->nelts; i++) { for (j = 0; j < server[i].naddrs; j++) { if (!server[i].backup) { continue; } backup->peer[n].weight = server[i].weight; backup->peer[n].effective_weight = server[i].weight; backup->peer[n].current_weight = 0; n++; } } /* an upstream implicitly defined by proxy_pass, etc. */ for (i = 0; i < u.naddrs; i++) { peers->peer[i].weight = 1; peers->peer[i].effective_weight = 1; peers->peer[i].current_weight = 0; }
//服務正常,effective_weight 逐漸恢復正常 if (peer->effective_weight < peer->weight) { peer->effective_weight++; }
if (peer->max_fails) { //服務發生異常時,調低effective_weight peer->effective_weight -= peer->weight / peer->max_fails; }
selected server |
current_weight beforeselected |
current_weight afterselected |
a |
{ 5, 1, 2 } |
{ -3, 1, 2 } |
c |
{ 2, 2, 4 } |
{ 2, 2, -4 } |
a |
{ 7, 3, -2 } |
{ -1, 3, -2 } |
a |
{ 4, 4, 0 } |
{ -4, 4, 0 } |
b |
{ 1, 5, 2 } |
{ 1, -3, 2 } |
a |
{ 6, -2, 4 } |
{ -2, -2, 4 } |
c |
{ 3, -1, 6 } |
{ 3, -1, -2 } |
a |
{ 8, 0, 0 } |
{ 0, 0, 0 } |
釋放后端服務器
//函數: //功能:釋放后端服務器 void ngx_http_upstream_free_round_robin_peer(ngx_peer_connection_t *pc, void *data, ngx_uint_t state) { ngx_http_upstream_rr_peer_data_t *rrp = data; time_t now; ngx_http_upstream_rr_peer_t *peer; ngx_log_debug2(NGX_LOG_DEBUG_HTTP, pc->log, 0, "free rr peer %ui %ui", pc->tries, state); /* TODO: NGX_PEER_KEEPALIVE */ //后端服務只有一個 if (rrp->peers->single) { pc->tries = 0; return; } peer = &rrp->peers->peer[rrp->current]; //在某一輪選擇里,某次選擇的服務器因連接失敗或請求處理失敗而需要重新進行選擇 if (state & NGX_PEER_FAILED) { now = ngx_time(); /* ngx_lock_mutex(rrp->peers->mutex); */ //已嘗試失敗次數加一 peer->fails++; peer->accessed = now; peer->checked = now; //如果有最大失敗次數限制 if (peer->max_fails) { //服務發生異常時,調低effective_weight peer->effective_weight -= peer->weight / peer->max_fails; } ngx_log_debug2(NGX_LOG_DEBUG_HTTP, pc->log, 0, "free rr peer failed: %ui %i", rrp->current, peer->effective_weight); //effective_weight總大於0 if (peer->effective_weight < 0) { peer->effective_weight = 0; } /* ngx_unlock_mutex(rrp->peers->mutex); */ } else { /* mark peer live if check passed */ if (peer->accessed < peer->checked) { peer->fails = 0; } } //ngx_peer_connection_t結構體中tries字段: //表示在連接一個遠端服務器時,當前連接出現異常失敗后可以重試的次數,也就是允許失敗的次數 if (pc->tries) { pc->tries--; } /* ngx_unlock_mutex(rrp->peers->mutex); */ }
整個加權輪詢的流程

static char * ngx_http_upstream_init_main_conf(ngx_conf_t *cf, void *conf) { ... for (i = 0; i < umcf->upstreams.nelts; i++) { //全局初始化 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; } } ... }收到客戶請求之后,針對當前請求進行初始化,完成此功能的函數是ngx_http_upstream_init_round_robin_peer,它在函數ngx_http_upstream_init_request中被調用:
static void ngx_http_upstream_init_request(ngx_http_request_t *r) { ... if (uscf->peer.init(r, uscf) != NGX_OK) { ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } ngx_http_upstream_connect(r, u); }然后是針對每個請求選擇后端服務器,實現此功能的函數是ngx_http_upstream_get_round_robin_peer。它在函數ngx_event_connect_peer中被調用:
//函數:連接后端upstream ngx_int_t ngx_event_connect_peer(ngx_peer_connection_t *pc) { ... //此處調用選擇后端服務器功能函數ngx_http_upstream_get_round_robin_peer rc = pc->get(pc, pc->data); if (rc != NGX_OK) { return rc; } s = ngx_socket(pc->sockaddr->sa_family, SOCK_STREAM, 0); ... }之后是測試連接ngx_http_upstream_test_connect。它在函數ngx_http_upstream_send_request被調用:
//函數:發送數據到后端upstream static void ngx_http_upstream_send_request(ngx_http_request_t *r, ngx_http_upstream_t *u) { ... if (!u->request_sent && ngx_http_upstream_test_connect(c) != NGX_OK) { //測試連接失敗 ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_ERROR); return; } ... }
static void ngx_http_upstream_next(ngx_http_request_t *r, ngx_http_upstream_t *u, ngx_uint_t ft_type) { ... if (u->peer.sockaddr) { if (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; } ... if (status) { u->state->status = status; if (u->peer.tries == 0 || !(u->conf->next_upstream & ft_type)) { #if (NGX_HTTP_CACHE) if (u->cache_status == NGX_HTTP_CACHE_EXPIRED && (u->conf->cache_use_stale & ft_type)) { ngx_int_t rc; rc = u->reinit_request(r); if (rc == NGX_OK) { u->cache_status = NGX_HTTP_CACHE_STALE; rc = ngx_http_upstream_cache_send(r, u); } ngx_http_upstream_finalize_request(r, u, rc); return; } #endif //結束請求 ngx_http_upstream_finalize_request(r, u, status); return; } } ... //再次發起連接 ngx_http_upstream_connect(r, u); }
參考資料:
http://www.cnblogs.com/xiaogangqq123/archive/2011/03/04/1971002.html
http://tengine.taobao.org/book/chapter_05.html#id5
http://blog.dccmx.com/2011/07/nginx-upsream-src-2/
http://nginx-source-analysis.googlecode.com/svn-history/r151/trunk/src/http/ngx_http_upstream.h
http://www.inginx.org/thread-89-1-1.html
http://blog.sina.com.cn/s/blog_7303a1dc01014i0j.html
https://github.com/phusion/nginx/commit/27e94984486058d73157038f7950a0a36ecc6e35
http://www.pagefault.info/?p=251
http://www.pagefault.info/?p=259