微信公眾號:鄭爾多斯
關注可瞭解更多的Nginx
知識。任何問題或建議,請公眾號留言;
關注公眾號,有趣有內涵的文章第一時間送達!
listen優化
前言
我們在前面介紹了listen
和server_name
指令的處理過程,下面我們繼續對這兩個指令進行分析。nginx
的http
指令的處理函式為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 addr
是wildcard
,那麼這個addr
排在後面second
的後面,這一句就說明wildcard
屬性要排在所有的地址的後面。- 如果
first addr
不是wildcard
,但是second
是wildcard
,那麼first
排在second
的前面,也就是說wildcard
型別的要排在 非wildcard
後面- 如果
first
和second
都不是wildcard
,但是first
有bind
屬性,而second
沒有bind
屬性,那麼first
排在second
的前面,這個規則說明bind
屬性要排在非bind
的前面。- 如果
first
和second
都不是wildcard
,但是first
沒有bind
屬性,而second
有bind
屬性,那麼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 */
18} 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
欄位的值。
喜歡本文的朋友們,歡迎長按下圖關注訂閱號鄭爾多斯,更多精彩內容第一時間送達