微信公众号:郑尔多斯
关注可了解更多的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
字段的值。
喜欢本文的朋友们,欢迎长按下图关注订阅号郑尔多斯,更多精彩内容第一时间送达
郑尔多斯