本篇对ngx_http_upstream_check_module的源码实现进行详细分析。
关于配置和使用部分可以查看上篇:nginx upstream server主动健康检测模块ngx_http_upstream_check_module 使用和源码分析(上)
本模块虽然代码量比较大,洋洋洒洒近5000行代码,但是执行逻辑还是非常清晰明了的。大致如下图:
程序的运行逻辑是靠nginx内核框架中的定时器定时驱动的,每一个定时间隔会对upstream中的peer(即后端服务)执行配置中定义好的健康检查。
配置指令中最重要的就是check指令了,这个配置指令的格式如2.1节所述,nginx解析到这个指定的时候,就会开启对当前所在upstream块中的服务器的健康检测工作。其定义如下:
static ngx_command_t ngx_http_upstream_check_commands[] = {
{
ngx_string("check"),
NGX_HTTP_UPS_CONF|NGX_CONF_1MORE,
ngx_http_upstream_check,
0,
0,
NULL },
......
};
从以上指令的定义可以知道,指令的解析函数是ngx_http_upstream_check,源码如下:
static char *
ngx_http_upstream_check(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_str_t *value, s;
ngx_uint_t i, port, rise, fall, default_down, unique;
ngx_msec_t interval, timeout;
ngx_check_conf_t *check;
ngx_http_upstream_check_srv_conf_t *ucscf;
/* default values */
port = 0;
rise = 2;
fall = 5;
interval = 30000;
timeout = 1000;
default_down = 1;
unique = 0;
value = cf->args->elts;
ucscf = ngx_http_conf_get_module_srv_conf(cf,
ngx_http_upstream_check_module);
if (ucscf == NULL) {
return NGX_CONF_ERROR;
}
/* 循环解析check指令的各个参数 */
for (i = 1; i < cf->args->nelts; i++) {
if (ngx_strncmp(value[i].data, "type=", 5) == 0) {
s.len = value[i].len - 5;
s.data = value[i].data + 5;
ucscf->check_type_conf = ngx_http_get_check_type_conf(&s);
if (ucscf->check_type_conf == NULL) {
goto invalid_check_parameter;
}
continue;
}
......
/* 这里省略了部分的配置项的解析 */
goto invalid_check_parameter;
}
/* 将解析出来的配置项设置到ucscf中 */
ucscf->port = port;
ucscf->check_interval = interval;
ucscf->check_timeout = timeout;
ucscf->fall_count = fall;
ucscf->rise_count = rise;
ucscf->default_down = default_down;
ucscf->unique = unique;
if (ucscf->check_type_conf == NGX_CONF_UNSET_PTR) {
ngx_str_set(&s, "tcp");
ucscf->check_type_conf = ngx_http_get_check_type_conf(&s);
}
check = ucscf->check_type_conf;
if (ucscf->send.len == 0) {
ucscf->send.data = check->default_send.data;
ucscf->send.len = check->default_send.len;
}
if (ucscf->code.status_alive == 0) {
ucscf->code.status_alive = check->default_status_alive;
}
return NGX_CONF_OK;
invalid_check_parameter:
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid parameter \"%V\"", &value[i]);
return NGX_CONF_ERROR;
}
check配置指令解析过程非常清晰,没有什么难度,无非就是把解析到的选项存放到本模块的server级别的配置中。
本模块的其他配置指令的解析也非常简单,不再赘述。
众所周知,在nginx的配置文件中的upstream块可以添加一个或者多个server,而这个server的配置指令其实并不是由ngx_http_upstream_check模块来处理的,它是由nginx的内核模块ngx_http_upstream来处理的。那本模块是如何感知到当前的upstream块有哪些需要健康检查的server呢?
为了解决这个问题,本模块向负载均衡模块开放了接口函数,列举如下:
/* 向本模块添加服务器 */
ngx_uint_t ngx_http_upstream_check_add_peer(ngx_conf_t *cf,
ngx_http_upstream_srv_conf_t *us, ngx_addr_t *peer);
/* 检查服务器是否已经故障 */*
ngx_uint_t ngx_http_upstream_check_peer_down(ngx_uint_t index);
在负载均衡模块初始化的时候,就调用ngx_http_upstream_check_add_peer向本模块添加需要监控的服务器。另外,在nginx的负载均衡模块需要判断server是否可用的时候,就调用ngx_http_upstream_check_peer_down来判断服务器的可用状态。
由于这个耦合性的存在,因此本模块是不能直接由官方原生nginx来使用的,tengine在各个负载均衡模块修改了部分代码来调用ngx_http_upstream_check_add_peer和ngx_http_upstream_check_peer_down。
以round robin负载均衡模块为例,在ngx_http_upstream_init_round_robin函数中:
for (i = 0; i < us->servers->nelts; i++) {
if (!server[i].backup) {
continue;
}
for (j = 0; j < server[i].naddrs; j++) {
......
#if (NGX_HTTP_UPSTREAM_CHECK)
if (!server[i].down) {
peer[n].check_index =
ngx_http_upstream_check_add_peer(cf, us,
&server[i].addrs[j]);
} else {
peer[n].check_index = (ngx_uint_t) NGX_ERROR;
}
#endif
n++;
}
}
以上代码会对于每个upstream中的server的每个地址(如果以域名方式配置的话可能解析出多个地址),负载均衡模块向本模块添加一条检测记录。
**我突然发现,如果采用域名方式添加的server,本模块在后续应该是不能在通过域名解析动态得到上游服务器的地址进行检测的。**这或许是一个不足之处吧。
同样以round robin负载均衡模块为例,在ngx_http_upstream_get_round_robin_peer函数中调用了ngx_http_upstream_check_peer_down,如下:
ngx_int_t
ngx_http_upstream_get_round_robin_peer(ngx_peer_connection_t *pc, void *data)
{