listen优化

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

listen优化

前言

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

源码分析

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

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

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

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

// @params cmcf: 全局的ngx_http_core_main_conf_t结构体
// @params ports: 保存ports信息的数组
static ngx_int_t
ngx_http_optimize_servers(ngx_conf_t *cf, ngx_http_core_main_conf_t *cmcf,
    ngx_array_t *ports)
{
    ngx_uint_t             p, a;
    ngx_http_conf_port_t  *port;
    ngx_http_conf_addr_t  *addr;

    port = ports->elts;
    // 遍历所有的端口,逐个处理每个端口
    for (p = 0; p < ports->nelts; p++) {
// 对 port->addrs 数组中的每个 addr 结构进行排序
// 排序规则下文有分析
        ngx_sort(port[p].addrs.elts, (size_t) port[p].addrs.nelts,
                 sizeof(ngx_http_conf_addr_t), ngx_http_cmp_conf_addrs);

/*
 check whether all name-based servers have the same configuraiton as a default server for given address:port
*/
        addr = port[p].addrs.elts;
        // 遍历端口的每个addrs数组元素,单独处理
        for (a = 0; a < port[p].addrs.nelts; a++) {
        // 如果相同的 address:port 对应的server有多个,
// 那么要对这些server进行排序
            if (addr[a].servers.nelts > 1
                || addr[a].default_server->captures
               )
            {
                if (ngx_http_server_names(cf, cmcf, &addr[a]) != NGX_OK) {
                    return NGX_ERROR;
                }
            }
        }
// 对当前port元素进行初始化
//切记:一个port元素就是一个端口,我们就要监听一个
// 该函数很重要,下篇文章专门分析这个函数
        if (ngx_http_init_listening(cf, &port[p]) != NGX_OK) {
            return NGX_ERROR;
        }
    }

    return NGX_OK;
}

addr排序

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

static ngx_int_t
ngx_http_cmp_conf_addrs(const void *one, const void *two)
{
    ngx_http_conf_addr_t  *first, *second;
 
    first = (ngx_http_conf_addr_t *) one;
    second = (ngx_http_conf_addr_t *) two;
 
    if (first->opt.wildcard) {
        /* a wildcard address must be the last resort, shift it to the end */
        return 1;
    }
    
    if (second->opt.wildcard) {
        /* a wildcard address must be the last resort, shift it to the end */
        return -1;
    }
 
    if (first->opt.bind && !second->opt.bind) {
        /* shift explicit bind()ed addresses to the start */
        return -1;
    }
 
    if (!first->opt.bind && second->opt.bind) {
        /* shift explicit bind()ed addresses to the start */
        return 1;
    }
 
    /* do not sort by default */
 
    return 0;
}

排序后的规则如下:

  • 如果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函数中,有下面一段代码:

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

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

ngx_http_server_names(
ngx_conf_t *cf,  // ngx_conf_t 结构体 配置文件的结构体
ngx_http_core_main_conf_t *cmcf, // 
ngx_http_conf_addr_t *addr  //每个port下面的addr数组中的一个元素
)
{
    ngx_int_t                   rc;
    ngx_uint_t                  n, s;
    ngx_hash_init_t             hash;
    ngx_hash_keys_arrays_t      ha;
    ngx_http_server_name_t     *name;
    ngx_http_core_srv_conf_t  **cscfp;
#if (NGX_PCRE)
    ngx_uint_t                  regex, i;
    regex = 0;
#endif
 
    ngx_memzero(&ha, sizeof(ngx_hash_keys_arrays_t));
 
    ha.temp_pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, cf->log);
    if (ha.temp_pool == NULL) {
        return NGX_ERROR;
    }
 
    ha.pool = cf->pool;
 
    if (ngx_hash_keys_array_init(&ha, NGX_HASH_LARGE) != NGX_OK) {
        goto failed;
    }
 
