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;

}

}

&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;.

}

上面的代码可以看到是在解析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)

{

&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;..

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;

}

&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;.

}

其实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 &#8211; 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 &#8211; 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]&#8211;;

//调用round robin的free.

lcp->free_rr_peer(pc, &lcp->rrp, state);

}