0%

Nginx源码简析(三)

这一节主要讲解Nginx的健康检查算法,由于Nginx并未开源其健康检查算法,只在商用版本的Nginx Plus中提供这个功能,我们参考Tengine的实现来讨论

一、主动健康检查

主动健康检查是指在Nginx定时固定的时间去调用upstream中节点的特定接口,并根据返回判断节点的可用性,其配置文件如下

1
2
3
4
5
6
7
8
9
10
upstream hello {
vnswrr;
server 127.0.0.1:9090 weight=1;
server 127.0.0.1:9090 weight=2;
server 127.0.0.1:9090 weight=3;
check interval=3000 rise=2 fall=5 timeout=1000 type=http;
check_keepalive_requests 100;
check_http_send "HEAD /healthcheck HTTP/1.1\r\nHost: localhost:9090\r\n\r\n";
check_http_expect_alive http_2xx http_3xx;
}

上述配置项中,check那一行表示进行主动健康检查,检查周期是3秒一次,每次检查的超时是1秒,并在2次成功后认为节点是up状态,5次失败后认为节点是down状态,通过http发送健康检查请求;check_keepalive_requests那一行指定了保活的请求数,可以减少和upstream中节点频繁建立tcp连接;heck_http_send那一行表示发送的HTTP请求的内容;check_http_expect_alive那一行表示健康检查的返回状态码只要是2xx或者3xx就认为是成功。
具体到代码上,模块在初始化的时候,初始化了回调函数ngx_http_upstream_check_init_process,在这个函数中会添加check的定时事件到eventloop中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
ngx_module_t  ngx_http_upstream_check_module = {
NGX_MODULE_V1,
&ngx_http_upstream_check_module_ctx, /* module context */
ngx_http_upstream_check_commands, /* module directives */
NGX_HTTP_MODULE, /* module type */
NULL, /* init master */
NULL, /* init module */
ngx_http_upstream_check_init_process, /* init process */
NULL, /* init thread */
NULL, /* exit thread */
NULL, /* exit process */
NULL, /* exit master */
NGX_MODULE_V1_PADDING
};

最终调用upstream的接口,获取响应后得到result,result为1表示成功,result为0表示失败,并在下面的主体逻辑中进行判断,更新peer->shm->down这个标志位。

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 void
ngx_http_upstream_check_status_update(ngx_http_upstream_check_peer_t *peer,
ngx_int_t result)
{
ngx_http_upstream_check_srv_conf_t *ucscf;

ucscf = peer->conf;

ngx_shmtx_lock(&peer->shm->mutex);

if (peer->shm->delete == PEER_DELETED) {

ngx_shmtx_unlock(&peer->shm->mutex);
return;
}

if (result) {
// 健康检查成功
peer->shm->rise_count++;
peer->shm->fall_count = 0;
if (peer->shm->down && peer->shm->rise_count >= ucscf->rise_count) {
// 成功次数超rise,认为up
peer->shm->down = 0;
ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0,
"enable check peer: %V ",
&peer->check_peer_addr->name);
}
} else {
// 健康检查失败
peer->shm->rise_count = 0;
peer->shm->fall_count++;
if (!peer->shm->down && peer->shm->fall_count >= ucscf->fall_count) {
// 失败次数超过fall,认为down
peer->shm->down = 1;
ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0,
"disable check peer: %V ",
&peer->check_peer_addr->name);
}
}

peer->shm->access_time = ngx_current_msec;

ngx_shmtx_unlock(&peer->shm->mutex);
}

而在实际使用过程中,以vnswrr负载均衡算法为例,会过滤掉健康检查失败(down)的节点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// ngx_http_upstream_vnswrr_module.c:ngx_http_upstream_get_vnswrr
#if (NGX_HTTP_UPSTREAM_CHECK)
if (ngx_http_upstream_check_peer_down(peer->check_index)) {
continue;
}
#endif


// ngx_http_upstream_check_module.c
ngx_uint_t
ngx_http_upstream_check_peer_down(ngx_uint_t index)
{
ngx_http_upstream_check_peer_shm_t *peer_shm;

if (upstream_check_index_invalid(check_peers_ctx, index)) {
return 0;
}

peer_shm = check_peers_ctx->peers_shm->peers;

// 通过down字段来决定是否down
return (peer_shm[index].down);
}

二、被动健康检查

被动健康检查,实际就是在每次请求upstream中节点的时候,根据返回数据的情况,来决定节点的健康情况,以下面的配置文件为例

1
2
3
4
5
6
upstream hello {
vnswrr;
server 127.0.0.1:9090 weight=1 max_fails=2 fail_timeout=1000;
server 127.0.0.1:9090 weight=2 max_fails=2 fail_timeout=1000;
server 127.0.0.1:9090 weight=3 max_fails=2 fail_timeout=1000;
}

其中max_fails表示失败多少次就认为节点down了,前提是check的事件没有超过fail_timeout毫秒数,具体到代码中,以vnswrr为例,会在选择节点的时候,过滤掉满足上述条件的节点。

1
2
3
4
5
6
7
8
// ngx_http_upstream_vnswrr_module.c:ngx_http_upstream_get_vnswrr

if (peer->max_fails
&& peer->fails >= peer->max_fails
&& now - peer->checked <= peer->fail_timeout)
{
continue;
}

而上述的失败次数其实是在下面的函数中被更新的

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
// ngx_http_upstream_round_robin.c
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);

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

// 失败次数加1
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--;

ngx_http_upstream_rr_peer_unlock(rrp->peers, peer);
ngx_http_upstream_rr_peers_unlock(rrp->peers);

if (pc->tries) {
pc->tries--;
}
}

而上述函数的主要调用点事 nginx_http_upstream.c:ngx_http_upstream_next 函数,主要是在一些连接失败、参数或者响应格式错误、请求响应超时的时候调用,并不涉及到http的status code的判断。从我的角度上看基本就是连接断开的情况。

三、总结

健康检查最终影响的是节点的状态是up还是down,只要在不同的负载均衡算法选择peer的过程去判断这个状态即可。主动负载均衡是使用了共享内存,方便了不同Worker可以共享这份数据。

四、Reference

  1. Tengine
  2. ngx_http_upstream_check_module
如果您觉得这些内容对您有帮助,你可以赞助我以提高站点的文章质量