当前位置: > Linux服务器 > nginx >

nginx least_conn 模块源码剖析

时间:2015-10-13 11:06来源:linux.it.net.cn 作者:IT

nginx在1.3.1添加了一个新模块 least_conn,也就是我们常说的最少连接负载均衡算法,简单来说就是每次选择的都是当前最少连接的一个server(这个最少连接不是全局的,是每个进程都有自己的一个统计列表)。

在看最少连接模块之前需要对round robin模块有一定的了解,这里我就不对round robin模块进行分析了,想要看这块代码,可以去我们组 卫岳的blog的这篇文章 
http://blog.sina.com.cn/s/blog_7303a1dc01014i0j.html

ok,接下来就来看这个模块,首先来看如何打开least_conn模块:


static ngx_command_t  ngx_http_upstream_least_conn_commands[] = {

    { ngx_string("least_conn"),
      NGX_HTTP_UPS_CONF|NGX_CONF_NOARGS,
      ngx_http_upstream_least_conn,
,
,
      NULL },

      ngx_null_command
};


可以看到命令很简单,就是在upstream块里面加上 least_conn就行了。

来看命令回调,least_conn的入口就是在这个里面设置的.


static char *
ngx_http_upstream_least_conn(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    ngx_http_upstream_srv_conf_t  *uscf;

    uscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_upstream_module);

    uscf->peer.init_upstream = ngx_http_upstream_init_least_conn;

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

可以看到很简单,就是设置了peer.init_upstream,然后设置了支持的flags。那么这里就有问题了,peer.init_upstream什么时候会被调用呢。


static char *
ngx_http_upstream_init_main_conf(ngx_conf_t *cf, void *conf)
{
    ngx_http_upstream_main_conf_t  *umcf = conf;

    ngx_uint_t                      i;
    ngx_array_t                     headers_in;
    ngx_hash_key_t                 *hk;
    ngx_hash_init_t                 hash;
    ngx_http_upstream_init_pt       init;
    ngx_http_upstream_header_t     *header;
    ngx_http_upstream_srv_conf_t  **uscfp;

    uscfp = umcf->upstreams.elts;

    for (i = 0; i < umcf->upstreams.nelts; i++) {
//判断是否有设置 initupstream,默认是round robin算法.
        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;
        }
    }
.......................................................
}

上面的代码可以看到是在解析upstream命令的时候,调用init_upstream的,因此也就可以这么说,init_upstream中初始化的的东西,每个进程都有自己的一份拷贝.

所以我们来看ngx_http_upstream_init_least_conn,这个函数主要就是初始化round robin(主要是为了初始化权重/timeout等参数,而且least conn中如果多个server有相同的连接数,则会使用round robin算法)以及设置peer的init回调.

还有一个要注意的就是conns数组,这个数组每个slot保存了对应server的连接数


ngx_int_t
ngx_http_upstream_init_least_conn(ngx_conf_t *cf,
    ngx_http_upstream_srv_conf_t *us)
{
    ngx_uint_t                            n;
    ngx_http_upstream_rr_peers_t         *peers;
    ngx_http_upstream_least_conn_conf_t  *lcf;

    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, cf->log, 0,
                   "init least conn");
//初始化round robin
    if (ngx_http_upstream_init_round_robin(cf, us) != NGX_OK) {
        return NGX_ERROR;
    }

    peers = us->peer.data;

    n = peers->number;

    if (peers->next) {
        n += peers->next->number;
    }

    lcf = ngx_http_conf_upstream_srv_conf(us,
                                          ngx_http_upstream_least_conn_module);
//创建conns数组
    lcf->conns = ngx_pcalloc(cf->pool, sizeof(ngx_uint_t) * n);
    if (lcf->conns == NULL) {
        return NGX_ERROR;
    }
//设置init
    us->peer.init = ngx_http_upstream_init_least_conn_peer;

    return NGX_OK;
}

而us->peer.init是在upstream的request初始化的时候调用的,也就是说每个request都会调用这个函数来初始化:


static void
ngx_http_upstream_init_request(ngx_http_request_t *r)
{
.............................
found:

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

然后我们就来看peer.init回调ngx_http_upstream_init_least_conn_peer,在这个函数中,主要是用来初始化对应的数据结构,然后挂载对应的回调(getpeer/freepeer).



typedef struct {
    /* the round robin data must be first */
    ngx_http_upstream_rr_peer_data_t   rrp;
//连接信息保存
    ngx_uint_t                        *conns;
//对应的get和free连接的回调
    ngx_event_get_peer_pt              get_rr_peer;
    ngx_event_free_peer_pt             free_rr_peer;
} ngx_http_upstream_lc_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_http_upstream_lc_peer_data_t     *lcp;
    ngx_http_upstream_least_conn_conf_t  *lcf;

    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "init least conn peer");

    lcf = ngx_http_conf_upstream_srv_conf(us,
                                          ngx_http_upstream_least_conn_module);

    lcp = ngx_palloc(r->pool, sizeof(ngx_http_upstream_lc_peer_data_t));
    if (lcp == NULL) {
        return NGX_ERROR;
    }

    lcp->conns = lcf->conns;

    r->upstream->peer.data = &lcp->rrp;
//初始化round robin
    if (ngx_http_upstream_init_round_robin_peer(r, us) != NGX_OK) {
        return NGX_ERROR;
    }
