ngx 配置解析

aside section._1OhGeD · · 1500 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

一 ngx配置解析框架

1.1 配置解析流程

配置解析源码在 ngx_conf_file.c文件中实现, 函数 ngx_conf_parse.

image.png

如上图所示, 配置解析采用递归的方式进行解析配置. 每次配置文件读取会读取一组数据, 其中第一个为配置项名称, 后面根据配置类型有不同数量的值. 示例 error_log /var/log/nginx/error.log; 其中键为 error_log 值为 /var/log/nginx/error.log. 除了普通的键值对类型, 还有递归类型 如:

image.png

当遇到递归类型配置时, 处理函数将会1.先保存配置解析的上下文. 2.递归调用配置解析函数. 3.恢复上下文.

1.2 配置文件读取

源码在 ngx_conf_file.c 文件的 ngx_conf_read_token 函数. 该函数主要实现 : a. 逐字符读取. b. 状态机处理字符. c. 有四种返回值, 错误 模块开始 模块结束 文件结束. d. 读取的键值对存放在 cf->args 数组中.

1.3 寻找配置处理函数

ngx_conf_t *cf; 数据结构理解 :

struct ngx_conf_s {
    char                 *name;  //配置文件名
    ngx_array_t          *args; //存储ngx_conf_read_token中读取的键值对

    ngx_cycle_t          *cycle; //全局的数据结构体
    ngx_pool_t           *pool; //全局内存池
    ngx_pool_t           *temp_pool; //临时内存池
    ngx_conf_file_t      *conf_file; //对应的配置文件结构体
    ngx_log_t            *log; //日志

    void                 *ctx; //描述当前配置解析的上下文(event模块用于指向对应的配置项)
    ngx_uint_t            module_type; //当前的模块类型 core event http mail
    ngx_uint_t            cmd_type; //当前的指令类型 

    ngx_conf_handler_pt   handler; //自定义的处理函数
    void                 *handler_conf; //自定义处理函数的数据
};

源码在ngx_conf_file.c文件的ngx_conf_handler函数中. 处理顺序 : 遍历所有模块的所有commands中依次1.匹配配置项名称. 2.匹配模块类型. 3.匹配指令类型. 4. 调用对应的处理函数.
在解析配置文件的过程中, 使用的始终是同一个 ngx_conf_t.

1.4 普通配置项的处理

通常是给指定配置项赋值, ngx提供了多种类型的赋值函数. 下面以 ngx_conf_set_num_slot为例.

char *ngx_conf_set_num_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    char  *p = conf;

    ngx_int_t        *np;
    ngx_str_t        *value;
    ngx_conf_post_t  *post;


    np = (ngx_int_t *) (p + cmd->offset); //位移到配置项所在的内存

    if (*np != NGX_CONF_UNSET) {
        return "is duplicate";
    }

    value = cf->args->elts;
    *np = ngx_atoi(value[1].data, value[1].len); // 将配置转成对应的类型 并赋值到对应的配置项
    if (*np == NGX_ERROR) {
        return "invalid number";
    }

    if (cmd->post) {
        post = cmd->post;
        return post->post_handler(cf, post, np); // 调用对应的处理函数
    }

    return NGX_CONF_OK;
}

其中cmf->offset通过 offsetof 宏获取字段在结构体中的偏移量.
其余的处理函数和上面的区别主要在于类型, 上面是针对 ngx_int_t类型, 还有 size_t, off_t, ngx_msec_t 等类型.

1.5 递归配置项的处理

递归配置项的处理流程: 1.保存当前上下文 2.设置上下文 3.调用配置解析函数. 4.恢复上下文. 其中上下文主要是 ngx_conf_t 中的 ctx, module_type, cmd_type. 参考 events 配置项的解析 :

