【Nginx】负载均衡-加权轮询策略剖析
转自:江南烟雨
本文介绍的是客户端请求在多个后端服务器之间的均衡,注意与客户端请求在多个nginx进程之间的均衡相区别。
如果Nginx是以反向代理的形式配置运行,那么对请求的实际处理需要转发到后端服务器运行,如果后端服务器有多台,如何选择一台合适的后端服务器来处理当前请求,就是本文要说的负载均衡。这两种均衡互不冲突并且能同时生效。
负载均衡模块简介
负载均衡模块Load-balance是辅助模块,主要为Upstream模块服务,目标明确且单一:如何从多台后端服务器中选择出一台合适的服务器来处理当前请求
nginx负载均衡模块ngx_http_upstream_module允许定义一组服务器,这组服务器可以被proxy_pass,fastcgi_pass和memcached_pass这些指令引用。
配置例子:
upstream backend {
server backend1.example.com weight=;
server backend2.example.com:;
server unix:/tmp/backend3; server backup1.example.com: backup;
server backup2.example.com: backup;
} server {
location / {
proxy_pass http://backend;
}
}
upstream backend {
server backend1.example.com weight=;
server 127.0.0.1: max_fails= fail_timeout=30s;
server unix:/tmp/backend3;
}
默认情况下,请求被分散在使用加权轮询的nginx负载均衡服务器上。在上面的例子中,每七个请求按下面分配:五个请求发送给backend1.example.com,127.0.0.1:8080和unix:/tmp/backend3各自分配一个。如果在与服务器通信时发生了一个错误,这个请求会被传递给下一个服务器,以此类推直到所有的功能服务器都尝试过。如果不能从所有的这些nginx负载均衡服务器上获得回应,客户端将会获得最后一个链接的服务器的处理结果。
语法:server 地址 [参数];
upstream backend {
server backend1.example.com weight=;
server 127.0.0.1: max_fails= fail_timeout=30s;
server unix:/tmp/backend3; server backup1.example.com: backup;
}
upstream backend {
ip_hash; server backend1.example.com;
server backend2.example.com;
server backend3.example.com down;
server backend4.example.com;
}
负载均衡策略
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:;
server localhost:;
}
当整个http配置块被Nginx解析完毕之后,会调用各个http模块对应的初始函数。对于模块ngx_http_upstream_module而言, 对应的main配置初始函数是ngx_http_upstream_init_main_conf(),在这个函数中有这样一段代码:
for (i = ; 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;
}
}
默 认采用加权轮询策略的原因就是在于上述代码中的init赋值一行。如果用户没有做任何策略选择,那么执行的策略初始函数为 ngx_http_upstream_init_round_robin,也就是加权轮询策略。否则的话执行的是 uscfp[i]->peer.init_upstream指针函数,如果有配置执行ip_hash ,那么就是ngx_http_upstream_init_ip_hash()。
加权轮询策略
全局准备工作
ngx_http_upstream_init_round_robin()函数:
将非后备服务器与后备服务器分开进行各自单独的链表。每一个后端服务器用一个结构体ngx_http_upstream_rr_peer_t与之对应。
首先是设置了一个回调指针(init),这个函数(init)用来针对每个请求选择后端服务器之前做一些初始化工作。
us->peer.init = ngx_http_upstream_init_round_robin_peer;遍历所有服务器,统计非备用服务器的数量及总的权重计算。为非后备服务器分配空间,设置非后备服务器列表头中各属性及服务器项。
然后对备用服务器数量进行统计并计算总的权重,为后备服务器分配空间,设置后备服务器列表头中各属性及服务器项。
若us参数中服务器指针为空,例如用户直接在proxy_pass等指令后配置后端服务器地址即没有设置后端服务器,返回NGX_ERROR
在配置解析的过程中,这些选项设置都被转换为Nginx内对应的变量值,对应的结构体ngx_http_upstream_server_t如下(ngx_http_upstream.h):
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:;
unsigned backup:;
} ngx_http_upstream_server_t;
这个阶段的函数是ngx_http_upstream_init_round_robin(),其主要完成的工作详见表1.
首先是设置了一个回调指针(init),这个函数(init)用来针对每个请求选择后端服务器之前做一些初始化工作:
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 */
};
而ngx_http_upstream_peer_t :
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:; //是否为指监听类的设置
unsigned uri_part:;
unsigned no_resolve:; //根据情况决定是否解析域名(将域名解析到IP地址)
unsigned one_addr:; //等于1时,仅有一个IP地址 unsigned no_port:; //标识url中没有显示指定端口(为1时没有指定)
unsigned wildcard:; //标识是否使用通配符(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表示后端服务器总共只有一台,用于优化,此时不需要再做选择
unsigned weighted:;//为1表示总的权重值等于服务器数量 ngx_str_t *name; ngx_http_upstream_rr_peers_t *next;//后备服务器列表挂载在这个字段下 ngx_http_upstream_rr_peer_t peer[];
};
函数的完整代码如下:
//函数:初始化服务器负载均衡表
//参数:
//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 = ;
w = ; //遍历所有服务器
for (i = ; i < us->servers->nelts; i++) {
//是后备服务器,跳过
if (server[i].backup) {
continue;
} //服务器地址数量统计
n += server[i].naddrs;
//总的权重计算
w += server[i].naddrs * server[i].weight;
} if (n == ) {
ngx_log_error(NGX_LOG_EMERG, cf->log, ,
"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 - ));
if (peers == NULL) {
return NGX_ERROR;
} //非后备服务器列表头中各属性设置
peers->single = (n == );
peers->number = n;
peers->weighted = (w != n);
peers->total_weight = w;
peers->name = &us->host; n = ; //后备服务器列表中各服务器项设置
for (i = ; i < us->servers->nelts; i++) {
for (j = ; 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 = ;
n++;
}
} //非后备服务器列表挂载的位置
us->peer.data = peers; /* backup servers */
//后备服务器
n = ;
w = ; for (i = ; i < us->servers->nelts; i++) {
if (!server[i].backup) {
continue;
}
//后备服务器地址数量统计
n += server[i].naddrs;
//后备服务器总权重计算
w += server[i].naddrs * server[i].weight;
} if (n == ) {
return NGX_OK;
} //后备服务器列表地址空间分配
backup = ngx_pcalloc(cf->pool, sizeof(ngx_http_upstream_rr_peers_t)
+ sizeof(ngx_http_upstream_rr_peer_t) * (n - ));
if (backup == NULL) {
return NGX_ERROR;
} peers->single = ;
//后备服务器列表头中各属性设置
backup->single = ;
backup->number = n;
backup->weighted = (w != n);
backup->total_weight = w;
backup->name = &us->host; n = ; //后备服务器列表中各服务器项设置
for (i = ; i < us->servers->nelts; i++) {
for (j = ; 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 = ;
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 == ) {
ngx_log_error(NGX_LOG_EMERG, cf->log, ,
"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, ,
"%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 - ));
if (peers == NULL) {
return NGX_ERROR;
} peers->single = (n == );
peers->number = n;
peers->weighted = ;
peers->total_weight = n;
peers->name = &us->host; for (i = ; 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 = ;
peers->peer[i].effective_weight = ;
peers->peer[i].current_weight = ;
peers->peer[i].max_fails = ;
peers->peer[i].fail_timeout = ;
} 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 = ; //n取值为:非后备服务器和后备服务器列表中个数较大的那个值
n = rrp->peers->number; if (rrp->peers->next && rrp->peers->next->number > n) {
n = rrp->peers->next->number;
} //如果n小于一个指针变量所能表示的范围
if (n <= * sizeof(uintptr_t)) {
//直接使用已有的指针类型的data变量做位图(tried是位图,用来标识在一轮选择中,各个后端服务器是否已经被选择过)
rrp->tried = &rrp->data;
rrp->data = ; } else {
//否则从内存池中申请空间
n = (n + ( * sizeof(uintptr_t) - )) / ( * 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_http_upstream_get_round_robin_peer内,流程图如下:
代码如下:
//函数:
//功能:对后端服务器做一次选择
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, ,
"get rr peer, try: %ui", pc->tries); /* ngx_lock_mutex(rrp->peers->mutex); */ pc->cached = ;
pc->connection = NULL; //如果只有一台后端服务器,Nginx直接选择并返回
if (rrp->peers->single) {
peer = &rrp->peers->peer[]; 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, ,
"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 == && 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, , "backup servers"); rrp->peers = peers->next;
pc->tries = rrp->peers->number; n = (rrp->peers->number + ( * sizeof(uintptr_t) - ))
/ ( * sizeof(uintptr_t)); for (i = ; i < n; i++) {
rrp->tried[i] = ;
} 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 = ; i < peers->number; i++) {
peers->peer[i].fails = ;
} /* 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 = ; for (i = ; i < rrp->peers->number; i++) {
//计算当前服务器的标记位在位图中的位置
n = i / ( * sizeof(uintptr_t));
m = (uintptr_t) << i % ( * 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[]; rrp->current = i; n = i / ( * sizeof(uintptr_t));
m = (uintptr_t) << i % ( * sizeof(uintptr_t)); //位图相应位置置位
rrp->tried[n] |= m; best->current_weight -= total;
best->checked = now; return best;
}
整个加权轮询的流程图如下:
说明:
首先是全局初始化,由函数ngx_http_upstream_init_round_robin完成,它在函数ngx_http_upstream_init_main_conf中被调用,代码:
static char *
ngx_http_upstream_init_main_conf(ngx_conf_t *cf, void *conf)
{
...
for (i = ; 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);
}
轮询的创建过程,代码如下图所示:
这里有个tried变量需要做些说明。tried中记录了服务器当前是否被尝试连接过。他是一个位图。如果服务器数量小于32,则只需在一个int中即可记录下所有服务器状态。如果服务器数量大于32,则需在内存池中申请内存来存储。
对该位图数组的使用可参考如下代码:
然后是针对每个请求选择后端服务器,实现此功能的函数是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, );
...
}
.----------------------------------------------------------------------------------------------------------
后端服务器权值计算算法:
表示后端服务器的ngx_http_upstream_rr_peer_t结构体中的一下三个成员变量:
ngx_int_t current_weight;
ngx_int_t effective_weight;
ngx_int_t weight;
核心代码如下:
foreach peer in peers {
peer->current_weight += peer->effective_weight;
total += peer->effective_weight; if (best == NULL || peer->current_weight > best->current_weight) {
best = peer;
}
}
best->current_weight -= total;
关于effective_weight的变化,有两处,一个是在函数ngx_http_upstream_get_peer中:
//服务正常,effective_weight 逐渐恢复正常
if (peer->effective_weight < peer->weight) {
peer->effective_weight++;
}
另一处是在释放后端服务的函数ngx_http_upstream_free_round_robin_peer中:
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 } |
.参考:
http://baidutech.blog.51cto.com/4114344/1033718/
【Nginx】负载均衡-加权轮询策略剖析的更多相关文章
- Nginx 负载均衡-加权轮询策略剖析
本文介绍的是客户端请求在多个后端服务器之间的均衡,注意与客户端请求在多个nginx进程之间的均衡相区别(Nginx根据每个工作进程的当前压力调整它们获取监听套接口的几率,那些当前比较空闲的工作进程有更 ...
- nginx负载均衡 加权轮询和ip_hash
下面给大家总结了几种真正的nginx负载均衡的功能了,在此我们加了一个权重判断法就是根据nginx负载的状态实现分配访问用户到权重值少的机器了,具体配置如下. nginx为后端web服务器(apach ...
- Nginx的负载均衡 - 加权轮询 (Weighted Round Robin) 下篇
Nginx版本:1.9.1 我的博客:http://blog.csdn.net/zhangskd 上篇blog讲述了加权轮询算法的原理.以及负载均衡模块中使用的数据结构,接着我们来看看加权轮询算法的具 ...
- Nginx的负载均衡 - 加权轮询 (Weighted Round Robin) 上篇
Nginx版本:1.9.1 我的博客:http://blog.csdn.net/zhangskd 算法介绍 来看一个简单的Nginx负载均衡配置. http { upstream cluster { ...
- 【Nginx】负载均衡-IP哈希策略剖析
转自:江南烟雨 IP哈希初始化 IP哈希的初始化函数ngx_http_upstream_init_ip_hash(ngx_http_upstream_ip_hash_module.c): static ...
- Nginx负载均衡的5种策略(转载)
Nginx的upstream目前支持的5种方式的分配 轮询(默认) 每个请求按时间顺序逐一分配到不同的后端服务器,如果后端服务器down掉,能自动剔除. upstream backserver { s ...
- Java实现负载均衡算法--轮询和加权轮询
1.普通轮询算法 轮询(Round Robin,RR)是依次将用户的访问请求,按循环顺序分配到web服务节点上,从1开始到最后一台服务器节点结束,然后再开始新一轮的循环.这种算法简单,但是没有考虑到每 ...
- nginx负载均衡的5种策略及原理
版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/qq_35119422/article/de ...
- 关于Nginx负载均衡的5种策略
nginx的upstream目前支持的5种方式的分配 1.轮询(默认) 每个请求按时间顺序逐一分配到不同的后端服务器,如果后端服务器down掉,能自动剔除. upstream backserver { ...
随机推荐
- ascii - 在八进制,十进制,十六进制中的 ASCII 字符集编码
描述 ASCII 是美国对于信息交换的标准代码,它是7位码,许多8位码(比如 ISO 8859-1, Linux 的默认字符集)容纳 ASCII 作为它们的下半部分.对应的国际 ASSII 是 ISO ...
- SVG 浏览器支持
可以参考以下链接: https://caniuse.com/#search=svg https://en.wikipedia.org/wiki/Comparison_of_layout_engines ...
- 【转】c++数组初始化
数组初始化列表中的元素个数小于指定的数组长度时,不足的元素补以默认值. 原文:C/C++数组初始化的一些误区 以前我这样初始化一个数组,并自我感觉良好: ] = { }; // 全部初始化为0 这种简 ...
- idea文件全部变红, 文件全部红色
idea如果当前project用了版本控制器,其下面新建的所有的项目默认都是加入到版本控制里面,所以项目名称和文件都是红色的,如图: 看起来非常不爽, 那么如何解决呢? File–>Settin ...
- 服务器中打开IIS管理器
1.选远程连接服务器,然后开始>控制面板>打开或关闭Windows功能>服务器管理器>web服务器>internet信息服务的展开下一项即可,如图:
- python selenium定位总结(转)
转自:http://www.cnblogs.com/yufeihlf/p/5717291.html 父子定位元素 查找有父亲元素的标签名为span,它的所有标签名叫input的子元素 find_ele ...
- Linux从入门到适应(四):Ubuntu 16.04环境下,安装Nvidia驱动,cuda9.2和 cudnn
在安装深度学习框架之前,cuda和cudnn是必须要提前安装的,现在按照流程而nvidia驱动的版本和cuda版本有这一些对应关系,所以需要按照版本进行安装,现在说一下如何安装: 1 安装nvidia ...
- BZOJ1001 狼抓兔子 平面图转对偶图 最小割
现在小朋友们最喜欢的"喜羊羊与灰太狼",话说灰太狼抓羊不到,但抓兔子还是比较在行的,而且现在的兔子还比较笨,它们只有两个窝,现在你做为狼王,面对下面这样一个网格的地形: 左上角点为 ...
- Git的入门
Git的基本介绍: Git:是一个版本控制工具. Github:是非常有名的在线版本管理网站(速度比较慢). Oschina:中国版本的github,(旗下的的码云地址:gitee.com,速度快) ...
- 初识 Bootstrap
Bootstrap 概述 Bootstrap 是一个前端框架,使用它可以快速开发响应式页面,还能专门针对 PC 端或移动端快速开发,大大提高了开发效率. Bootstrap 是最受欢迎的 HTML.C ...