//设置回调
    r->upstream->peer.get = ngx_http_upstream_get_least_conn_peer;
    r->upstream->peer.free = ngx_http_upstream_free_least_conn_peer;

    lcp->get_rr_peer = ngx_http_upstream_get_round_robin_peer;
    lcp->free_rr_peer = ngx_http_upstream_free_round_robin_peer;

    return NGX_OK;
}

上面可以看到每个lcp都有自己的get peer和free回调,这是什么原因呢,和upstream->peer的get和free的区别在哪里,这个是这样的原因,主要是least conn算法中,如果多个server都有相同的连接数,那么就需要使用round robin算法了,所以就保存了round robin的peer回调。

然后来看对应的peer get回调在那里调用的,首先通过前面的blog 我们知道每次当要和upstream建立连接的时候,我们都需要调用ngx_event_connect_peer,最终这个函数会创建连接,然后再去connect upstream,而我们的get回调也就是在这个函数中调用的。


ngx_int_t
ngx_event_connect_peer(ngx_peer_connection_t *pc)
{
    int                rc;
    ngx_int_t          event;
    ngx_err_t          err;
    ngx_uint_t         level;
    ngx_socket_t       s;
    ngx_event_t       *rev, *wev;
    ngx_connection_t  *c;
//调用get
    rc = pc->get(pc, pc->data);
    if (rc != NGX_OK) {
        return rc;
    }
.....................................
}

其实nginx的load balance模块中,最核心的就是peer.get回调了,基本上核心的算法都在get回调里面实现,所以我们来看ngx_http_upstream_get_least_conn_peer,这个函数比较长,我们分段来看,首先是选择最少连接的server,这里要注意,其实不仅仅是最少连接,还要加上权重,这里nginx使用的是连接数和权重的乘积。

还有一个要注意的,就是对于每一个请求,Nginx保存了一个位图,这个位图保存了所有server是否已经被当前request使用过的状态,如果使用过则对应的位是1,否则为0,这个主要是为了处理失败的情况。


    for (i = 0; i < peers->number; i++) {
//一个字节8位,所以计算当前peer所处的位置
        n = i / (8 * sizeof(uintptr_t));
//得到当前peer的状态
        m = (uintptr_t) 1 << i % (8 * sizeof(uintptr_t));
//如果已经服务过,则跳过
        if (lcp->rrp.tried[n] & m) {
            continue;
        }

        peer = &peers->peer[i];

        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
         */
//选择server
        if (best == NULL
            || lcp->conns[i] * best->weight < lcp->conns[p] * peer->weight)
        {
            best = peer;
//many表示是否有多个server满足条件.
            many = 0;
//p为best的位置
            p = i;

        } else if (lcp->conns[i] * best->weight
                   == lcp->conns[p] * peer->weight)
        {
//相等则说明有多个server满足条件.
            many = 1;
        }
    }

接下来这段就是当有多个server满足条件的时候的处理,这里是如果多个server满足条件,则进入round robin的处理逻辑。下面的代码和round robin的get peer回调中算法是一模一样的。就是根据权重选择一个合适的server,这里Nginx还调整过round robin算法,想了解,可以看这里 http://trac.nginx.org/nginx/changeset/4668/nginx.


    if (many) {
        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, pc->log, 0,
                       "get least conn peer, many");

        for (i = p; i < peers->number; i++) {

            n = i / (8 * sizeof(uintptr_t));
            m = (uintptr_t) 1 << i % (8 * sizeof(uintptr_t));

            if (lcp->rrp.tried[n] & m) {
                continue;
            }

            peer = &peers->peer[i];

            if (peer->down) {
                continue;
            }

            if (lcp->conns[i] * best->weight != lcp->conns[p] * 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;

            if (peer->effective_weight < peer->weight) {
                peer->effective_weight++;
            }

            if (peer->current_weight > best->current_weight) {
                best = peer;
                p = i;
            }
        }
    }

    best->current_weight -= total;
    best->checked = now;

最后就是更新一些状态位,比如更新server的连接数 等


    pc->sockaddr = best->sockaddr;
    pc->socklen = best->socklen;
    pc->name = &best->name;

    lcp->rrp.current = p;

    n = p / (8 * sizeof(uintptr_t));
    m = (uintptr_t) 1 << p % (8 * sizeof(uintptr_t));
//设置对应的server的状态
    lcp->rrp.tried[n] |= m;
//更新连接数
    lcp->conns[p]++;

    if (pc->tries == 1 && peers->next) {
        pc->tries += peers->next->number;
    }

最后就来看peer的free回调,它主要是用来清理一些状态比如连接数减1等。


static void
ngx_http_upstream_free_least_conn_peer(ngx_peer_connection_t *pc,
    void *data, ngx_uint_t state)
{
    ngx_http_upstream_lc_peer_data_t  *lcp = data;

    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, pc->log, 0,
                   "free least conn peer %ui %ui", pc->tries, state);

    if (lcp->rrp.peers->single) {
        lcp->free_rr_peer(pc, &lcp->rrp, state);
        return;
    }

    if (state == 0 && pc->tries == 0) {
        return;
    }
//更新连接状态
    lcp->conns[lcp->rrp.current]--;
//调用round robin的free.
    lcp->free_rr_peer(pc, &lcp->rrp, state);
}


Related posts:

  1. nginx 0.8.x稳定版对linux aio的支持
  2. nginx的启动流程分析(一)
  3. nginx的启动流程分析(二)

(责任编辑:IT)
------分隔线----------------------------
栏目列表
推荐内容