您的位置:首页 > 运维架构 > Nginx

nginx配置解析过程分析

2018-02-02 14:39 686 查看
nginx在启动,reload和平滑升级时,都会重新加载配置。重新加载配置分成两步:

1. 重新加载命令行参数(-g)中定义的全局配置

2. 重新加载配置文件中的配置

解析配置的核心函数是ngx_conf_parse,不论是解析命令行定义的全局配置还是解析配置文件内容,最终都会调用该函数。函数的原型如下:

char *ngx_conf_parse(ngx_conf_t *cf, ngx_str_t *filename);
参数说明:

cf::配置解析上下文。在函数执行前由调用者设定

filename:配置文件路径。函数会根据该参数是否为NULL决定是否打开该文件并设置相关文件及buf参数,为后面读取配置做准备

ngx_conf_parse的一次执行流程可以归纳成几个步骤:

设置解析上下文环境

nginx中,调用到ngx_conf_parse的场景有很多。在刚开始读取一个配置文件,遇到一个块指令时都会调用到,因此需要根据不同的场景设置上下文,为后续的配置解析和保存提供信息

打开配置文件

在filename不为NULL的时候才需要

设置解析类型

根据上下文环境和filename参数设置解析类型。解析类型一共有三种,parse_file,parse_block和parse_param,分别对应于解析配置文件,解析配置块和解析命令行参数

读取配置

读取配置是一个循环的过程。每一轮是以调用ngx_conf_read_token函数开始的。该函数的作用是以字节为单位读取并分析配置,根据预定的语法规则判断当前语法状态,决定返回值,并更新cf中的缓冲区位置,以便下次读取。如果是合法的指令格式,读取的参数将会保存在cf->args中,为后面指令的set回调准备数据

语法检查

根据ngx_conf_read_token的返回值判断配置语法是否合法,并决定下一步行为。如果判定语法有错误,就直接打印错误信息,然后结束本次执行;如果语法没有问题,就执行ngx_conf_handler函数,查找并执行读取到的指令。如果ngx_conf_handler返回NGX_ERROR,立即结束本次流程,否则继续下一轮循环

执行自定义处理函数

如果当前上下文制定了自定义处理函数,那么就执行自定义处理函数,然后继续下一轮,否则跳到下一步

执行指令

ngx_conf_handler是真正执行指令的地方。ngx_conf_handler会遍历所有模块的指令,查找与读取到的指令名称和上下文环境匹配的模块指令。如果找到匹配指令,就根据指令设置的类型和上下文位置查找到合适的上下文,将该上下文作为指令的set回调函数的第三个参数,调用指令的set回调函数,并根据set回调函数的返回值决定返回值。如果没有匹配的指令,将会打印错误信息,然后返回NGX_ERROR

看完了ngx_conf_parse的执行流程,再来看一下一次完整的配置文件加载过程。nginx的配置文件加载在ngx_init_cycle中进行。下面是从ngx_init_cycle中摘取的相关代码。

ngx_memzero(&conf, sizeof(ngx_conf_t));
/* STUB: init array ? */
conf.args = ngx_array_create(pool, 10, sizeof(ngx_str_t));
if (conf.args == NULL) {
 ngx_destroy_pool(pool);
 return NULL;
}

conf.temp_pool = ngx_create_pool(NGX_CYCLE_POOL_SIZE, log);
if (conf.temp_pool == NULL) {
ngx_destroy_pool(pool);
return NULL;
}

conf.ctx = cycle->conf_ctx;
conf.cycle = cycle;
conf.pool = pool;
conf.log = log;
conf.module_type = NGX_CORE_MODULE;
conf.cmd_type = NGX_MAIN_CONF;

#if 0
log->log_level = NGX_LOG_DEBUG_ALL;
#endif

/* 先解析并执行命令行参数(-g选项)中定义的全局配置 */
if (ngx_conf_param(&conf) != NGX_CONF_OK) {
environ = senv;
ngx_destroy_cycle_pools(&conf);
return NULL;
}

/* 解析配置文件,执行指令(创建模块配置上下文,设置参数,执行模块回调) */
if (ngx_conf_parse(&conf, &cycle->conf_file) != NGX_CONF_OK) {
environ = senv;
ngx_destroy_cycle_pools(&conf);
return NULL;
}


从上面的代码可以看到,nginx是先对-g选项指定的全局配置进行加载,然后再对配置文件里的配置进行加载的,因此如果-g选项指定的配置与配置文件中重复,可能会被后者覆盖或者报错。ngx_conf_param读取命令行的全局配置,最终调用ngx_conf_parse进行解析,比较简单。配置文件的加载,调用了一次ngx_conf_parse就完成了,看起来似乎也很简单,实际上里面的过程还是比较复杂的。前面说到,nginx在解析配置文件的时候会多次调用到ngx_conf_parse,每次调用的场景都不同。调用场景归纳起来可以分成两大类:

1.
初次读取一个配置文件时

