1、概述
Nginx轮询算法应用于http模块和stream模块的upstream块,根据权重选择相应的服务器进行负载均衡。
1.1、相关配置
1 |
|
2、算法实现
Nginx中http_upstream_module跟stream_upstream_module模块都使用了轮询算法,这里以stream_upstrean模块为例
2.1、相关结构体
1 | typedef struct { |
上述3个结构体是整个轮询算法的核心,ngx_stream_upstream_rr_peer_data_t结构体保存已经选择过的服务器以及当前选择的服务器,ngx_stream_upstream_rr_peers_t结构体中几个weight成员用来选择服务器。
2.1 初始化ngx_stream_upstream_rr_peer_t结构体
Nginx在解析配置文件时,当解析到upstream块时,会调用ngx_stream_upstream_init_round_robin函数,将配置文件中的server保存在ngx_stream_upstream_rr_peer_t结构体中。接下来看看ngx_stream_upstream_init_round_robin函数定义
1 |
|
- 如果没有指定其他负载均衡算法,则此函数在配置解析阶段执行,由ngx_stream_upstream_init_main_conf函数调用执行。
- 将配置中的后端服务器使用peer连接起来,对backup服务器采取同样的操作。
2.2 获取一个选中的server
选择一个上游peer,主要由ngx_http_upstream_get_round_robin_peer实现
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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177 ngx_int_t
ngx_http_upstream_get_round_robin_peer(ngx_peer_connection_t *pc, void *data)
{
ngx_http_upstream_rr_peer_data_t *rrp = data;
ngx_int_t rc;
ngx_uint_t i, n;
ngx_http_upstream_rr_peer_t *peer;
ngx_http_upstream_rr_peers_t *peers;
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0,
"get rr peer, try: %ui", pc->tries);
pc->cached = 0;
pc->connection = NULL;
peers = rrp->peers;
//如果启用了zone,则加锁
ngx_http_upstream_rr_peers_wlock(peers);
//只配置了一个server,则直接使用
if (peers->single) {
peer = peers->peer;
//单个server被标记为down,不可用
if (peer->down) {
goto failed;
}
if (peer->max_conns && peer->conns >= peer->max_conns) {
goto failed;
}
rrp->current = peer;
} else {
/* there are several peers */
// 调用选择peer的函数。
peer = ngx_http_upstream_get_peer(rrp);
if (peer == NULL) {
goto failed;
}
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, pc->log, 0,
"get rr peer, current: %p %i",
peer, peer->current_weight);
}
// 赋值socket数据
pc->sockaddr = peer->sockaddr;
pc->socklen = peer->socklen;
pc->name = &peer->name;
//此peer上的连接数自增
peer->conns++;
// 解锁
ngx_http_upstream_rr_peers_unlock(peers);
return NGX_OK;
// 错误处理
failed:
if (peers->next) {
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, pc->log, 0, "backup servers");
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;
}
ngx_http_upstream_rr_peers_unlock(peers);
rc = ngx_http_upstream_get_round_robin_peer(pc, rrp);
if (rc != NGX_BUSY) {
return rc;
}
ngx_http_upstream_rr_peers_wlock(peers);
}
ngx_http_upstream_rr_peers_unlock(peers);
pc->name = peers->name;
return NGX_BUSY;
}
//轮询的核心算法
static ngx_http_upstream_rr_peer_t *
ngx_http_upstream_get_peer(ngx_http_upstream_rr_peer_data_t *rrp)
{
time_t now;
uintptr_t m;
ngx_int_t total;
ngx_uint_t i, n, p;
ngx_http_upstream_rr_peer_t *peer, *best;
now = ngx_time();
best = NULL;
total = 0;
p = 0;
for (peer = rrp->peers->peer, i = 0;
peer;
peer = peer->next, i++)
{
//查找已选择过的peer
n = i / (8 * sizeof(uintptr_t));
m = (uintptr_t) 1 << i % (8 * sizeof(uintptr_t));
//已选择过,跳过
if (rrp->tried[n] & m) {
continue;
}
//被标记为不可用,跳过
if (peer->down) {
continue;
}
//失败次数超过阈值,跳过
if (peer->max_fails
&& peer->fails >= peer->max_fails
&& now - peer->checked <= peer->fail_timeout)
{
continue;
}
//最大连接数超过配置,跳过
if (peer->max_conns && peer->conns >= peer->max_conns) {
continue;
}
//调整当前peer权重
peer->current_weight += peer->effective_weight;
total += peer->effective_weight;
if (peer->effective_weight < peer->weight) {
peer->effective_weight++;
}
//根据权重选择
if (best == NULL || peer->current_weight > best->current_weight) {
best = peer;
p = i;
}
}
//没有选择到合适的peer,退出
if (best == NULL) {
return NULL;
}
rrp->current = best;
n = p / (8 * sizeof(uintptr_t));
m = (uintptr_t) 1 << p % (8 * sizeof(uintptr_t));
//标记已选中
rrp->tried[n] |= m;
//调整选中节点的权重
best->current_weight -= total;
//调整不可用时间
if (now - best->checked > best->fail_timeout) {
best->checked = now;
}
return best;
}
2.3 释放server
释放上游函数主要由ngx_http_upstream_free_round_robin_peer实现
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 void
ngx_http_upstream_free_round_robin_peer(ngx_peer_connection_t *pc, void *data,
ngx_uint_t state)
{
ngx_http_upstream_rr_peer_data_t *rrp = data;
time_t now;
ngx_http_upstream_rr_peer_t *peer;
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, pc->log, 0,
"free rr peer %ui %ui", pc->tries, state);
/* TODO: NGX_PEER_KEEPALIVE */
peer = rrp->current;
ngx_http_upstream_rr_peers_rlock(rrp->peers);
ngx_http_upstream_rr_peer_lock(rrp->peers, peer);
// 单个peer情况
if (rrp->peers->single) {
peer->conns--;
ngx_http_upstream_rr_peer_unlock(rrp->peers, peer);
ngx_http_upstream_rr_peers_unlock(rrp->peers);
pc->tries = 0;
return;
}
//如果以失败的状态进入此函数,调整权重
if (state & NGX_PEER_FAILED) {
now = ngx_time();
peer->fails++;
peer->accessed = now;
peer->checked = now;
//调整权重
if (peer->max_fails) {
peer->effective_weight -= peer->weight / peer->max_fails;
if (peer->fails >= peer->max_fails) {
ngx_log_error(NGX_LOG_WARN, pc->log, 0,
"upstream server temporarily disabled");
}
}
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, pc->log, 0,
"free rr peer failed: %p %i",
peer, peer->effective_weight);
//调整权重
if (peer->effective_weight < 0) {
peer->effective_weight = 0;
}
} else {
/* mark peer live if check passed */
if (peer->accessed < peer->checked) {
peer->fails = 0;
}
}
//此上游连接数自减
peer->conns--;
// 若启用了zone模块,则解锁
ngx_http_upstream_rr_peer_unlock(rrp->peers, peer);
ngx_http_upstream_rr_peers_unlock(rrp->peers);
// 重试次数自减
if (pc->tries) {
pc->tries--;
}
}