源码地址 https://github.com/gnosek/nginx-upstream-fair
一、模块简介:
fair模块会对全局的请求进行统计,并根据该服务器空闲状态,及处理过的请求及所有请求数进行权衡,实现负载均衡。
二、主要函数:
2.1、主要函数:
2.1.1、ngx_http_upstream_fair_try_peer:
该函数出现在idle调度和busy调度,会对一些符合条件的候选服务器中进行尝试。
尝试首先会判断该候选服务器是否已经进行了尝试,在未尝试且服务器未下线的情况下,会判断服务器的失败次数,如果服务器失败的次数在允许范围之内,会根据服务器上一次接受请求的时间与当前时间的时间差,判断上一次请求是否已经超时,如果已经超时,会重置该服务器的失败次数,同时返回尝试成功。
if (ngx_bitvector_test(fp->tried, peer_id))
return NGX_BUSY;
peer = &fp->peers->peer[peer_id];
if (!peer->down) {
if (peer->max_fails == 0 || peer->shared->fails < peer->max_fails) {
return NGX_OK;
}
if (ngx_time() - peer->accessed > peer->fail_timeout) {
ngx_log_debug3(NGX_LOG_DEBUG_HTTP, pc->log, 0, "[upstream_fair] resetting fail count for peer %d, time delta %d > %d",
peer_id, ngx_time() - peer->accessed, peer->fail_timeout);
peer->shared->fails = 0;
return NGX_OK;
}
}
2.1.2、ngx_http_upstream_fair_sched_score:
该函数出现在busy调度,根据候选服务器当前承载的请求和所有服务器的总请求数进行计算,获得该候选服务器的状况评分。
分数计算比较简单,用候选服务器的当前承载请求构成分数的高位,用候选服务器上一次请求和总请求之差(即空闲时间)的取反构成分数的低位,最后分数取小,即当前总请求数越少,优先级越高(暂不考虑weight),请求差越大,优先级越高。
SCHED_SCORE(ngx_upstream_fair_min(fs->nreq, SCHED_NREQ_MAX),
ngx_upstream_fair_min(req_delta, SCHED_COUNTER_MAX));
#define SCHED_COUNTER_BITS 20
#define SCHED_NREQ_MAX ((~0UL) >> SCHED_COUNTER_BITS)
#define SCHED_COUNTER_MAX ((1 << SCHED_COUNTER_BITS) - 1)
#define SCHED_SCORE(nreq,delta) (((nreq) << SCHED_COUNTER_BITS) | (~(delta) & SCHED_COUNTER_MAX))
#define ngx_upstream_fair_min(a,b) (((a) < (b)) ? (a) : (b))
三、代码流程:
1、 初始化(ngx_http_upstream_init_fair):
1.1、服务器初始化(ngx_http_upstream_init_fair_rr):
初始化的第一步是根据配置初始化所有配置的服务器,这里初始化和round robin初始化过程类似,初始化主备服务器,唯一的区别是fair会对权重进行排序。
如果没有配置,会按照代理的模式去搜索相应的域名,对域名解析出来的的地址进行默认的初始化。1.2、其余初始化:
对共享内存进行相应的初始化,以及一些函数指针的初始化。
2、节点选择(ngx_http_upstream_choose_fair_peer):
节点选择分为两步,一步选择idle(ngx_http_upstream_choose_fair_peer_idle),当没有任何可用的idle服务器时,将对所有busy的服务器进行选择(ngx_http_upstream_choose_fair_peer_busy)。
2.1、IDLE选择:
IDLE选择的时候,对于有失败过的服务器,或者当前服务器所承载的请求数过多(大于权重,不是WM_IDLE模式时候有请求就算?),就放弃选择该服务器。
当该服务器所承载的请求在可接受范围内,就会接着进行尝试,尝试部分和busy时候的尝试是相同的。
尝试成功之后,对于不是WM_IDLE且关闭轮询的情况下,会返回这个尝试成功的服务器。
在WM_IDLE和no_rr开启的情况下,会根据当前所承载的请求数来轮询选优,选择承载请求数最少的服务器。
2.2、BUSY选择:
在BUSY选择过程中,如果是WM_PEAK模式,会保证当前服务器承载的请求数小于权重的值。
接着对候选服务器进行尝试,尝试成功的话就去计算候选服务器的评分,并选择分数最低的服务器。
如果是WM_DEFAULT模式,计算出来的评分还会进行加权,这里权重的计算会根据失败次数而变化。最后实际分数会除以权值。fails越多,weight越小,最终分数越高,优先级越低。
if (peer->max_fails) {
ngx_uint_t mf = peer->max_fails;
weight = peer->shared->current_weight * (mf - peer->shared->fails) / mf;
}
if (weight > 0) {
sched_score /= weight;
}
2.3、后处理:
成功选择一台服务器以后,把相应的标志位置位,如果是WM_DEFAULT模式,会减少当前权重,防止busy时候,权重没有变化而导致总是选择了相同的服务器(比如nreq处理较快,总是比其他少一些,在基础评分时候总是优于其他同权值的服务器,如果权值还是没有变化,最终选择的总是该服务器)。
ngx_bitvector_set(fp->tried, best_idx);
if (weight_mode == WM_DEFAULT) {
ngx_http_upstream_fair_peer_t *peer = &fp->peers->peer[best_idx];
if (peer->shared->current_weight-- == 0) {
peer->shared->current_weight = peer->weight;
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, pc->log, 0, "[upstream_fair] peer %d expired weight, reset to %d", best_idx, peer->weight);
}
}
选优之后还需要更新相应节点的请求数,最后接受的请求,总请求数
peer->shared->last_req_id = fp->peers->shared->total_requests;
ngx_http_upstream_fair_update_nreq(fp, 1, pc->log);
peer->shared->total_req++;