关闭

Nginx的负载均衡 - 最少连接 (least_conn)

标签: Nginx负载均衡
9539人阅读 评论(0) 收藏 举报
分类:

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;
}

 

 

2
0
查看评论
发表评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场

Nginx 负载均衡

1.负载均衡概览;2.代理流量到服务器组;3.选择一个负载均衡方法;4.服务器权值;5.服务器慢启动;6.开启会话持久;7.限制连接数目;8.被动的健康检测;9.主动的健康检测;10.多工作进程共享数...
  • agangdi
  • agangdi
  • 2014-11-13 21:05
  • 6200

nginx如何做到TCP的负载均衡

如有转载,请声明出处: 极致博客: http://blog.csdn.net/u011218159 TCP 的 负载均衡 这个片段描述了如何通过nginx插件进行负载均衡 在版本5中,n...
  • u011218159
  • u011218159
  • 2016-03-23 21:47
  • 12248

Nginx VS HaProxy

个人制作的Nginx和Haproxy的简单对比,包括我之前测试的程序性能、并发数量、负载算法、会话保持、健康检查、SSL卸载等等功能的支持程度。
  • lushuai93
  • lushuai93
  • 2016-03-07 15:11
  • 400

Nginx 限制连接数与请求数

nginx限制连接数ngx_http_limit_conn_module模块 一. 前言 我们经常会遇到这种情况,服务器流量异常,负载过大等等。对于大流量恶意的攻击访问,会带来带宽的浪费...
  • u010391029
  • u010391029
  • 2016-05-13 12:03
  • 7684

nginx 查看 并发连接数

通过查看Nginx的并发连接,我们可以更清除的知道网站的负载情况。Nginx并发查看有两种方法(之所以这么说,是因为笔者只知道两种),一种是通过web界面,一种是通过命令,web查看要比命令查看显示的...
  • xiaofeng119
  • xiaofeng119
  • 2014-04-17 13:47
  • 17297

Nginx:承受3万并发连接数,胜过Apache 10倍

编者按:Nginx是目前比较重要的开源性负载均衡技术,新浪、网易、六间房等很多网站都将Nginx部署进自己的网站系统架构,并解决部分问题。本文是作者长期的实战经验,很有参考价值。 本文是我撰写的...
  • zxxSsdsd
  • zxxSsdsd
  • 2016-09-04 12:06
  • 4382

Nginx的负载均衡 - 保持会话 (ip_hash)

Nginx版本:1.9.1 我的博客:http://blog.csdn.net/zhangskd   算法介绍   ip_hash算法的原理很简单,根据请求所属的客户端IP计算得到一个数值,然后把请求...
  • zhangskd
  • zhangskd
  • 2015-12-12 23:39
  • 12321

Nginx的负载均衡的那点事

Nginx的负载均衡的那点事 本节就聊聊采用Nginx负载均衡之后碰到的问题: Session问题文件上传下载 通常解决服务器负载问题,都会通过多服务器分载来解决。常见的解决方案有: 网站入口...
  • andong154564667
  • andong154564667
  • 2016-08-04 17:24
  • 4276

负载均衡之加权轮询算法

在介绍加权轮询算法(WeightedRound-Robin)之前,首先介绍一下轮询算法(Round-Robin)。    一:轮询算法(Round-Robin)   轮询算法是最简单的一种负载均...
  • gqtcgq
  • gqtcgq
  • 2016-07-31 09:30
  • 8854

最小连接调度(Least-Connection Scheduling)

最小连接调度(Least-Connection Scheduling)算法是把新的连接请求分配到当前连接数最小的服务器。最小连接调度是一种动态调度算法,它通过服务器当前所活跃的连接数来估计服务器的负载...
  • starxu85
  • starxu85
  • 2008-11-05 22:43
  • 3726
    个人资料
    • 访问:1449499次
    • 积分:12712
    • 等级:
    • 排名:第1272名
    • 原创:150篇
    • 转载:8篇
    • 译文:0篇
    • 评论:223条
    博客专栏
    博客公告

    小程序员一个,坐标深圳。

    Email: zhangskd at gmail.com

    不总在,多包涵

    最新评论