// cscfp 指向了addr下面的ngx_http_core_srv_conf_t 数组
    cscfp = addr->servers.elts; 
 // 遍历该addr下面的所有 ngx_http_core_srv_conf_t 数组
    for (s = 0; s < addr->servers.nelts; s++) {
 // ngx_http_core_srv_name中的server_names字段也是一个数组
        name = cscfp[s]->server_names.elts;
 // 这里遍历这个server_names数组
        for (n = 0; n < cscfp[s]->server_names.nelts; n++) {
#if (NGX_PCRE)
            if (name[n].regex) {
                regex++;
                continue;
            }
#endif
 // ngx_http_srv_conf_t 结构体的server_names字段是一个
// ngx_http_server_name_t 数组,这个结构体有一个server字段
// 该字段指向server_name指令所在的ngx_http_core_srv_conf_t结构体
// 下面的指令是将 server_name 和 这个server_name 所在的 
// ngx_http_core_srv_name_t 结构体作为 <key, value> 键值对保存到
// hash表中。这样通过server_name 就可以快速找到对应的 
// ngx_http_core_srv_conf_t 结构体
// 下面的函数是将server name和对应的ngx_http_core_srv_conf_t结构
// 体都添加到ngx_hash_keys_arrays_t数组中,
// 为下面的hash初始化做准备
            rc = ngx_hash_add_key(&ha, &name[n].name, name[n].server,  NGX_HASH_WILDCARD_KEY);
 
           //我们删除了一些错误处理的代码,只关注主流程
 
        }
    }
 
    hash.key = ngx_hash_key_lc;
    hash.max_size = cmcf->server_names_hash_max_size;
    hash.bucket_size = cmcf->server_names_hash_bucket_size;
    hash.name = "server_names_hash";
    hash.pool = cf->pool;
 // ha.keys 保存了不含通配符的hash表
    if (ha.keys.nelts) {
        hash.hash = &addr->hash;
        hash.temp_pool = NULL;
 
        if (ngx_hash_init(&hash, ha.keys.elts, ha.keys.nelts) != NGX_OK) {
            goto failed;
        }
    }
 // ha.dns_wc_head 保存了包含前缀符的哈希表
    if (ha.dns_wc_head.nelts) {
 // 先对hash表中的key进行排序,然后在进行初始化
        ngx_qsort(ha.dns_wc_head.elts, (size_t) ha.dns_wc_head.nelts,
                  sizeof(ngx_hash_key_t), ngx_http_cmp_dns_wildcards);
 
        hash.hash = NULL;
        hash.temp_pool = ha.temp_pool;
 
        if (ngx_hash_wildcard_init(&hash, ha.dns_wc_head.elts,
                                   ha.dns_wc_head.nelts)
            != NGX_OK)
        {
            goto failed;
        }
 
        addr->wc_head = (ngx_hash_wildcard_t *) hash.hash;
    }
 // ha.dns_wc_tail 包含了后缀符的哈希表
    if (ha.dns_wc_tail.nelts) {
 // 先对hash表中的key进行排序,然后在进行初始化
    ngx_qsort(ha.dns_wc_tail.elts, (size_t) ha.dns_wc_tail.nelts,
                  sizeof(ngx_hash_key_t), ngx_http_cmp_dns_wildcards);
 
        hash.hash = NULL;
        hash.temp_pool = ha.temp_pool;
 
        if (ngx_hash_wildcard_init(&hash, ha.dns_wc_tail.elts,
                                   ha.dns_wc_tail.nelts)
            != NGX_OK)
        {
            goto failed;
        }
 
        addr->wc_tail = (ngx_hash_wildcard_t *) hash.hash;
    }
 
    ngx_destroy_pool(ha.temp_pool);
 
#if (NGX_PCRE)
 //如果包含了正则匹配,那么 addr->nregex 保存了正在匹配的server的数量
    if (regex == 0) {
        return NGX_OK;
    }
 
    addr->nregex = regex;
    addr->regex = ngx_palloc(cf->pool, regex * sizeof(ngx_http_server_name_t));
    if (addr->regex == NULL) {
        return NGX_ERROR;
    }
 
    i = 0;
 
    for (s = 0; s < addr->servers.nelts; s++) {
 
        name = cscfp[s]->server_names.elts;
 
        for (n = 0; n < cscfp[s]->server_names.nelts; n++) {
            if (name[n].regex) {
                addr->regex[i++] = name[n];
            }
        }
    }
 
#endif
 
    return NGX_OK;
 
failed:
 
    ngx_destroy_pool(ha.temp_pool);
 
    return NGX_ERROR;
}

总结

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

typedef struct {
    ngx_http_listen_opt_t      opt;//当前address:port对应的listen配置项
    // hash, wc_head, wc_tail 这三个哈希表的key都是server_name, value是对应的ngx_http_core_srv_conf_t结构体
    ngx_hash_t                 hash;
    ngx_hash_wildcard_t       *wc_head;
    ngx_hash_wildcard_t       *wc_tail;
 
#if (NGX_PCRE)
    ngx_uint_t                 nregex;
    ngx_http_server_name_t    *regex;
#endif
 
    /* the default server configuration for this address:port */
    ngx_http_core_srv_conf_t  *default_server;
// servers是一个数组,它用来保存当前address:port对应的所有ngx_http_core_srv_conf_t结构体。
// 在ngx_http_servers() 函数处理之后,我们可以从hash, wc_head, wc_tail中方便的获取server name以及对应的ngx_http_core_srv_conf_t
    ngx_array_t                servers;  /* array of ngx_http_core_srv_conf_t */
} ngx_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字段的值。

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


郑尔多斯
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容

  • 生活中的我们总是避免开口,也可能是不够自信,但更多的是没有话聊,别人感兴趣的我们不感兴趣,我们感兴趣的却觉得别人不...
    NJ_LIFE阅读 744评论 4 3
  • 1今天礼拜六,我没睡懒觉,六点半起来做的早饭。儿子七点半起来的,等儿子洗漱完后,我都把饭全收拾餐桌上啦。我顿时一看...
    文皓文文妈妈阅读 244评论 0 6