• listen优化


    微信公众号:郑尔多斯
    关注可了解更多的Nginx知识。任何问题或建议,请公众号留言;
    关注公众号,有趣有内涵的文章第一时间送达!

    listen优化

    前言

    我们在前面介绍了listenserver_name指令的处理过程,下面我们继续对这两个指令进行分析。
    nginxhttp指令的处理函数为ngx_http_block(),在该函数的最后有会调用ngx_http_optimize_servers()函数对listen指令和server_name指令的处理结果进行优化,本文的目的就是分析这个优化过程。

    源码分析

    首先我们查看ngx_http_block()函数的最后面有如下代码:

    1 if (ngx_http_optimize_servers(cf, cmcf, cmcf->ports) != NGX_OK) {
    2        return NGX_CONF_ERROR;
    3    }

    这里调用了ngx_http_optimize_servers()函数对listen指令的结果进行优化。

    下面的代码我删除了一些错误判断等处理代码,只关注主流程。

     1// @params cmcf: 全局的ngx_http_core_main_conf_t结构体
    2// @params ports: 保存ports信息的数组
    3static ngx_int_t
    4ngx_http_optimize_servers(ngx_conf_t *cf, ngx_http_core_main_conf_t *cmcf,
    5    ngx_array_t *ports)

    6
    {
    7    ngx_uint_t             p, a;
    8    ngx_http_conf_port_t  *port;
    9    ngx_http_conf_addr_t  *addr;
    10
    11    port = ports->elts;
    12    // 遍历所有的端口,逐个处理每个端口
    13    for (p = 0; p < ports->nelts; p++) {
    14// 对 port->addrs 数组中的每个 addr 结构进行排序
    15// 排序规则下文有分析
    16        ngx_sort(port[p].addrs.elts, (size_t) port[p].addrs.nelts,
    17                 sizeof(ngx_http_conf_addr_t), ngx_http_cmp_conf_addrs);
    18
    19/*
    20 check whether all name-based servers have the same configuraiton as a default server for given address:port
    21*/

    22        addr = port[p].addrs.elts;
    23        // 遍历端口的每个addrs数组元素,单独处理
    24        for (a = 0; a < port[p].addrs.nelts; a++) {
    25        // 如果相同的 address:port 对应的server有多个,
    26// 那么要对这些server进行排序
    27            if (addr[a].servers.nelts > 1
    28                || addr[a].default_server->captures
    29               )
    30            {
    31                if (ngx_http_server_names(cf, cmcf, &addr[a]) != NGX_OK) {
    32                    return NGX_ERROR;
    33                }
    34            }
    35        }
    36// 对当前port元素进行初始化
    37//切记:一个port元素就是一个端口,我们就要监听一个
    38// 该函数很重要,下篇文章专门分析这个函数
    39        if (ngx_http_init_listening(cf, &port[p]) != NGX_OK) {
    40            return NGX_ERROR;
    41        }
    42    }
    43
    44    return NGX_OK;
    45}

    addr排序

    上面的代码中遍历所有的ports数组元素,然后对每一个ports元素的addr进行排序。排序函数是ngx_http_cmp_conf_addrs(),代码如下:

     1static ngx_int_t
    2ngx_http_cmp_conf_addrs(const void *one, const void *two)
    3
    {
    4    ngx_http_conf_addr_t  *first, *second;
    5
    6    first = (ngx_http_conf_addr_t *) one;
    7    second = (ngx_http_conf_addr_t *) two;
    8
    9    if (first->opt.wildcard) {
    10        /* a wildcard address must be the last resort, shift it to the end */
    11        return 1;
    12    }
    13
    14    if (second->opt.wildcard) {
    15        /* a wildcard address must be the last resort, shift it to the end */
    16        return -1;
    17    }
    18
    19    if (first->opt.bind && !second->opt.bind) {
    20        /* shift explicit bind()ed addresses to the start */
    21        return -1;
    22    }
    23
    24    if (!first->opt.bind && second->opt.bind) {
    25        /* shift explicit bind()ed addresses to the start */
    26        return 1;
    27    }
    28
    29    /* do not sort by default */
    30
    31    return 0;
    32}

    排序后的规则如下:

    • 如果first addrwildcard,那么这个addr排在后面second的后面,这一句就说明wildcard 属性要排在所有的地址的后面。
    • 如果first addr不是wildcard,但是secondwildcard,那么first排在second的前面,也就是说wildcard类型的要排在 非wildcard后面
    • 如果 firstsecond 都不是wildcard,但是firstbind属性,而second没有bind属性,那么first排在second的前面,这个规则说明bind属性要排在非bind的前面。
    • 如果firstsecond都不是wildcard,但是first没有bind属性,而secondbind属性,那么frist排在second的后面,也是为了说明bind要排在前面。
    • 其他情况的排序规则不变

    总而言之,

    排序规则排序规则

    server_name 预处理

    ngx_http_optimize_servers函数中,有下面一段代码:

     1 for (a = 0; a < port[p].addrs.nelts; a++) {
    2        // 如果相同的 address:port 对应的server有多个,
    3// 那么要对这些server进行排序
    4            if (addr[a].servers.nelts > 1
    5                || addr[a].default_server->captures
    6               )
    7            {
    8                if (ngx_http_server_names(cf, cmcf, &addr[a]) != NGX_OK) {
    9                    return NGX_ERROR;
    10                }
    11            }
    12        }

    这段代码是遍历一个port端口,然后对当前端口的server进行处理。代码分析如下:

      1ngx_http_server_names(
    2ngx_conf_t *cf,  // ngx_conf_t 结构体 配置文件的结构体
    3ngx_http_core_main_conf_t *cmcf, // 
    4ngx_http_conf_addr_t *addr  //每个port下面的addr数组中的一个元素
    5)
    6{
    7    ngx_int_t                   rc;
    8    ngx_uint_t                  n, s;
    9    ngx_hash_init_t             hash;
    10    ngx_hash_keys_arrays_t      ha;
    11    ngx_http_server_name_t     *name;
    12    ngx_http_core_srv_conf_t  **cscfp;
    13#if (NGX_PCRE)
    14    ngx_uint_t                  regex, i;
    15    regex = 0;
    16#endif
    17
    18    ngx_memzero(&ha, sizeof(ngx_hash_keys_arrays_t));
    19
    20    ha.temp_pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, cf->log);
    21    if (ha.temp_pool == NULL) {
    22        return NGX_ERROR;
    23    }
    24
    25    ha.pool = cf->pool;
    26
    27    if (ngx_hash_keys_array_init(&ha, NGX_HASH_LARGE) != NGX_OK) {
    28        goto failed;
    29    }
    30
    31// cscfp 指向了addr下面的ngx_http_core_srv_conf_t 数组
    32    cscfp = addr->servers.elts; 
    33 // 遍历该addr下面的所有 ngx_http_core_srv_conf_t 数组
    34    for (s = 0; s < addr->servers.nelts; s++) {
    35 // ngx_http_core_srv_name中的server_names字段也是一个数组
    36        name = cscfp[s]->server_names.elts;
    37 // 这里遍历这个server_names数组
    38        for (n = 0; n < cscfp[s]->server_names.nelts; n++) {
    39#if (NGX_PCRE)
    40            if (name[n].regex) {
    41                regex++;
    42                continue;
    43            }
    44#endif
    45 // ngx_http_srv_conf_t 结构体的server_names字段是一个
    46// ngx_http_server_name_t 数组,这个结构体有一个server字段
    47// 该字段指向server_name指令所在的ngx_http_core_srv_conf_t结构体
    48// 下面的指令是将 server_name 和 这个server_name 所在的 
    49// ngx_http_core_srv_name_t 结构体作为 <key, value> 键值对保存到
    50// hash表中。这样通过server_name 就可以快速找到对应的 
    51// ngx_http_core_srv_conf_t 结构体
    52// 下面的函数是将server name和对应的ngx_http_core_srv_conf_t结构
    53// 体都添加到ngx_hash_keys_arrays_t数组中,
    54// 为下面的hash初始化做准备
    55            rc = ngx_hash_add_key(&ha, &name[n].name, name[n].server,  NGX_HASH_WILDCARD_KEY);
    56
    57           //我们删除了一些错误处理的代码,只关注主流程
    58
    59        }
    60    }
    61
    62    hash.key = ngx_hash_key_lc;
    63    hash.max_size = cmcf->server_names_hash_max_size;
    64    hash.bucket_size = cmcf->server_names_hash_bucket_size;
    65    hash.name = "server_names_hash";
    66    hash.pool = cf->pool;
    67 // ha.keys 保存了不含通配符的hash表
    68    if (ha.keys.nelts) {
    69        hash.hash = &addr->hash;
    70        hash.temp_pool = NULL;
    71
    72        if (ngx_hash_init(&hash, ha.keys.elts, ha.keys.nelts) != NGX_OK) {
    73            goto failed;
    74        }
    75    }
    76 // ha.dns_wc_head 保存了包含前缀符的哈希表
    77    if (ha.dns_wc_head.nelts) {
    78 // 先对hash表中的key进行排序,然后在进行初始化
    79        ngx_qsort(ha.dns_wc_head.elts, (size_t) ha.dns_wc_head.nelts,
    80                  sizeof(ngx_hash_key_t), ngx_http_cmp_dns_wildcards);
    81
    82        hash.hash = NULL;
    83        hash.temp_pool = ha.temp_pool;
    84
    85        if (ngx_hash_wildcard_init(&hash, ha.dns_wc_head.elts,
    86                                   ha.dns_wc_head.nelts)
    87            != NGX_OK)
    88        {
    89            goto failed;
    90        }
    91
    92        addr->wc_head = (ngx_hash_wildcard_t *) hash.hash;
    93    }
    94 // ha.dns_wc_tail 包含了后缀符的哈希表
    95    if (ha.dns_wc_tail.nelts) {
    96 // 先对hash表中的key进行排序,然后在进行初始化
    97     ngx_qsort(ha.dns_wc_tail.elts, (size_t) ha.dns_wc_tail.nelts,
    98                  sizeof(ngx_hash_key_t), ngx_http_cmp_dns_wildcards);
    99
    100        hash.hash = NULL;
    101        hash.temp_pool = ha.temp_pool;
    102
    103        if (ngx_hash_wildcard_init(&hash, ha.dns_wc_tail.elts,
    104                                   ha.dns_wc_tail.nelts)
    105            != NGX_OK)
    106        {
    107            goto failed;
    108        }
    109
    110        addr->wc_tail = (ngx_hash_wildcard_t *) hash.hash;
    111    }
    112
    113    ngx_destroy_pool(ha.temp_pool);
    114
    115#if (NGX_PCRE)
    116 //如果包含了正则匹配,那么 addr->nregex 保存了正在匹配的server的数量
    117    if (regex == 0) {
    118        return NGX_OK;
    119    }
    120
    121    addr->nregex = regex;
    122    addr->regex = ngx_palloc(cf->pool, regex * sizeof(ngx_http_server_name_t));
    123    if (addr->regex == NULL) {
    124        return NGX_ERROR;
    125    }
    126
    127    i = 0;
    128
    129    for (s = 0; s < addr->servers.nelts; s++) {
    130
    131        name = cscfp[s]->server_names.elts;
    132
    133        for (n = 0; n < cscfp[s]->server_names.nelts; n++) {
    134            if (name[n].regex) {
    135                addr->regex[i++] = name[n];
    136            }
    137        }
    138    }
    139
    140#endif
    141
    142    return NGX_OK;
    143
    144failed:
    145
    146    ngx_destroy_pool(ha.temp_pool);
    147
    148    return NGX_ERROR;
    149}

    总结

    上面的函数很简单,我们总结一下就行了:
    使用到的结构体如下所示:

     1typedef struct {
    2    ngx_http_listen_opt_t      opt;//当前address:port对应的listen配置项
    3    // hash, wc_head, wc_tail 这三个哈希表的key都是server_name, value是对应的ngx_http_core_srv_conf_t结构体
    4    ngx_hash_t                 hash;
    5    ngx_hash_wildcard_t       *wc_head;
    6    ngx_hash_wildcard_t       *wc_tail;
    7
    8#if (NGX_PCRE)
    9    ngx_uint_t                 nregex;
    10    ngx_http_server_name_t    *regex;
    11#endif
    12
    13    /* the default server configuration for this address:port */
    14    ngx_http_core_srv_conf_t  *default_server;
    15// servers是一个数组,它用来保存当前address:port对应的所有ngx_http_core_srv_conf_t结构体。
    16// 在ngx_http_servers() 函数处理之后,我们可以从hash, wc_head, wc_tail中方便的获取server name以及对应的ngx_http_core_srv_conf_t
    17    ngx_array_t                servers;  /* array of ngx_http_core_srv_conf_t */
    18ngx_http_conf_addr_t;
    • opt字段保存了 ngx_http_listen_opt_t 结构体,这个结构体包含了当前端口的一些配置属性。

    • hash字段保存了不包含通配符的server_name, ngx_http_core_srv_conf_t 组成的键值对。

    • wc_head 字段:包含前缀通配符的server_name, ngx_http_core_srv_conf_t 组成的键值对。

    • wc_tail 字段:包含后缀通配符的server_name, ngx_http_core_srv_conf_t 组成的键值对。

    • nregex 字段:包含正则匹配的 ngx_http_core_srv_conf_t 的数量

    • regex 字段:这是一个数组,数组元素为 server_name 包含正则表达式的ngx_http_server_name_t 结构。数组的大小为上述的nregex字段的值。


    喜欢本文的朋友们,欢迎长按下图关注订阅号郑尔多斯,更多精彩内容第一时间送达

    郑尔多斯郑尔多斯
  • 相关阅读:
    window查看已保存过的wifi的密码
    js 多个数组取交集
    macOS APP 窗口焦点监听
    proxifier注册码
    天才算法之睡眠排序(C#实现)
    Tomcat 7使用AJP协议设置问题
    nginx启动报错(1113: No mapping for the Unicode character exists in the target multi-byte code page)
    八皇后的n种放置方法
    insufficient permission for adding an object to repository database .git/objects
    centos下搭建php开发环境(lamp)
  • 原文地址:https://www.cnblogs.com/zhengerduosi/p/10429199.html
Copyright © 2020-2023  润新知