2.
解析过程中遇到配置块指令时

上面的代码就是第一类场景的一种,另外一种就是遇到include指令时。events,http,mail等等都属于第二类。那么nginx是如何实现仅仅调用一次ngx_conf_parse就实现了所有配置的加载呢?在分析之前,必须先了解ngx_conf_parse的参数cf。cf指定了本次解析的上下文环境,ngx_conf_parse的执行需要依照cf的配置来进行。cf是ngx_conf_t类型的指针,ngx_conf_t的定义是这样的

struct ngx_conf_s {
char                 *name;//未使用
/*一次循环读取到的配置都保存在args中,args[0]是指令名称,args[1]是指令第一个参数,args[2]是第二次参数,依次类推。在下一次循环开始时,args会被清空,以便保存下次循环读取的配置 */
ngx_array_t          *args;

ngx_cycle_t          *cycle;cf所属cycle
ngx_pool_t           *pool;//cf所使用的内存池,与cycle共用一个
ngx_pool_t           *temp_pool;//cf所使用的临时内存池,配置加载完成后会被销毁
ngx_conf_file_t      *conf_file;//配置文件路径
ngx_log_t            *log;//日志对象

void                 *ctx;//当前所处的上下文环境
ngx_uint_t            module_type;//当前上下文环境允许的模块类型
ngx_uint_t            cmd_type;//当前上下文环境类型

ngx_conf_handler_pt   handler;//自定义配置处理函数
char                 *handler_conf;//自定义配置处理函数的参数
};
了解了cf之后,现在可以开始分析nginx的配置加载过程了。在开始加载配置前,nginx指定了开始加载配置的上下文,如下所示:
conf.ctx = cycle->conf_ctx;
conf.cycle = cycle;
conf.pool = pool;
conf.log = log;
conf.module_type = NGX_CORE_MODULE;
conf.cmd_type = NGX_MAIN_CONF;
ctx,module_type和cmd_type是重点,其它三个与我们的分析关系不大,可以略过。

首先分析conf.ctx
= cycle->conf_ctx,这条语句指定了配置的上下文是cycle->conf_ctx。cycle->conf_ctx是一个数组,数组的每一个元素对应于一个模块的配置上下文,因此,这实际上指定了配置保存的位置是cycle->conf_ctx中对应于指令所在模块的元素。

再看conf.module_type=NGX_CORE_MODULE,这指定了指令所属的模块必须是NGX_CORE_MODULE类型,否则该指令将会被忽略。

最后,conf.cmd_type指定了指令的合法位置必须是NGX_MAIN_CONF,即全局配置,否则也将被忽略。

从上面的分析可以看出,开始解析配置前,nginx默认读取的是CORE类型模块的指令,并且指令是全局配置,读取到的配置会被保存在cycle->conf_ctx对应的元素中。那么,如果碰到不满足上面指定的条件的指令,nginx会如何处理呢?

上面说到,有两类场景会调用ngx_conf_parse函数,其中一种就是块指令。实际上,所有出现在全局配置中的块指令都一定是NGX_MAIN_CONF类型的指令,其所属模块都是NGX_CORE_MODULE类型,http,events,mail,stream都属于这一类块指令。那么,在全局块指令内部的指令该如何处理呢?

前面说到,在读取指令后,nginx会执行指令的set回调函数。每个全局块指令的回调函数中,会为本块内的解析准备环境,分配内存,并且重新设置解析上下文,然后调用ngx_conf_parse在本块内进行解析,直到当前块结束后,将上下文环境还原成调用前的状态。以http块指令为例,ngx_http_block(ngx_conf_t
*cf, ngx_command_t *cmd, void *conf)是http块指令的回调函数,下面的代码是从ngx_http_block中摘取出来的,中间省略部分不相关代码:

ngx_conf_t                   pcf;//用来临时保存上下文环境
ngx_http_conf_ctx_t         *ctx;//http块的上下文环境
...
ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_conf_ctx_t));
if (ctx == NULL) {
return NGX_CONF_ERROR;
}
*(ngx_http_conf_ctx_t **) conf = ctx;
...
pcf = *cf;/*保存调用前上下文环境*/
...
/* 设置当前上下文环境是http块 */
cf->ctx = ctx;
...
cf->module_type = NGX_HTTP_MODULE;//当前上下文环境允许的模块类型是NGX_HTTP_MODULE
cf->cmd_type = NGX_HTTP_MAIN_CONF;//当前上下文环境是http全局
rv = ngx_conf_parse(cf, NULL);
...
*cf = pcf;/*还原调用前上下文*/
return rv;
上面的代码很清晰地展示了http指令的逻辑,这与我们的描述是一致的。
另外,指令块内如果还有属于该指令块的指令块(例如http内的server, location等),这些指令块的回调函数也将执行类似的逻辑。最终,在遇到文件结束符时,配置加载就完成了。

通过这种切换上下文+递归调用的方式,nginx就实现了配置文件的解析。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: