这一节主要讲解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, ngx_http_upstream_check_commands, NGX_HTTP_MODULE, NULL, NULL, ngx_http_upstream_check_init_process, NULL, NULL, NULL, NULL, 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) { 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) { 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
| #if (NGX_HTTP_UPSTREAM_CHECK) if (ngx_http_upstream_check_peer_down(peer->check_index)) { continue; } #endif
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;
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
|
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
| 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);
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();
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 {
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
- Tengine
- ngx_http_upstream_check_module