ngx_events_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    char                 *rv;
    void               ***ctx;
    ngx_uint_t            i;
    ngx_conf_t            pcf;
    ngx_event_module_t   *m;

    if (*(void **) conf) {
        return "is duplicate";
    }

    /* count the number of the event modules and set up their indices */
    //生成 event 配置项指针
    ngx_event_max_module = ngx_count_modules(cf->cycle, NGX_EVENT_MODULE);

    ctx = ngx_pcalloc(cf->pool, sizeof(void *));
    if (ctx == NULL) {
        return NGX_CONF_ERROR;
    }

    *ctx = ngx_pcalloc(cf->pool, ngx_event_max_module * sizeof(void *));
    if (*ctx == NULL) {
        return NGX_CONF_ERROR;
    }

    *(void **) conf = ctx;

    // 依次调用 event 模块的配置生成函数
    for (i = 0; cf->cycle->modules[i]; i++) {
        if (cf->cycle->modules[i]->type != NGX_EVENT_MODULE) {
            continue;
        }

        m = cf->cycle->modules[i]->ctx;

        if (m->create_conf) {
            (*ctx)[cf->cycle->modules[i]->ctx_index] =
                                                     m->create_conf(cf->cycle);
            if ((*ctx)[cf->cycle->modules[i]->ctx_index] == NULL) {
                return NGX_CONF_ERROR;
            }
        }
    }

    // 保存上下文
    pcf = *cf;
    cf->ctx = ctx;
    cf->module_type = NGX_EVENT_MODULE;
    cf->cmd_type = NGX_EVENT_CONF;

    // 递归调用配置解析
    rv = ngx_conf_parse(cf, NULL);

    // 恢复配置解析的上下文
    *cf = pcf;

    if (rv != NGX_CONF_OK) {
        return rv;
    }

    // 依次调用 event 模块的配置初始化函数
    for (i = 0; cf->cycle->modules[i]; i++) {
        if (cf->cycle->modules[i]->type != NGX_EVENT_MODULE) {
            continue;
        }

        m = cf->cycle->modules[i]->ctx;

        if (m->init_conf) {
            rv = m->init_conf(cf->cycle,
                              (*ctx)[cf->cycle->modules[i]->ctx_index]);
            if (rv != NGX_CONF_OK) {
                return rv;
            }
        }
    }

    return NGX_CONF_OK;
}

二 配置存储

图片来源https://ialloc.org/blog/ngx-notes-conf-parsing/

image.png

在 ngx_cycle.c文件 ngx_init_cycle() 函数中, 创建配置存储数组.

    cycle->conf_ctx = ngx_pcalloc(pool, ngx_max_module * sizeof(void *));
    if (cycle->conf_ctx == NULL) {
        ngx_destroy_pool(pool);
        return NULL;
    }

配置获取, 以event 模块为例.

#define ngx_event_get_conf(conf_ctx, module)                                  \
             (*(ngx_get_conf(conf_ctx, ngx_events_module))) [module.ctx_index]

#define ngx_get_conf(conf_ctx, module)  conf_ctx[module.index]

四大模块 Core Event Http Mail. cyclt->conf_ctx数组中, event的数据实际存储在 ngx_events_module 对应下标的指针指向的数组中, 该指针数组的大小为Event模块的数量.

三 吐槽

ngx源码通过 void * 和函数指针把C语言的多态发挥的淋漓尽致. 同时, nginx 把业务抽象化, 模块化做的令人叹为观止. 但是ngx也有让我异常想吐槽的. 举例

1.枚举类型的边界. ngx_conf_t 结构体中有 module_type 和 cmd_type 两个字段. 其值本应是枚举类型, 通过位运算判断属于哪些类型. 但采用的是#define的方式, 导致很难确定具体有哪些 module_type 和 cmd_type. 类型的边界很难确定.

2.数据的边界. 以ngx非常重要的一个数据 ngx_cycle_s为例. ngx_cycle_s存储了一个进程所有的相关数据, 并且独一份. 这个结构体在各个业务模块中被使用, 导致你很难确定一个变量在哪些地方被使用. 对比 golang的做法, 以日志模块为例. 在 ngx 中 ngx_log_t 是全局独一份的日志数据, 其值被各个模块引用. 如果是 golang, 它的做法是把日志相关的数据封装在这个模块内, 提供日志记录接口. 如果实在有必要获取日志结构体, 也可以提供接口获取日志结构体.

吐槽到此为止, ngx确实有太多可以学习的地方, 但是依旧要以批判的眼光看待, 取其精华, 弃其糟粕.
当然 或许这就是大神和凡人的区别 但是自己作为一个凡人 一定不要没有大神的能力就玩些骚的


有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:aside section._1OhGeD

查看原文:ngx 配置解析

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

1500 次点击  
加入收藏 微博
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传