nginx在1.3.1添加了一个新模块 least_conn,也就是我们常说的最少连接负载均衡算法,简单来说就是每次选择的都是当前最少连接的一个server(这个最少连接不是全局的,是每个进程都有自己的一个统计列表)。
在看最少连接模块之前需要对round robin模块有一定的了解,这里我就不对round robin模块进行分析了,想要看这块代码,可以去我们组 卫岳的blog的这篇文章
http://blog.sina.com.cn/s/blog_7303a1dc01014i0j.html
ok,接下来就来看这个模块,首先来看如何打开least_conn模块:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 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, 0, 0, NULL }, ngx_null_command };
可以看到命令很简单,就是在upstream块里面加上 least_conn就行了。
来看命令回调,least_conn的入口就是在这个里面设置的.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 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什么时候会被调用呢。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 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的连接数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 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都会调用这个函数来初始化:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 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).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 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回调也就是在这个函数中调用的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 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,这个主要是为了处理失败的情况。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 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 .
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 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的连接数 等
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 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等。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 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); }