本文分析基于Nginx-1.2.6,与旧版本或未来版本可能有些许出入,但应该差异不大,可作参考html
上篇对nginx中的hash分析尚未结束,由于带有wildcard或wc字样的结构体和函数尚未说明。但我也不知道该从何提及。(其实这篇我理解的可能有错,由于涉及到http module中的代码,还没读透,因此还不吝赐教)nginx
#从配置文件的server_name指令提及# 在这里有这个指令的详解,这里也有个例子。这个指令有一个动做是比较HTTP报文头中的HOST与server_name的参数,选择最匹配的那个server配置,匹配的前后顺序为:正则表达式
好比有配置以下:数组
#下面这两个配置等价,由于.example.com等价于 example.com *.example.com server { server_name example.com *.example.com www.example.*; } server { server_name .example.com www.example.*; }
当读取完配置加载到内存后,应该维持一个什么样的数据结构,才能在每一个HTTP请求到来时按上述顺序快速寻找到最佳匹配?数据结构
#哈希表的功效#dom
容易想到的是hash表,其实nginx中正是利用了三个hash表实现这项功能,一个是普通hash表对应不带通配符的server_name配置,另两个对应带通配符的配置。函数
###为方便描述称存放带通配符的key的哈希表为wildcard hash,通常hash为exact hash。###ui
系统处理过程大体为:.net
解析出请求的HOSTunix
而后调用ngx_http_find_virtual_server(r,host,len),其中r是封装了HTTP请求的一个结构体,找到最佳匹配的配置cscf(类型为ngx_http_core_srv_conf_t),并在r结构体中记录下来,对应代码以下所示。
继续往下处理
第二步代码:
<!-- lang: cpp --> cscf = ngx_hash_find_combined(&r->virtual_names->names, ngx_hash_key(host, len), host, len); ..... r->srv_conf = cscf->ctx->srv_conf; r->loc_conf = cscf->ctx->loc_conf;
在寻找最佳匹配时调用了ngx_hash.c中定义的一个函数,要重点关注这个调用:
<!-- lang: cpp --> cscf = ngx_hash_find_combined(&r->virtual_names->names, ngx_hash_key(host, len), host, len); /*r->virtual_names是这个类型,与server_name指令的匹配顺序能够对着看*/ typedef struct { ngx_hash_combined_t names; ngx_uint_t nregex; ngx_http_server_name_t *regex; } ngx_http_virtual_names_t; typedef struct { ngx_hash_t hash; ngx_hash_wildcard_t *wc_head; ngx_hash_wildcard_t *wc_tail; } ngx_hash_combined_t;
如今回到正题,ngx_hash_combined_t中有三个hash表,调用ngx_hash_find_combined(hash,key,name,len)时,就是在按前后顺序在三个hash表中查找对应的项。
<!-- lang: cpp --> void * ngx_hash_find_combined(ngx_hash_combined_t *hash, ngx_uint_t key, u_char *name, size_t len) { void *value; /*在普通hash表中查找是否有精准匹配*/ if (hash->hash.buckets) { value = ngx_hash_find(&hash->hash, key, name, len); if (value) { return value; } } if (len == 0) { return NULL; } /*在wc_head哈希表中查找是否能与某个带前置通配符的项匹配*/ if (hash->wc_head && hash->wc_head->hash.buckets) { value = ngx_hash_find_wc_head(hash->wc_head, name, len); if (value) { return value; } } /*在wc_tail哈希表中查找是否能与某个带后置通配符的项匹配*/ if (hash->wc_tail && hash->wc_tail->hash.buckets) { value = ngx_hash_find_wc_tail(hash->wc_tail, name, len); if (value) { return value; } } return NULL; }
#wildcard hash 初始化#
初始化过程是ngx_hash_wildcard_init中进行的,但咱们只知道这个函数的第二个参数是ngx_hash_key_t数组,其实这个数组中存放的并非带通配符的域名,而是通过转换以后的。由于对sub.example.com选择匹配时可能会要判断是否有*.example.com或sub.example.*之类的server_name配置,那怎么才能把sub.example.com快速匹配到*.example.com而不是*.example.org呢?
并且当给出的key值既有不带通配符的记作A,又有带前置通配符的记为B,又有带后置通配符的记为C,咱们但愿从中筛选出A存放到exact hash中,B存放到_head wildcard hash中,C存放到tail wildcard hash中,ngx_hash_keys_array_init和ngx_hash_add_key是作这个的,源码里面有注释。在说明这两个函数以前,先看下涉及到的数据结构:
<!-- lang: cpp --> typedef struct { ngx_uint_t hsize; ngx_pool_t *pool; ngx_pool_t *temp_pool; ngx_array_t keys; ngx_array_t *keys_hash; ngx_array_t dns_wc_head; ngx_array_t *dns_wc_head_hash; ngx_array_t dns_wc_tail; ngx_array_t *dns_wc_tail_hash; } ngx_hash_keys_arrays_t;
这个数据结构的说明在这里,下面这段是从他那里复制过来的
hsize: 将要构建的hash表的桶的个数。对于使用这个结构中包含的信息构建的三种类型的hash表都会使用此参数。
pool: 构建这些hash表使用的pool。
temp_pool:在构建这个类型以及最终的三个hash表过程当中可能用到临时pool。该temp_pool能够在构建完成之后,被销毁掉。这里只是存放临时的一些内存消耗。
keys: 存放全部非通配符key的数组。
keys_hash: 这是个二维数组,第一个维度表明的是bucket的编号,那么keys_hash[i]中存放的是全部的key算出来的hash值对hsize取模之后的值为i的key。假设有3个key,分别是key1,key2和key3假设hash值算出来之后对hsize取模的值都是i,那么这三个key的值就顺序存放在keys_hash[i][0],keys_hash[i][5], keys_hash[i][6]。该值在调用的过程当中用来保存和检测是否有冲突的key值,也就是是否有重复。
dns_wc_head: 存放前向通配符key被处理完成之后的值。好比:“*.abc.com”被处理完成之后,变成“com.abc.”被存放在此数组中。
dns_wc_tail: 存放后向通配符key被处理完成之后的值。好比:“mail.xxx.*”被处理完成之后,变成“mail.xxx.”被存放在此数组中。
dns_wc_head_hash: 该值在调用的过程当中用来保存和检测是否有冲突的前向通配符的key值,也就是是否有重复。 dns_wc_tail_hash: 该值在调用的过程当中用来保存和检测是否有冲突的后向通配符的key值,也就是是否有重复。
注:keys,dns_wc_head,dns_wc_tail,三个数组中存放的元素时ngx_hash_key_t类型的,而keys_hash,dns_wc_head_hash,dns_wc_tail_hash,三个二维数组中存放的元素是ngx_str_t类型的。
ngx_hash_keys_array_init就是为上述结构分配空间。
ngx_hash_add_key是将带或不带通配符的key转换后存放在上述结构中的,其过程是
调用ngx_hash_wildcard_init初始化后,生成的哈希表结构(是个决策树吧)以下图所示:
注释:
<!-- lang: cpp --> /* * the 2 low bits of value have the special meaning: * 00 - value is data pointer for both "example.com" * and "*.example.com"; * 01 - value is data pointer for "*.example.com" only; * 10 - value is pointer to wildcard hash allowing * both "example.com" and "*.example.com"; * 11 - value is pointer to wildcard hash allowing * "*.example.com" only. */
在ngx_http_server_names中有使用。
<!-- lang: cpp --> if (ngx_hash_keys_array_init(&ha, NGX_HASH_LARGE) != NGX_OK) { goto failed; } ...... rc = ngx_hash_add_key(&ha, &name[n].name, name[n].server, NGX_HASH_WILDCARD_KEY); 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; } } if (ha.dns_wc_head.nelts) { /*这里有排序*/ 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; } if (ha.dns_wc_tail.nelts) { /*这里有排序*/ 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; }
#wildcard hash 查找# 有了上面wildcard hash的树状的结构图,会容易地读懂代码。
ngx_hash_find_wc_head: * 好比查找sub.example.com,会首先在wildcard hash中查找com,并根据com的value的低两位判断,发现是11,就继续在级联的hash中查找example,发现其value低两位是01,而待查找的key还有sub,则返回((uintptr_t) value & (uintptr_t) ~3),即sub.example.com匹配*.example.com。 * 但当你要查找的是example.com时,在第二级hash对应项example中发现value低两位是01,而待查找的key已从后往前遍历到头了,则返回NULL,说明example.com不匹配*.example.com。 * 而当查找domain.com时,先查找com,其value低两位是11,继续在级联hash中查找domain,发现其value是10,且待查找key已从后往前遍历到头,则返回级联的wildcard hash中的value值和~3的与。说明domain.com 匹配 .domain.com。
ngx_hash_find_wc_tail:后置通配符与前置通配符的处理相似,并且更简单,由于后置通配符没有相似"*.example.com"与”.example.com"的区别。
这篇写的杂乱。。哎。。。