- 负载均衡:将负载尽量均衡的分摊到多个不同的后端服务单元,以保证服务的可用性和可靠性;
- upstream:proxy和upstream模块
nginx默认采用robin round策略,详情参考Nginx学习之十二-负载均衡-加权轮询策略剖析
一、主体结构
upstream backend {
server 192.168.0.1:8080 weight=2 max_fails=3;
server 192.168.0.2:8080 weight=1 max_fails=3;
}
- 解析server项,
ngx_http_upstream_server()
,指令详情参考:Server; - upstream配置加载后的初始化函数
ngx_http_upstream_init_main_conf()
,调用RobinRound初始化函数ngx_http_upstream_init_round_robin()
; ngx_http_upstream_init_round_robin()
,初始化upstream中的每个server为peer,填充peer的权重、最大失败次数、超时、是否失败,并设置请求初始化函数us->peer.init = ngx_http_upstream_init_round_robin_peer
;- 每次请求都会调用
us->peer.init()
,实际调用ngx_http_upstream_init_round_robin_peer()
,创建tried和tries变量,其中tried表示peer已使用的bit图,tries表示本次请求获取后端的次数。并设置了peer的获取、释放句柄。
该执行入口在ngx_http_upstream_init_request()
upstream请求初始化函数尾部; - 获取后端服务器
ngx_http_upstream_get_round_robin_peer()
,负载均衡策略获取一个后端务器返回,入口函数为ngx_http_upstream_connect()
==>ngx_event_connect_peer()
; - 当遇到错误或与后端通信结束,需要释放后端服务器
ngx_http_upstream_free_round_robin_peer()
,记录一些状态,以供若遇到错误时可以获取下一个后端服务器。入口函数为ngx_http_upstream_next()
获取下一个peer或ngx_http_upstream_finalize_request()
upstream结束请求。
二、获取peer
获取一个后端服务器,若后端服务器只有一个,直接返回即可,但若有多个供选择,则调用ngx_http_upstream_get_peer()
。RoundRobin用tried位图记录后端服务器是否访问过,uintptr_t数组,每个元素8bit记录8个服务器,所以服务器i的状态表示为
n = i / (8 * sizeof(uintptr_t));
m = (uintptr_t) 1 << i % (8 * sizeof(uintptr_t));
if (tried[n] & m) {
// 表示i服务器已访问过
}
负载均衡策略为Weighted Round Robin,遍历所有可用服务器(非down,fails < max_fails),权重累加为total,同时挑出权重最大的peer,并将该peer的当时权重减去total,返回该peer。
三、释放peer
释放正在使用的后端服务器,如果只有一个后端直接释放即可,若有多个后端则
// 如果请求出现异常,则会进入该分支
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;
}
if (peer->effective_weight < 0) {
peer->effective_weight = 0;
}
} else { // 如果请求正常关闭
if (peer->accessed < peer->checked) {
peer->fails = 0;
}
}
if (pc->tries) {
pc->tries--;
}
当该peer访问失败时,会使失败计数器加1,记录当时时间到时accessed、checked变量:
- accessed,表示该peer最近访问失败时的时间;
- checked,表示该peer最近访问失败时的时间或从“死”到“活”时的活时间,如果checked > accessed,则会对失败计数器清零。
所以有如下场景:后端服务器A失败次数达到max_fails,并时间checked未超过fail_timeout,则该peer不会在候选列表(if … continue)。冷却时间到,该peer又回到候选列表中,且被选中则更新checked。若peer又“活”过来能进行正常请求,则在peer释放环节checked>accessed
将失败计数器清零,恢复正常。