listen優化

鄭爾多斯發表於2019-02-25

微信公眾號:鄭爾多斯
關注可瞭解更多的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欄位的值。


喜歡本文的朋友們,歡迎長按下圖關注訂閱號鄭爾多斯,更多精彩內容第一時間送達

鄭爾多斯
鄭爾多斯

相關文章