Nginx的负载均衡 - 最少连接 (least_conn)
Nginx版本:1.9.1
我的博客:http://blog.csdn.net/zhangskd
算法介绍
我们知道轮询算法是把请求平均的转发给各个后端,使它们的负载大致相同。
这有个前提,就是每个请求所占用的后端时间要差不多,如果有些请求占用的时间很长,会导致其所在的后端
负载较高。在这种场景下,把请求转发给连接数较少的后端,能够达到更好的负载均衡效果,这就是least_conn算法。
least_conn算法很简单,首选遍历后端集群,比较每个后端的conns/weight,选取该值最小的后端。
如果有多个后端的conns/weight值同为最小的,那么对它们采用加权轮询算法。
指令的解析函数
在一个upstream配置块中,如果有least_conn指令,表示使用least connected负载均衡算法。
least_conn指令的解析函数为ngx_http_upstream_least_conn,主要做了:
指定初始化此upstream块的函数uscf->peer.init_upstream
指定此upstream块中server指令支持的属性
static char *ngx_http_upstream_least_conn(ngx_conf_t *cf, ngx_command_t *cmd, void *ctx)
{
ngx_http_upstream_srv_conf_t *uscf; /* 获取所在的upstream{}块 */
uscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_upstream_module); if (uscf->peer.init_upstream)
ngx_conf_log_error(NGX_LOG_WARN, cf, 0, "load balancing method redefined"); /* 此upstream块的初始化函数 */
uscf->peer.init_upstream = ngx_http_upstream_init_least_conn; /* 指定此upstream块中server指令支持的属性 */
uscf->flags = NGX_HTTP_UPSTREAM_CREATE
| NGX_HTTP_UPSTREAM_WEIGHT
| NGX_HTTP_UPSTREAM_MAX_FAILS
| NGX_HTTP_UPSTREAM_FAIL_TIMEOUT
| NGX_HTTP_UPSTREAM_DOWN
| NGX_HTTP_UPSTREAM_BACKUP; return NGX_CONF_OK;
}
以下是upstream块中server指令可支持的属性
NGX_HTTP_UPSTREAM_CREATE:检查是否重复创建,以及必要的参数是否填写
NGX_HTTP_UPSTREAM_WEIGHT:server指令支持weight属性
NGX_HTTP_UPSTREAM_MAX_FAILS:server指令支持max_fails属性
NGX_HTTP_UPSTREAM_FAIL_TIMEOUT:server指令支持fail_timeout属性
NGX_HTTP_UPSTREAM_DOWN:server指令支持down属性
NGX_HTTP_UPSTREAM_BACKUP:server指令支持backup属性
初始化upstream块
执行完指令的解析函数后,紧接着会调用所有HTTP模块的init main conf函数。
在执行ngx_http_upstream_module的init main conf函数时,会调用所有upstream块的初始化函数。
对于使用least_conn的upstream块,其初始化函数(peer.init_upstream)就是上一步中指定
ngx_http_upstream_init_least_conn,它主要做了:
调用round robin的upstream块初始化函数来创建和初始化后端集群,保存该upstream块的数据
指定per request的负载均衡初始化函数peer.init
因为脏活累活都让round robin的upstream块初始化函数给干了,所以ngx_http_upstream_init_least_conn很简单。
static ngx_int_t ngx_http_upstream_init_least_conn(ngx_conf_t *cf, ngx_http_upstream_srv_conf_t *us)
{
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, cf->log, 0, "init least conn"); /* 使用round robin的upstream块初始化函数,创建和初始化后端集群 */
if (ngx_http_upstream_init_round_robin(cf, us) != NGX_OK)
return NGX_ERROR; /* 重新设置per request的负载均衡初始化函数 */
us->peer.init = ngx_http_upstream_init_least_conn_peer; return NGX_OK;
}
初始化请求的负载均衡数据
收到一个请求后,一般使用的反向代理模块(upstream模块)为ngx_http_proxy_module,
其NGX_HTTP_CONTENT_PHASE阶段的处理函数为ngx_http_proxy_handler,在初始化upstream机制的
ngx_http_upstream_init_request函数中,调用在第二步中指定的peer.init,主要用于初始化请求的负载均衡数据。
对于least_conn,peer.init实例为ngx_http_upstream_init_least_conn_peer,主要做了:
调用round robin的peer.init来初始化请求的负载均衡数据
重新指定peer.get,用于从集群中选取一台后端服务器
least_conn的per request负载均衡数据和round robin的完全一样,都是一个ngx_http_upstream_rr_peer_data_t实例。
static ngx_int_t ngx_http_upstream_init_least_conn_peer(ngx_http_request_t *r, ngx_http_upstream_srv_conf_t *us)
{
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "init least conn peer"); /* 调用round robin的per request负载均衡初始化函数 */
if (ngx_http_upstream_init_round_robin_peer(r, us) != NGX_OK)
return NGX_ERROR; /* 指定peer.get,用于从集群中选取一台后端 */
r->upstream->peer.get = ngx_http_upstream_get_least_conn_peer; return NGX_OK;
}
选取一台后端服务器
一般upstream块中会有多台后端,那么对于本次请求,要选定哪一台后端呢?
这时候第三步中r->upstream->peer.get指向的函数就派上用场了:
采用least connected算法,从集群中选出一台后端来处理本次请求。 选定后端的地址保存在pc->sockaddr,pc为主动连接。
函数的返回值:
NGX_DONE:选定一个后端,和该后端的连接已经建立。之后会直接发送请求。
NGX_OK:选定一个后端,和该后端的连接尚未建立。之后会和后端建立连接。
NGX_BUSY:所有的后端(包括备份集群)都不可用。之后会给客户端发送502(Bad Gateway)。
static ngx_int_t ngx_http_upstream_get_least_conn_peer(ngx_peer_connection_t *pc, void *data)
{
ngx_http_upstream_rr_peer_data_t *rrp = data; /* 请求的负载均衡数据 */
time_t now;
uintptr_t m;
ngx_int_t rc, total;
ngx_uint_t i, n, p, many;
ngx_http_upstream_rr_peer_t *peer, *best;
ngx_http_upstream_rr_peers_t *peers;
...
/* 如果集群只包含一台后端,那么就不用选了 */
if (rrp->peers->single)
return ngx_http_upstream_get_round_robin_peer(pc, rrp); pc->cached = 0;
pc->connection = NULL;
now = ngx_time();
peers = rrp->peers; /* 后端集群 */
best = NULL;
total = 0;
...
/* 遍历后端集群 */
for (peer = peers->peer, i = 0; peer; peer = peer->next, i++)
{
/* 检查此后端在状态位图中对应的位,为1时表示不可用 */
n = i / (8 * sizeof(uintptr_t));
m = (uintptr_t) 1 << i % (8 * sizeof(uintptr_t)); if (rrp->tried[n] & m)
continue; /* server指令中携带了down属性,表示后端永久不可用 */
if (peer->down)
continue; /* 在一段时间内,如果此后端服务器的失败次数,超过了允许的最大值,那么不允许使用此后端了 */
if (peer->max_fails && peer->fails >= peer->max_fails
&& now - peer->checked <= peer->fail_timeout)
continue; /* select peer with least number of connections; if there are multiple peers
* with the same number of connections, select based on round-robin.
*/
/* 比较各个后端的conns/weight,选取最小者;
* 如果有多个最小者,记录第一个的序号p,且设置many标志。
*/
if (best == NULL || peer->conns * best->weight < best->conns * peer->weight)
{
best = peer;
many = 0;
p = i;
} else if (peer->conns * best->weight == best->conns * peer->weight)
many = 1;
} /* 找不到可用的后端 */
if (best == NULL)
goto failed; /* 如果有多个后端的conns/weight同为最小者,则对它们使用轮询算法 */
if (many) {
for (peer = best, i = p; peer; peer->peer->next, i++)
{
/* 检查此后端在状态位图中对应的位,为1时表示不可用 */
n = i / (8 * sizeof(uintptr_t));
m = (uintptr_t) 1 << i % (8 * sizeof(uintptr_t)); /* server指令中携带了down属性,表示后端永久不可用 */
if (peer->down)
continue; /* conns/weight必须为最小的 */
if (peer->conns * best->weight != best->conns * peer->weight)
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;
}
}
} best->current_weight -= total; /* 如果使用轮询,要降低选定后端的当前权重 */ /* 更新checked时间 */
if (now - best->checked > best->fail_timeout)
best->checked = now; /* 保存选定的后端服务器的地址,之后会向这个地址发起连接 */
pc->sockaddr = best->sockaddr;
pc->socklen = best->socklen;
pc->name = &best->name; best->conns++; /* 增加选定后端的当前连接数 */ n = p / (8 * sizeof(uintptr_t));
m = (uintptr_t) 1 << p % (8 * sizeof(uintptr_t));
rrp->tried[n] |= m; /* 对于此请求,如果之后需要再次选取后端,不能再选取这个后端了 */ 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_least_conn_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;
}
Nginx的负载均衡 - 最少连接 (least_conn)的更多相关文章
- Nginx的负载均衡 - 整体架构
Nginx的负载均衡 - 整体架构 Nginx版本:1.9.1 我的博客:http://blog.csdn.net/zhangskd Nginx目前提供的负载均衡模块: ngx_http_upstre ...
- Nginx实现负载均衡&Nginx缓存功能
一.Nginx是什么 Nginx (engine x) 是一个高性能的HTTP和反向代理服务器,也是一个IMAP/POP3/SMTP服务器.Nginx是由伊戈尔·赛索耶夫为俄罗斯访问量第二的Rambl ...
- 死磕nginx系列--使用nginx做负载均衡
使用nginx做负载均衡的两大模块: upstream 定义负载节点池. location 模块 进行URL匹配. proxy模块 发送请求给upstream定义的节点池. upstream模块解读 ...
- 关于Nginx的负载均衡
一.关于Nginx的负载均衡 在服务器集群中,Nginx起到一个代理服务器的角色(即反向代理),为了避免单独一个服务器压力过大,将来自用户的请求转发给不同的服务器.详情请查看我的另一篇博客. 二.Ng ...
- Nginx搭建负载均衡集群
(1).实验环境 youxi1 192.168.5.101 负载均衡器 youxi2 192.168.5.102 主机1 youxi3 192.168.5.103 主机2 (2).Nginx负载均衡策 ...
- NginX——配置负载均衡
A. 在http模块加上upstream配置 upstream www.myweb.com { server 127.0.0.1:9100 weight=3; server ...
- nginx实现负载均衡、缓存功能实战
nginx实现负载均衡.缓存功能实战 什么是正向代理?应用场景:翻墙 什么是反向代理?例如:haproxy和nginx Nginx实现反向代理 nginx代理基于是ngx_http_proxy_m ...
- 如何利用nginx实现负载均衡(总结)
如何利用nginx实现负载均衡(总结) 一.总结 一句话总结: 推荐使用nginx七层(应用层)负载均衡的实现:配置那是相当的简单 1.nginx配置实例? |||-begin #这里的域名要和下面p ...
- Nginx之负载均衡配置(一)
前文我们聊了下nginx作为反向代理服务器,代理后端动态应用服务器的配置,回顾请参考https://www.cnblogs.com/qiuhom-1874/p/12430543.html:今天我们来聊 ...
随机推荐
- Evensgn 捡树枝
问题 A: Evensgn 剪树枝 时间限制: 1 Sec 内存限制: 128 MB 题目描述 繁华中学有一棵苹果树.苹果树有 n 个节点(也就是苹果),n − 1 条边(也就 是树枝).调皮的 E ...
- ●BZOJ 1076 [SCOI2008]奖励关
题链: http://www.lydsy.com/JudgeOnline/problem.php?id=1076题解: 期望dp. (模糊的题意,2333) 题中的:"现在决定不吃的宝物以后 ...
- ●BZOJ 2154 Crash的数字表格
题链: http://www.lydsy.com/JudgeOnline/problem.php?id=2154 题解: 莫比乌斯反演. 题意还是很清楚的,就不赘述了. 显然有 $ANS=\sum_{ ...
- k-d tree模板练习
1. [BZOJ]1941: [Sdoi2010]Hide and Seek 题目大意:给出n个二维平面上的点,一个点的权值是它到其他点的最长距离减最短距离,距离为曼哈顿距离,求最小权值.(n< ...
- C++ 中私有继承、保护继承与公有继承
区别 下面通过一个示例来介绍三种继承的区别. 定义一个基类(假设为一个快退休的富豪): class RichMan { public: RichMan(); ~RichMan(); int m_com ...
- jqGrid 使用心得
参考: https://blog.csdn.net/u012411219/article/details/51315419 https://www.cnblogs.com/kissdodog/p/38 ...
- React .js框架的环境搭建
React学习笔记(一)- 环境搭建 最近在学习react相关的知识,刚刚起步,一路遇坑不断.自己做个笔记,方便日后总结,也供相同趣味的小伙伴一起交流探讨. 学习时主要参考官网的教程:https: ...
- JavaScript正则表达式模式匹配(5)——特殊字符匹配、换行模式
特殊字符匹配 var pattern=/\[/; // 用\符号来转义正则里的特殊字符才能匹配 var str='['; alert(pattern.test(str)); 换行模式 var patt ...
- Python OptionParser 使用详解(转载)
Python使用命令行参数能使处理流程更自动化. 链接的内容讲解得十分详细:https://www.tuicool.com/articles/rUvIbi
- C++雾中风景8:Lambda表达式
上一篇C++的博客是Long Long ago了,前文讲到在看Lambda表达式的内容.笔者首次接触Lambda表达式应该是学习Python语言的时候,当时也不太明白这种表达方式的精髓,后续接触了Sc ...