目錄
<code><span>typedef</span><span>struct</span> { <span>/* 负载均衡的类型 */</span> ngx_http_upstream_init_pt init_upstream; <span>/* 负载均衡类型的初始化函数 */</span> ngx_http_upstream_init_peer_pt init; <span>/* 指向 ngx_http_upstream_rr_peers_t 结构体 */</span><span>void</span> *data; } ngx_http_upstream_peer_t;</code>ngx_http_upstream_server_t 結構體
<code><span>/* 服务器结构体 */</span><span>typedef</span><span>struct</span> { <span>/* 指向存储 IP 地址的数组,因为同一个域名可能会有多个 IP 地址 */</span> ngx_addr_t *addrs; <span>/* IP 地址数组中元素个数 */</span> ngx_uint_t naddrs; <span>/* 权重 */</span> ngx_uint_t weight; <span>/* 最大失败次数 */</span> ngx_uint_t max_fails; <span>/* 失败时间阈值 */</span> time_t fail_timeout; <span>/* 标志位,若为 1,表示不参与策略选择 */</span><span>unsigned</span> down:<span>1</span>; <span>/* 标志位,若为 1,表示为备用服务器 */</span><span>unsigned</span> backup:<span>1</span>; } ngx_http_upstream_server_t;</code>
ngx_http_upstream_rr_peer_peer 結構體
ngx_http_upstream_rr_peer_data_t 結構體<code>typedef <span>struct</span> { <span>/* 后端服务器 IP 地址 */</span><span>struct</span> sockaddr *sockaddr; <span>/* 后端服务器 IP 地址的长度 */</span> socklen_t socklen; <span>/* 后端服务器的名称 */</span> ngx_str_t name; <span>/* 后端服务器当前的权重 */</span> ngx_int_t current_weight; <span>/* 后端服务器有效权重 */</span> ngx_int_t effective_weight; <span>/* 配置项所指定的权重 */</span> ngx_int_t weight; <span>/* 已经失败的次数 */</span> ngx_uint_t fails; <span>/* 访问时间 */</span> time_t accessed; time_t <span>checked</span>; <span>/* 最大失败次数 */</span> ngx_uint_t max_fails; <span>/* 失败时间阈值 */</span> time_t fail_timeout; <span>/* 后端服务器是否参与策略,若为1,表示不参与 */</span> ngx_uint_t down; <span>/* unsigned down:1; */</span><span>#<span>if</span> (NGX_HTTP_SSL)</span> ngx_ssl_session_t *ssl_session; <span>/* local to a process */</span><span>#<span>endif</span></span> } ngx_http_upstream_rr_peer_t;</code>
在Nginx 啟動過程中,解析完http 配置區塊之後,會呼叫各個http 模組對應的初始函數。對於upstream 機制的
ngx_http_upstream_module模組來說,對應的main 配置初始函數是
ngx_http_upstream_init_main_conf()如下所示:
<code><span>typedef</span><span>struct</span> ngx_http_upstream_rr_peers_s ngx_http_upstream_rr_peers_t; <span>struct</span> ngx_http_upstream_rr_peers_s { <span>/* 竞选队列中后端服务器的数量 */</span> ngx_uint_t number; <span>/* ngx_mutex_t *mutex; */</span><span>/* 所有后端服务器总的权重 */</span> ngx_uint_t total_weight; <span>/* 标志位,若为 1,表示后端服务器仅有一台,此时不需要选择策略 */</span><span>unsigned</span> single:<span>1</span>; <span>/* 标志位,若为 1,表示所有后端服务器总的权重等于服务器的数量 */</span><span>unsigned</span> weighted:<span>1</span>; ngx_str_t *name; <span>/* 后端服务器的链表 */</span> ngx_http_upstream_rr_peers_t *next; <span>/* 特定的后端服务器 */</span> ngx_http_upstream_rr_peer_t peer[<span>1</span>]; };</code>
在
ngx_http_upstream_x_http_輪詢策略初始函數為ngx_http_upstream_init_round_robin
。否則的話執行的是uscfp[i]->peer.init_upstream
指標函數。 當接收到來自客戶端的請求時,Nginx 會呼叫ngx_http_upstream_init_request
初始化請求的過程中,呼叫uscf->peer.init(r, uscf)
,對於upstream 機制的加權輪查詢策略來說就是ngx_http_upstream_init_round_robin_peer,該方法完成請求初始化工作。
<code><span>typedef</span> struct { ngx_http_upstream_rr_peers_t *peers; ngx_uint_t current; uintptr_t *tried; uintptr_t <span><span>data</span>;</span> } ngx_http_upstream_rr_peer_data_t;</code>
完成客戶端請求的初始化工作之後,會選擇一個後端伺服器來處理該請求,選擇後端伺服器由函數
ngx_http_upstream_get_round_robin_peer 實作。函數在 ngx_event_connect_peer中被呼叫。
<code><span>for</span> (i = <span>0</span>; i < umcf->upstreams.nelts; i++) { init = uscfp[i]<span>-></span>peer.init_upstream ? uscfp[i]<span>-></span>peer.<span>init_upstream</span>: ngx_http_upstream_init_round_robin; <span>if</span> (init(cf, uscfp[i]) != NGX_OK) { <span>return</span> NGX_CONF_ERROR; } } </code>
當已經選擇一台後端伺服器來處理請求時,接下來就會測試該後端伺服器的連接情況,測試連接由函數
ngx_http_upstream_test_connect 實現,在函數
ngx_http_upstream_send_request 實現,在函數 ngx_http_upstream_send_request 中被呼叫。
<code>static void ngx_http_upstream_init_request(ngx_http_request_t *r) { <span>...</span><span>if</span> (uscf->peer.init(r, uscf) != NGX_OK) { ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR); <span>return</span>; } ngx_http_upstream_connect(r, u); } </code>
若連線測試失敗,會由函數
ngx_http_upstream_next 啟動再次測試,若測試成功,則處理完請求之後,會呼叫
ngx_http_upstream_free_round_robin_peer 釋放後端伺服器。 加權輪詢工作流程
加權輪詢策略的基本工作流程是:初始化負載平衡伺服器列表,初始化後端伺服器,選擇合適後端伺服器處理請求,釋放後端伺服器。
初始化伺服器清單由函數
ngx_http_upstream_init_round_robin 實現,該函數的執行流程如下所示:
第一種情況:若非備用機製配置中配置了伺服器配置伺服器列表,並將其掛載到us->peer.data
中;初始化備用伺服器列表,並將其掛載到
中;
初始化非備用伺服器列表,並將其掛載到
us->peer.data
<code>ngx_int_t ngx_event_connect_peer(ngx_peer_connection_t *pc) { <span>...</span>/* 调用 ngx_http_upstream_get_round_robin_peer */ rc = pc->get(pc, pc->data); <span>if</span> (rc != NGX_OK) { <span>return</span> rc; } s = ngx_socket(pc->sockaddr->sa_family, SOCK_STREAM, <span>0</span>); <span>...</span>} </code>
選擇合適的後端伺服器
上面的初始化负载服务器列表的全局初始化工作完成之后,当客户端发起请求时,Nginx 会选择一个合适的后端服务器来处理该请求。在本轮选择后端服务器之前,Nginx 会对后端服务器进行初始化工作,该工作由函数 ngx_http_upstream_init_round_robin_peer
实现。
ngx_http_upstream_init_round_robin_peer
函数的执行流程如下所示:
tried
,该位图是记录后端服务器是否被选择过: int
中记录所有后端服务器的状态;ngx_peer_connection_t
结构体中 get
的回调方法为 ngx_http_upstream_get_round_robin_peer
;free
的回调方法为 ngx_http_upstream_free_round_robin_peer
,设置 tries
重试连接的次数为非备用后端服务器的个数;<code><span>/* 当客户端发起请求时,upstream 机制为本轮选择一个后端服务器做初始化工作 */</span> ngx_int_t ngx_http_upstream_init_round_robin_peer(ngx_http_request_t <span>*</span>r, ngx_http_upstream_srv_conf_t <span>*</span>us) { ngx_uint_t n; ngx_http_upstream_rr_peer_data_t <span>*</span>rrp; <span>/* 注意:r->upstream->peer 是 ngx_peer_connection_t 结构体类型 */</span><span>/* 获取当前客户端请求中的 ngx_http_upstream_rr_peer_data_t 结构体 */</span> rrp <span>=</span> r<span>-></span>upstream<span>-></span>peer<span>.</span><span>data</span>; <span>if</span> (rrp <span>==</span><span>NULL</span>) { rrp <span>=</span> ngx_palloc(r<span>-></span>pool, sizeof(ngx_http_upstream_rr_peer_data_t)); <span>if</span> (rrp <span>==</span><span>NULL</span>) { <span>return</span> NGX_ERROR; } r<span>-></span>upstream<span>-></span>peer<span>.</span><span>data</span><span>=</span> rrp; } <span>/* 获取非备用后端服务器列表 */</span> rrp<span>-></span>peers <span>=</span> us<span>-></span>peer<span>.</span><span>data</span>; rrp<span>-></span>current <span>=</span><span>0</span>;<span>/* 若采用遍历方式选择后端服务器时,作为起始节点编号 */</span><span>/* 下面是取值 n,若存在备用后端服务器列表,则 n 的值为非备用后端服务器个数 与 备用后端服务器个数 之间的较大者 */</span> n <span>=</span> rrp<span>-></span>peers<span>-></span>number; <span>if</span> (rrp<span>-></span>peers<span>-></span>next <span>&&</span> rrp<span>-></span>peers<span>-></span>next<span>-></span>number <span>></span> n) { n <span>=</span> rrp<span>-></span>peers<span>-></span>next<span>-></span>number; } <span>/* rrp->tried 是一个位图,在本轮选择中,该位图记录各个后端服务器是否被选择过 */</span><span>/* * 如果后端服务器数量 n 不大于 32,则只需在一个 int 中即可记录下所有后端服务器状态; * 如果后端服务器数量 n 大于 32,则需在内存池中申请内存来存储所有后端服务器的状态; */</span><span>if</span> (n <span><=</span><span>8</span><span>*</span> sizeof(uintptr_t)) { rrp<span>-></span>tried <span>=</span><span>&</span>rrp<span>-></span><span>data</span>; rrp<span>-></span><span>data</span><span>=</span><span>0</span>; } <span>else</span> { n <span>=</span> (n <span>+</span> (<span>8</span><span>*</span> sizeof(uintptr_t) <span>-</span><span>1</span>)) <span>/</span> (<span>8</span><span>*</span> sizeof(uintptr_t)); rrp<span>-></span>tried <span>=</span> ngx_pcalloc(r<span>-></span>pool, n <span>*</span> sizeof(uintptr_t)); <span>if</span> (rrp<span>-></span>tried <span>==</span><span>NULL</span>) { <span>return</span> NGX_ERROR; } } <span>/* * 设置 ngx_peer_connection_t 结构体中 get 、free 的回调方法; * 设置 ngx_peer_connection_t 结构体中 tries 重试连接的次数为非备用后端服务器的个数; */</span> r<span>-></span>upstream<span>-></span>peer<span>.</span>get <span>=</span> ngx_http_upstream_get_round_robin_peer; r<span>-></span>upstream<span>-></span>peer<span>.</span>free <span>=</span> ngx_http_upstream_free_round_robin_peer; r<span>-></span>upstream<span>-></span>peer<span>.</span>tries <span>=</span> rrp<span>-></span>peers<span>-></span>number; <span>#if</span> (NGX_HTTP_SSL) r<span>-></span>upstream<span>-></span>peer<span>.</span>set_session <span>=</span> ngx_http_upstream_set_round_robin_peer_session; r<span>-></span>upstream<span>-></span>peer<span>.</span>save_session <span>=</span> ngx_http_upstream_save_round_robin_peer_session; <span>#endif</span><span>return</span> NGX_OK; }</code>
完成后端服务器的初始化工作之后,根据各个后端服务器的权重来选择权重最高的后端服务器处理客户端请求,由函数 ngx_http_upstream_get_round_robin_peer
实现。
ngx_http_upstream_get_round_robin_peer
函数的执行流程如下所示:
ngx_http_upstream_rr_peers_t
结构体中的 single
标志位: single
标志位为 1,表示只有一台非备用后端服务器: down
标志位: down
标志位为 0,则选择该非备用后端服务器来处理请求;down
标志位为 1, 该非备用后端服务器表示不参与策略选择,则跳至 goto failed
步骤从备用后端服务器列表中选择后端服务器来处理请求; single
标志位为 0,则表示不止一台非备用后端服务器,则调用 ngx_http_upstream_get_peer
方法根据非备用后端服务器的权重来选择一台后端服务器处理请求,根据该方法的返回值 peer
进行判断: peer = NULL
, 表示在非备用后端服务器列表中没有选中到合适的后端服务器来处理请求,则跳至 goto failed
从备用后端服务器列表中选择一台后端服务器来处理请求;return NGX_OK
从当前函数返回;ngx_http_upstream_get_round_robin_peer
选择一台后端服务器来处理请求;<code>/* 选择一个后端服务器来处理请求 */ <span>ngx_int_t</span><span>ngx_http_upstream_get_round_robin_peer</span>(ngx_peer_connection_t *pc, void *<span><span>data</span>)</span> { ngx_http_upstream_rr_peer_data_t *rrp = <span><span>data</span>;</span> 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(<span>NGX_LOG_DEBUG_HTTP</span>, pc->log, <span>0</span>, <span>"get rr peer, try: %ui"</span>, pc->tries); /* ngx_lock_mutex(rrp->peers->mutex); */ pc->cached = <span>0</span>; pc->connection = <span>NULL</span>; /* * 检查 ngx_http_upstream_rr_peers_t 结构体中的 single 标志位; * 若 single 标志位为 <span>1</span>,表示只有一台非备用后端服务器, * 接着检查该非备用后端服务器的 down 标志位,若 down 标志位为 <span>0</span>,则选择该非备用后端服务器来处理请求; * 若 down 标志位为 <span>1</span>, 该非备用后端服务器表示不参与策略选择, * 则跳至 goto failed 步骤从备用后端服务器列表中选择后端服务器来处理请求; */ <span>if</span> (rrp->peers->single) { peer = &rrp->peers->peer[<span>0</span>]; <span>if</span> (peer->down) { goto failed; } } <span>else</span> {/* 若 single 标志位为 <span>0</span>,表示不止一台非备用后端服务器 */ /* there are several peers */ /* 根据非备用后端服务器的权重来选择一台后端服务器处理请求 */ peer = ngx_http_upstream_get_peer(rrp); <span>if</span> (peer == <span>NULL</span>) { /* * 若从非备用后端服务器列表中没有选择一台合适的后端服务器处理请求, * 则 goto failed 从备用后端服务器列表中选择一台后端服务器来处理请求; */ goto failed; } ngx_log_debug2(<span>NGX_LOG_DEBUG_HTTP</span>, pc->log, <span>0</span>, <span>"get rr peer, current: %ui %i"</span>, rrp->current, peer->current_weight); } /* * 若从非备用后端服务器列表中已经选到了一台合适的后端服务器处理请求; * 则获取该后端服务器的地址信息; */ pc->sockaddr = peer->sockaddr;/* 获取被选中的非备用后端服务器的地址 */ pc->socklen = peer->socklen;/* 获取被选中的非备用后端服务器的地址长度 */ pc->name = &peer->name;/* 获取被选中的非备用后端服务器的域名 */ /* ngx_unlock_mutex(rrp->peers->mutex); */ /* * 检查被选中的非备用后端服务器重试连接的次数为 <span>1</span>,且存在备用后端服务器列表, * 则将该非备用后端服务器重试连接的次数设置为 备用后端服务器个数加 <span>1</span>; * 否则不用重新设置; */ <span>if</span> (pc->tries == <span>1</span> && rrp->peers->next) { pc->tries += rrp->peers->next->number; } /* 到此,表示已经选择到了一台合适的非备用后端服务器来处理请求,则成功返回 */ return <span>NGX_OK</span>; <span>failed</span>: /* * 若从非备用后端服务器列表中没有选择到后端服务器处理请求, * 若存在备用后端服务器,则从备用后端服务器列表中选择一台后端服务器来处理请求; */ peers = rrp->peers; /* 若存在备用后端服务器,则从备用后端服务器列表中选择一台后端服务器来处理请求;*/ <span>if</span> (peers->next) { /* ngx_unlock_mutex(peers->mutex); */ ngx_log_debug0(<span>NGX_LOG_DEBUG_HTTP</span>, pc->log, <span>0</span>, <span>"backup servers"</span>); /* 获取备用后端服务器列表 */ rrp->peers = peers->next; /* 把后端服务器重试连接的次数 tries 设置为备用后端服务器个数 number */ pc->tries = rrp->peers->number; /* 计算备用后端服务器在位图中的位置 n */ n = (rrp->peers->number + (<span>8</span> * sizeof(uintptr_t) - <span>1</span>)) / (<span>8</span> * sizeof(uintptr_t)); /* 初始化备用后端服务器在位图 rrp->tried[i] 中的值为 <span>0</span> */ for (i = <span>0</span>; i < n; i++) { rrp->tried[i] = <span>0</span>; } /* 把备用后端服务器列表当前非备用后端服务器列表递归调用 ngx_http_upstream_get_round_robin_peer 选择一台后端服务器 */ rc = ngx_http_upstream_get_round_robin_peer(pc, rrp); /* 若选择成功则返回 */ <span>if</span> (rc != <span>NGX_BUSY</span>) { return rc; } /* ngx_lock_mutex(peers->mutex); */ } /* * 若从备用后端服务器列表中也没有选择到一台后端服务器处理请求, * 则重新设置非备用后端服务器连接失败的次数 fails 为 <span>0</span> ,以便重新被选择; */ /* all peers failed, mark them <span>as</span> live for quick recovery */ for (i = <span>0</span>; i < peers->number; i++) { peers->peer[i].fails = <span>0</span>; } /* ngx_unlock_mutex(peers->mutex); */ pc->name = peers->name; /* 选择失败,则返回 */ return <span>NGX_BUSY</span>; }</code>
ngx_http_upstream_get_peer
函数是计算每一个后端服务器的权重值,并选择一个权重最高的后端服务器。
ngx_http_upstream_get_peer
函数的执行流程如下所示:
ngx_http_upstream_rr_peer_data_t
结构体 current 成员的值,在释放后端服务器时会用到该值;<code><span>/* 根据后端服务器的权重来选择一台后端服务器处理请求 */</span><span>static</span> 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 = <span>NULL</span>; total = <span>0</span>; <span>/* 遍历后端服务器列表 */</span><span>for</span> (i = <span>0</span>; i < rrp->peers->number; i++) { <span>/* 计算当前后端服务器在位图中的位置 n */</span> n = i / (<span>8</span> * <span>sizeof</span>(uintptr_t)); m = (uintptr_t) <span>1</span> << i % (<span>8</span> * <span>sizeof</span>(uintptr_t)); <span>/* 当前后端服务器在位图中已经有记录,则不再次被选择,即 continue 检查下一个后端服务器 */</span><span>if</span> (rrp->tried[n] & m) { <span>continue</span>; } <span>/* 若当前后端服务器在位图中没有记录,则可能被选中,接着计算其权重 */</span> peer = &rrp->peers->peer[i]; <span>/* 检查当前后端服务器的 down 标志位,若为 1 表示不参与策略选择,则 continue 检查下一个后端服务器 */</span><span>if</span> (peer->down) { <span>continue</span>; } <span>/* * 当前后端服务器的 down 标志位为 0,接着检查当前后端服务器连接失败的次数是否已经达到 max_fails; * 且睡眠的时间还没到 fail_timeout,则当前后端服务器不被选择,continue 检查下一个后端服务器; */</span><span>if</span> (peer->max_fails && peer->fails >= peer->max_fails && now - peer->checked <= peer->fail_timeout) { <span>continue</span>; } <span>/* 若当前后端服务器可能被选中,则计算其权重 */</span><span>/* * 在上面初始化过程中 current_weight = 0,effective_weight = weight; * 此时,设置当前后端服务器的权重 current_weight 的值为原始值加上 effective_weight; * 设置总的权重为原始值加上 effective_weight; */</span> peer->current_weight += peer->effective_weight; total += peer->effective_weight; <span>/* 服务器正常,调整 effective_weight 的值 */</span><span>if</span> (peer->effective_weight < peer->weight) { peer->effective_weight++; } <span>/* 若当前后端服务器的权重 current_weight 大于目前 best 服务器的权重,则当前后端服务器被选中 */</span><span>if</span> (best == <span>NULL</span> || peer->current_weight > best->current_weight) { best = peer; } } <span>if</span> (best == <span>NULL</span>) { <span>return</span><span>NULL</span>; } <span>/* 计算被选中后端服务器在服务器列表中的位置 i */</span> i = best - &rrp->peers->peer[<span>0</span>]; <span>/* 记录被选中后端服务器在 ngx_http_upstream_rr_peer_data_t 结构体 current 成员的值,在释放后端服务器时会用到该值 */</span> rrp->current = i; <span>/* 计算被选中后端服务器在位图中的位置 */</span> n = i / (<span>8</span> * <span>sizeof</span>(uintptr_t)); m = (uintptr_t) <span>1</span> << i % (<span>8</span> * <span>sizeof</span>(uintptr_t)); <span>/* 在位图相应的位置记录被选中后端服务器 */</span> rrp->tried[n] |= m; <span>/* 更新被选中后端服务器的权重 */</span> best->current_weight -= total; <span>if</span> (now - best->checked > best->fail_timeout) { best->checked = now; } <span>/* 返回被选中的后端服务器 */</span><span>return</span> best; }</code>
成功连接后端服务器并且正常处理完成客户端请求后需释放后端服务器,由函数 ngx_http_upstream_free_round_robin_peer
实现。
<code>/* 释放后端服务器 */ <span>void</span><span>ngx_http_upstream_free_round_robin_peer</span>(ngx_peer_connection_t *pc, void *<span><span>data</span>,</span> ngx_uint_t state) { ngx_http_upstream_rr_peer_data_t *rrp = <span><span>data</span>;</span> time_t now; ngx_http_upstream_rr_peer_t *peer; ngx_log_debug2(<span>NGX_LOG_DEBUG_HTTP</span>, pc->log, <span>0</span>, <span>"free rr peer %ui %ui"</span>, pc->tries, state); /* <span>TODO</span>: <span>NGX_PEER_KEEPALIVE</span> */ /* 若只有一个后端服务器,则设置 ngx_peer_connection_t 结构体成员 tries 为 <span>0</span>,并 return 返回 */ <span>if</span> (rrp->peers->single) { pc->tries = <span>0</span>; return; } /* 若不止一个后端服务器,则执行以下程序 */ /* 获取已经被选中的后端服务器 */ peer = &rrp->peers->peer[rrp->current]; /* * 若在本轮被选中的后端服务器在进行连接测试时失败,或者在处理请求过程中失败, * 则需要进行重新选择后端服务器; */ <span>if</span> (state & <span>NGX_PEER_FAILED</span>) { now = ngx_time(); /* ngx_lock_mutex(rrp->peers->mutex); */ peer->fails++;/* 增加当前后端服务器失败的次数 */ /* 设置当前后端服务器访问的时间 */ peer->accessed = now; peer->checked = now; <span>if</span> (peer->max_fails) { /* 由于当前后端服务器失败,表示发生异常,此时降低 effective_weight 的值 */ peer->effective_weight -= peer->weight / peer->max_fails; } ngx_log_debug2(<span>NGX_LOG_DEBUG_HTTP</span>, pc->log, <span>0</span>, <span>"free rr peer failed: %ui %i"</span>, rrp->current, peer->effective_weight); /* 保证 effective_weight 的值不能小于 <span>0</span> */ <span>if</span> (peer->effective_weight < <span>0</span>) { peer->effective_weight = <span>0</span>; } /* ngx_unlock_mutex(rrp->peers->mutex); */ } <span>else</span> {/* 若被选中的后端服务器成功处理请求,并返回,则将其 fails 设置为 <span>0</span> */ /* mark peer live <span>if</span> check passed */ /* 若 fail_timeout 时间已过,则将其 fails 设置为 <span>0</span> */ <span>if</span> (peer->accessed < peer->checked) { peer->fails = <span>0</span>; } } /* 减少 tries 的值 */ <span>if</span> (pc->tries) { pc->tries<span>--;</span> } /* ngx_unlock_mutex(rrp->peers->mutex); */ }</code>
IP 哈希
IP 哈希策略选择后端服务器时,将来自同一个 IP 地址的客户端请求分发到同一台后端服务器处理。在 Nginx 中,IP 哈希策略的一些初始化工作是基于加权轮询策略的,这样减少了一些工作。
Nginx 使用 IP 哈希负载均衡策略时,在进行策略选择之前由 ngx_http_upstream_init_ip_hash
函数进行全局初始化工作,其实该函数也是调用加权轮询策略的全局初始化函数。当一个客户端请求过来时,Nginx 将调用 ngx_http_upstream_init_ip_hash_peer()
为选择后端服务器处理该请求做初始化工作。在多次哈希选择失败后,Nginx 会将选择策略退化到加权轮询。
ngx_http_upstream_get_ip_hash_peer
函数会在选择后端服务器时计算客户端请求 IP 地址的哈希值,并根据哈希值得到被选中的后端服务器,判断其是否可用,如果可用则保存服务器地址,若不可用则在上次哈希选择结果基础上再次进行哈希选择。如果哈希选择失败次数达到 20 次以上,此时回退到采用轮询策略进行选择。
初始化后端服务器列表
初始化服务器列表工作是调用加权轮询策略的初始化函数,只是最后设置 IP 哈希的回调方法为 ngx_http_upstream_init_ip_hash_peer
。
<code><span>static</span> ngx_int_t ngx_http_upstream_init_ip_hash(ngx_conf_t *cf, ngx_http_upstream_srv_conf_t *us) { <span>/* 调用加权轮询策略的初始化函数 */</span><span>if</span> (ngx_http_upstream_init_round_robin(cf, us) != NGX_OK) { <span>return</span> NGX_ERROR; } <span>/* 由于 ngx_http_upstream_init_round_robin 方法的选择后端服务器处理客户请求的初始化函数 * 为 us->peer.init = ngx_http_upstream_init_round_robin_peer; */</span><span>/* 重新设置 ngx_http_upstream_peer_t 结构体中 init 的回调方法为 ngx_http_upstream_init_ip_hash_peer */</span> us->peer.init = ngx_http_upstream_init_ip_hash_peer; <span>return</span> NGX_OK; }</code>
选择后端服务器
选择后端服务器之前会调用函数 ngx_http_upstream_init_ip_hash_peer
进行一些服务器初始化工作。最终由函数 ngx_http_upstream_get_ip_hash_peer
进行 IP 哈希选择。
ngx_http_upstream_init_ip_hash_peer
函数执行流程:
ngx_http_upstream_init_round_robin_peer
;ngx_http_upstream_get_ip_hash_peer
;ngx_http_upstream_ip_hash_peer_data_t
结构体成员 hash 值为 89;tries 重试连接次数为 0;get_rr_peer
为加权轮询的决策函数 ngx_http_upstream_get_round_robin_peer
;ngx_http_upstream_get_ip_hash_peer
函数执行流程:
ngx_http_upstream_rr_peers_t
结构体中 weighted 标志位为 1,则被选中的后端服务器在后端服务器列表中的位置为 hash 值与后端服务器数量的余数 p;ngx_http_upstream_rr_peers_t
结构体中 weighted 标志位为 0,首先计算 hash 值与后端服务器总权重的余数 w; 将 w 值减去后端服务器的权重,直到有一个后端服务器使 w 值小于 0,则选中该后端服务器来处理请求,并记录在后端服务器列表中的位置 p;<code>static ngx_int_t ngx_http_upstream_init_ip_hash_peer(ngx_http_request_t <span>*</span>r, ngx_http_upstream_srv_conf_t <span>*</span>us) { struct sockaddr_in <span>*</span>sin; <span>#if</span> (NGX_HAVE_INET6) struct sockaddr_in6 <span>*</span>sin6; <span>#endif</span> ngx_http_upstream_ip_hash_peer_data_t <span>*</span>iphp; <span>/* 分配 ngx_http_upstream_ip_hash_peer_data_t 结构体内存空间 */</span> iphp <span>=</span> ngx_palloc(r<span>-></span>pool, sizeof(ngx_http_upstream_ip_hash_peer_data_t)); <span>if</span> (iphp <span>==</span><span>NULL</span>) { <span>return</span> NGX_ERROR; } r<span>-></span>upstream<span>-></span>peer<span>.</span><span>data</span><span>=</span><span>&</span>iphp<span>-></span>rrp; <span>/* 调用加权轮询策略的初始化函数 ngx_http_upstream_init_round_robin_peer */</span><span>if</span> (ngx_http_upstream_init_round_robin_peer(r, us) <span>!=</span> NGX_OK) { <span>return</span> NGX_ERROR; } <span>/* 设置 IP hash 的决策函数 */</span> r<span>-></span>upstream<span>-></span>peer<span>.</span>get <span>=</span> ngx_http_upstream_get_ip_hash_peer; switch (r<span>-></span>connection<span>-></span>sockaddr<span>-></span>sa_family) { <span>/* 保存客户端 IP 地址 */</span><span>/* IPv4 地址 */</span><span>case</span> AF_INET: sin <span>=</span> (struct sockaddr_in <span>*</span>) r<span>-></span>connection<span>-></span>sockaddr; iphp<span>-></span>addr <span>=</span> (u_char <span>*</span>) <span>&</span>sin<span>-></span>sin_addr<span>.</span>s_addr; iphp<span>-></span>addrlen <span>=</span><span>3</span>; break; <span>/* IPv6 地址 */</span><span>#if</span> (NGX_HAVE_INET6) <span>case</span> AF_INET6: sin6 <span>=</span> (struct sockaddr_in6 <span>*</span>) r<span>-></span>connection<span>-></span>sockaddr; iphp<span>-></span>addr <span>=</span> (u_char <span>*</span>) <span>&</span>sin6<span>-></span>sin6_addr<span>.</span>s6_addr; iphp<span>-></span>addrlen <span>=</span><span>16</span>; break; <span>#endif</span><span>/* 非法地址 */</span> default: iphp<span>-></span>addr <span>=</span> ngx_http_upstream_ip_hash_pseudo_addr; iphp<span>-></span>addrlen <span>=</span><span>3</span>; } <span>/* 初始化 ngx_http_upstream_ip_hash_peer_data_t结构体成员 */</span> iphp<span>-></span>hash <span>=</span><span>89</span>; iphp<span>-></span>tries <span>=</span><span>0</span>; <span>/* 这个是设置为加权轮询策略的决策函数 */</span> iphp<span>-></span>get_rr_peer <span>=</span> ngx_http_upstream_get_round_robin_peer; <span>return</span> NGX_OK; }</code>
<code>/* 选择后端服务器处理请求 */ <span>static</span> ngx_int_t <span>ngx_http_upstream_get_ip_hash_peer</span>(ngx_peer_connection_t *pc, void *<span><span>data</span>)</span> { ngx_http_upstream_ip_hash_peer_data_t *iphp = <span><span>data</span>;</span> time_t now; ngx_int_t w; uintptr_t m; ngx_uint_t i, n, p, hash; ngx_http_upstream_rr_peer_t *peer; ngx_log_debug1(<span>NGX_LOG_DEBUG_HTTP</span>, pc->log, <span>0</span>, <span>"get ip hash peer, try: %ui"</span>, pc->tries); /* <span>TODO</span>: cached */ /* 若重试连接的次数 tries 大于 <span>20</span>,或 只有一台后端服务器,则直接调用加权轮询策略选择当前后端服务器处理请求 */ <span>if</span> (iphp->tries > <span>20</span> || iphp->rrp.peers->single) { return iphp->get_rr_peer(pc, &iphp->rrp); } now = ngx_time(); pc->cached = <span>0</span>; pc->connection = <span>NULL</span>; hash = iphp->hash; for ( ;; ) { /* 计算 <span>IP</span> 地址的 hash 值 */ for (i = <span>0</span>; i < (ngx_uint_t) iphp->addrlen; i++) { hash = (hash * <span>113</span> + iphp->addr[i]) % <span>6271</span>;/* hash 函数 */ } /* 以下是根据 hash 值选择合适的后端服务器来处理请求 */ /* 若 ngx_http_upstream_rr_peers_t 结构体中 weighted 标志位为 <span>1</span>, * 表示所有后端服务器的总权重 与 后端服务器的数量 相等, * 则被选中的后端服务器在后端服务器列表中的位置为 hash 值与后端服务器数量的余数 p; */ <span>if</span> (!iphp->rrp.peers->weighted) { p = hash % iphp->rrp.peers->number; } <span>else</span> { /* 若 ngx_http_upstream_rr_peers_t 结构体中 weighted 标志位为 <span>0</span>, * 首先计算 hash 值与后端服务器总权重的余数 w; * 将 w 值减去后端服务器的权重,直到有一个后端服务器使 w 值小于 <span>0</span>, * 则选中该后端服务器来处理请求,并记录在后端服务器列表中的位置 p; */ w = hash % iphp->rrp.peers->total_weight; for (i = <span>0</span>; i < iphp->rrp.peers->number; i++) { w -= iphp->rrp.peers->peer[i].weight; <span>if</span> (w < <span>0</span>) { break; } } p = i; } /* 计算被选中后端服务器在位图中的位置 n */ n = p / (<span>8</span> * sizeof(uintptr_t)); m = (uintptr_t) <span>1</span> << p % (<span>8</span> * sizeof(uintptr_t)); /* 若当前被选中的后端服务器已经在位图记录过,则跳至 goto next 执行 */ <span>if</span> (iphp->rrp.tried[n] & m) { goto next; } ngx_log_debug2(<span>NGX_LOG_DEBUG_HTTP</span>, pc->log, <span>0</span>, <span>"get ip hash peer, hash: %ui %04XA"</span>, p, m); /* 获取当前被选中的后端服务器 */ peer = &iphp->rrp.peers->peer[p]; /* ngx_lock_mutex(iphp->rrp.peers->mutex); */ /* 检查当前被选中后端服务器的 down 标志位,若该标志位为<span>1</span>,则跳至 goto next_try 执行 */ <span>if</span> (peer->down) { goto next_try; } /* 若 down 标志位为 <span>0</span>,接着检查当前被选中后端服务器失败连接次数是否到达 max_fails, * 若已经达到 max_fails 次,并且睡眠时间还没到 fail_timeout,则跳至 goto next_try 执行; */ <span>if</span> (peer->max_fails && peer->fails >= peer->max_fails && now - peer->checked <= peer->fail_timeout) { goto next_try; } /* 若不满足以上条件,则表示选择后方服务器成功 */ break; next_try: /* 把当前后端服务器记录在位图中 */ iphp->rrp.tried[n] |= m; /* ngx_unlock_mutex(iphp->rrp.peers->mutex); */ /* 减少当前后端服务器重试连接的次数 */ pc->tries<span>--;</span> next: /* tries 重试连接的次数加 <span>1</span>,并判断 tries 是否大于阈值 <span>20</span>,若大于,则采用加权轮询策略 */ <span>if</span> (++iphp->tries >= <span>20</span>) { return iphp->get_rr_peer(pc, &iphp->rrp); } } /* 到此已经成功选择了后端服务器来处理请求 */ /* 记录当前后端服务器在后端服务器列表中的位置,该位置方便释放后端服务器调用 */ iphp->rrp.current = p; /* 记录当前后端服务器的地址信息 */ pc->sockaddr = peer->sockaddr; pc->socklen = peer->socklen; pc->name = &peer->name; <span>if</span> (now - peer->checked > peer->fail_timeout) { peer->checked = now; } /* ngx_unlock_mutex(iphp->rrp.peers->mutex); */ /* 把当前后端服务器记录在位图相应的位置 */ iphp->rrp.tried[n] |= m; /* 记录 hash 值 */ iphp->hash = hash; return <span>NGX_OK</span>; }</code>
总结
加权轮询策略:不依赖于客户端的任何信息,完全依靠后端服务器的情况来进行选择。但是同一个客户端的多次请求可能会被分配到不同的后端服务器进行处理,无法满足做会话保持的应用的需求。
IP哈希策略:把同一个 IP 地址的客户端请求分配到同一台服务器处理,避免了加权轮询无法适用会话保持的需求。但是来自同一的 IP 地址的请求比较多时,会导致某台后端服务器的压力可能非常大,而其他后端服务器却空闲的不均衡情况。
以上就介绍了Nginx 中 upstream 机制的负载均衡,包括了方面的内容,希望对PHP教程有兴趣的朋友有所帮助。