<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                企業??AI智能體構建引擎,智能編排和調試,一鍵部署,支持知識庫和私有化部署方案 廣告
                Nginx版本:1.9.1 我的博客:[http://blog.csdn.net/zhangskd](http://blog.csdn.net/zhangskd) ? 上篇blog講述了加權輪詢算法的原理、以及負載均衡模塊中使用的數據結構,接著我們來看看加權輪詢算法的具體實現。 ? **指令的解析函數** ? 如果upstream配置塊中沒有指定使用哪種負載均衡算法,那么默認使用加權輪詢。 也就是說使用加權輪詢算法,并不需要特定的指令,因此也不需要實現指令的解析函數。 而實際上,和其它負載均衡算法不同(比如ip_hash),加權輪詢算法并不是以模塊的方式實現的, 而是作為Nginx框架的一部分。 ? **初始化upstream塊** ? 在執行ngx_http_upstream_module的init main conf函數時,會遍歷所有upstream配置塊,調用它們 事先指定的初始化函數。對于一個upstream配置塊,如果沒有指定初始化函數,則調用加權輪詢算法 提供的upstream塊初始化函數 - ngx_http_upstream_init_round_robin。 ? 來看下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; } } ... } ~~~ ngx_http_upstream_init_round_robin做的工作很簡單: 指定請求的負載均衡初始化函數,用于初始化per request的負載均衡數據。 創建和初始化后端集群、備份集群。 ~~~ 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_peer_t *peer, **peerp; ngx_http_upstream_rr_peers_t *peers, *backup; /* 指定請求的負載均衡初始化函數,用于初始化per request的負載均衡數據 */ us->peer.init = ngx_http_upstream_init_round_robin_peer; /* upstream配置塊的servers數組,在解析配置文件時就創建好了 */ if (us->servers) { server = us->servers->elts; n = 0; w = 0; /* 數組元素類型為ngx_http_upstream_server_t,對應一條server指令 */ 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_ERROR; } /* 創建一個后端集群的實例 */ peers = ngx_pcalloc(cf->pool, sizeof(ngx_http_upstream_rr_peers_t)); ... /* 創建后端服務器的實例,總共有n臺 */ peer = ngx_pcalloc(cf->pool, sizeof(ngx_http_upstream_rr_peer_t) * n); ... /* 初始化集群 */ peers->single = (n == 1); /* 是否只有一臺后端 */ peers->number = n; /* 后端服務器的數量 */ peers->weight = (w != n); /* 是否使用權重 */ peers->total_weight = w; /* 所有后端服務器權重的累加值 */ peers->name = &us->host; /* upstream配置塊的名稱 */ n = 0; peerp = &peers->peer; /* 初始化代表后端的結構體ngx_http_upstream_peer_t. * server指令后跟的是域名的話,可能對應多臺后端. */ for(i = 0; i < us->servers->nelts; i++) { if (server[i].backup) continue; for (j = 0; j < server[i].naddrs; j++) { peer[n].sockaddr = server[i].addrs[j].sockaddr; /* 后端服務器的地址 */ peer[n].socklen = server[i].addrs[j].socklen; /* 地址的長度*/ peer[n].name = server[i].addrs[j].name; /* 后端服務器地址的字符串 */ peer[n].weight = server[i].weight; /* 配置項指定的權重,固定值 */ peer[n].effective_weight = server[i].weight; /* 有效的權重,會因為失敗而降低 */ peer[n].current_weight = 0; /* 當前的權重,動態調整,初始值為0 */ peer[n].max_fails = server[i].max_fails; /* "一段時間內",最大的失敗次數,固定值 */ peer[n].fail_timeout = server[i].fail_timeout; /* "一段時間"的值,固定值 */ peer[n].down = server[i].down; /* 服務器永久不可用的標志 */ peer[n].server = server[i].name; /* server的名稱 */ /* 把后端服務器組成一個鏈表,第一個后端的地址保存在peers->peer */ *peerp = &peer[n]; peerp = &peer[n].next; n++; } } us->peer.data = peers; /* 保存后端集群的地址 */ } /* backup servers */ /* 創建和初始化備份集群,peers->next指向備份集群,和上述流程類似,不再贅述 */ ... /* an upstream implicitly defined by proxy_pass, etc. */ /* 如果直接使用proxy_pass指令,沒有定義upstream配置塊 */ if (us->port == 0) { ... return NGX_ERROR; } ngx_memzero(&u, sizeof(ngx_url_t)); u.host = us->host; u.port = us->port; /* 根據URL解析域名 */ if (ngx_inet_resolve_host(cf->pool, &u) != NGX_OK) { ... return NGX_ERROR; } n = u.naddrs; /* 共有n個后端 */ /* 接下來創建后端集群,并進行初始化,和上述流程類似,這里不再贅述 */ ... return NGX_OK; } ~~~ ? **初始化請求的負載均衡數據** ? 當收到一個請求后,一般使用的反向代理模塊(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**,當不用該后端時,進行數據的更新(不管成功或失敗都調用) 指定r->upstream->peer.tries,請求最多允許嘗試這么多個后端 ~~~ 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; /* upstream塊的后端集群 */ rrp->current = NULL; n = rrp->peers->number; /* 后端的數量 */ /* 如果存在備份集群,且其服務器數量超過n */ if (rrp->peers->next && rrp->peers->next->number > n) { n = rrp->peers->next->number; } /* rrp->tried指向后端服務器的位圖,每一位代表一臺后端的狀態,0表示可用,1表示不可用。 * 如果后端數較少,直接使用rrp->data作為位圖。如果后端數較多,則需要申請一塊內存。 */ if (n <= 8 *sizeof(uintptr_t)) { 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; /* 指定peer.get,用于從集群中選取一臺后端服務器 */ r->upstream->peer.free = ngx_http_upstream_free_round_robin_peer; /* 指定peer.free,當不用該后端時,進行數據的更新 */ r->upstream->peer.tries = ngx_http_upstream_tries(rrp->peers); /* 指定peer.tries,是請求允許嘗試的后端服務器個數 */ ... return NGX_OK; } #define ngx_http_upstream_tries(p) ((p)->number + ((p)->next ? (p)->next->number : 0)) ~~~ ? **選取一臺后端服務器** ? 一般upstream塊中會有多臺后端,那么對于本次請求,要選定哪一臺后端呢? 這時候第三步中r->upstream->peer.get指向的函數就派上用場了: 采用加權輪詢算法,從集群中選出一臺后端來處理本次請求。?選定后端的地址保存在pc->sockaddr,pc為主動連接。 函數的返回值: NGX_DONE:選定一個后端,和該后端的連接已經建立。之后會直接發送請求。 NGX_OK:選定一個后端,和該后端的連接尚未建立。之后會和后端建立連接。 NGX_BUSY:所有的后端(包括備份集群)都不可用。之后會給客戶端發送502(Bad Gateway)。 ~~~ 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; ... pc->cached = 0; pc->connection = NULL; peers = rrp->peers; /* 后端集群 */ ... /* 如果只有一臺后端,那就不用選了 */ if (peers->single) { peer = peers->peer; if (peer->down) goto failed; rrp->current = peer; } else { /* there are several peers */ /* 調用ngx_http_upstream_get_peer來從后端集群中選定一臺后端服務器 */ peer = ngx_http_upstream_get_peer(rrp); if (peer == NULL) goto failed; ... } /* 保存選定的后端服務器的地址,之后會向這個地址發起連接 */ pc->sockaddr = peer->sockaddr; pc->socklen = peer->socklen; pc->name = &peer->name; peer->conns++; /* 增加選定后端的當前連接數 */ ... return NGX_OK; failed: /* 如果不能從集群中選取一臺后端,那么嘗試備用集群 */ if (peers->next) { ... rrp->peers = peers->next; 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; } /* all peers failed, mark them as live for quick recovery */ for (peer = peers->peer; peer; peer = peer->next) { peer->fails = 0; } pc->name = peers->name; 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, p; ngx_http_upstream_rr_peer_t *peer, *best; now = ngx_time(); best = NULL; total = 0; ... /* 遍歷集群中的所有后端 */ for (peer = rrp->peers->peer, i = 0; peer; peer = peer->next, i++) { n = i / (8 * sizeof(uintptr_t)); m = (uintptr_t) 1 << i % (8 * sizeof(uintptr_t)); /* 檢查該后端服務器在位圖中對應的位,為1時表示不可用 */ if (rrp->tried[n] & m) continue; /* 永久不可用的標志 */ 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; /* 累加所有后端的有效權重 */ /* 如果之前此后端發生了失敗,會減小其effective_weight來降低它的權重。 * 此后在選取后端的過程中,又通過增加其effective_weight來恢復它的權重。 */ if (peer->effective_weight < peer->weight) peer->effective_weight++; /* 選取當前權重最大者,作為本次選定的后端 */ if (best == NULL || peer->current_weight > best->current_weight) { best = peer; p = i; } } if (best == NULL) /* 沒有可用的后端 */ return NULL; rrp->current = best; /* 保存本次選定的后端 */ n = p / (8 * sizeof(uintptr_t)); m = (uintptr_t) 1 << p % (8 * sizeof(uintptr_t)); /* 對于本次請求,如果之后需要再次選取后端,不能再選取這個后端了 */ rrp->tried[n] |= m; best->current_weight -= total; /* 選定后端后,需要降低其當前權重 */ /* 更新checked時間 */ if (now - best->checked > best->fail_timeout) best->checked = now; return best; } ~~~ **釋放一臺后端服務器** ? 當不再使用一臺后端時,需要進行收尾處理,比如統計失敗的次數。 這時候會調用第三步中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)。 ~~~ 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; ... peer = rrp->current; /* 當前使用的后端服務器 */ if (rrp->peers->single) { peer->conns--; /* 減少后端的當前連接數 */ pc->tries = 0; /* 不能再繼續嘗試了 */ return; } /* 如果連接后端失敗了 */ if (state & NGX_PEER_FAILED) { now = ngx_time(); peer->fails++; /* 一段時間內,已經失敗的次數 */ peer->accessed = now; /* 最近一次失敗的時間點 */ peer->checked = now; /* 用于檢查是否超過了“一段時間” */ /* 當后端出錯時,降低其有效權重 */ if (peer->max_fails) peer->effective_weight -= peer->weight / peer->max_fails; /* 有效權重的最小值為0 */ if (peer->effective_weight < 0) peer->effective_weight = 0; } else { /* mark peer live if check passed */ /* 說明距離最后一次失敗的時間點,已超過fail_timeout了,清零fails */ if (peer->accessed < peer->checked) peer->fails = 0; } peer->conns--; /* 更新后端的當前連接數 */ if (pc->tries) pc->tries--; /* 對于一個請求,允許嘗試的后端個數 */ } ~~~ ? **判斷后端是否可用** ? **相關的變量的定義** ngx_uint_t fails; /* 一段時間內,已經失敗的次數 */ time_t accessed; /* 最近一次失敗的時間點 */ time_t checked; /* 用于檢查是否超過了“一段時間” */ ngx_uint_t max_fails; /* 一段時間內,允許的最大的失敗次數,固定值 */ time_t fail_timeout; /* “一段時間”的長度,固定值 */ ~~~ ngx_http_upstream_get_peeer /* 在一段時間內,如果此后端服務器的失敗次數,超過了允許的最大值, * 那么在此后的一段時間內不允許使用此后端了。 */ if (peer->max_fails && peer->fails >= peer->max_fails && now - peer->checked <= peer->fail_timeout) continue; ... /* 選定本后端了 */ if (now - best->checked > best->fail_timeout) best->checked = now; ~~~ ~~~ ngx_http_upstream_free_round_robin_peer if (state & NGX_PEER_FAILED) { peer->fails++; peer->accessed = now; peer->checked = now; ... } else if (peer->accessed < peer->checked) peer->fails = 0; ~~~ 相關變量的更新 accessed:釋放peer時,如果發現后端出錯了,則更新為now。 checked:釋放peer時,如果發現后端出錯了,則更新為now。選定該peer時,如果now - checked > fail_timeout,則更新為now。 fails:釋放peer時,如果本次成功了且accessed < checked,說明距離最后一次失敗的時間點,已超過fail_timeout了,清零fails。 ? **上述變量的準備定義** fails并不是“一段時間內”的失敗次數,而是兩兩間時間間隔小于“一段時間”的連續失敗次數。 max_fails也不是“一段時間內”允許的最大失敗次數,而是兩兩間的時間間隔小于“一段時間”的最大失敗次數。 舉例說明,假設fail_timeout為10s,max_fails為3。 10s內失敗3次,肯定會導致接下來的10s不可用。 27s內失敗3次,也可能導致接下來的10s不可用,只要3次失敗兩兩之間的時間間隔為9s。 ? 下圖用來簡要說明 ? ?![](https://box.kancloud.cn/2016-03-22_56f0c8a54d085.jpg) ? ?
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看