博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Nginx源代码分析-hash
阅读量:6194 次
发布时间:2019-06-21

本文共 7193 字,大约阅读时间需要 23 分钟。

hot3.png

本文分析基于Nginx-1.2.6,与旧版本或将来版本可能有些许出入,但应该差别不大,可做参考

上篇对nginx中的hash分析还没有结束,因为带有wildcard或wc字样的结构体和函数还没有说明。但我也不知道该从何说起。(其实这篇我理解的可能有错,因为涉及到http module中的代码,还没读透,所以还不吝赐教)

#从配置文件的server_name指令说起# 在有这个指令的详解,也有个例子。这个指令有一个动作是比较HTTP报文头中的HOST与server_name的参数,选择最匹配的那个server配置,匹配的先后顺序为:

  • 精确匹配,即HOST与server_name中某个参数相同
  • 与带前置通配符的域名匹配,例如sub.domain.com匹配*.domain.com
  • 与带后置通配符的域名匹配,例如www.example.com匹配www.example.*
  • 与某个正则表达式匹配

比如有配置如下:

#下面这两个配置等价,因为.example.com等价于 example.com  *.example.comserver {  server_name   example.com  *.example.com  www.example.*;}server {  server_name  .example.com  www.example.*;}

当读取完配置加载到内存后,应该维持一个什么样的数据结构,才能在每个HTTP请求到来时按上述顺序快速寻找到最佳匹配?

#哈希表的功效#

容易想到的是hash表,其实nginx中正是利用了三个hash表实现这项功能,一个是普通hash表对应不带通配符的server_name配置,另两个对应带通配符的配置。

###为方便描述称存放带通配符的key的哈希表为wildcard hash,一般hash为exact hash。###

系统处理过程大致为:

  • 解析出请求的HOST

  • 然后调用ngx_http_find_virtual_server(r,host,len),其中r是封装了HTTP请求的一个结构体,找到最佳匹配的配置cscf(类型为ngx_http_core_srv_conf_t),并在r结构体中记录下来,对应代码如下所示。

  • 继续往下处理

第二步代码:

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中定义的一个函数,要重点关注这个调用:

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表中查找对应的项。

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是做这个的,源码里面有注释。在说明这两个函数之前,先看下涉及到的数据结构:

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转换后存放在上述结构中的,其过程是

  • 先看传入的第三个参数标志标明的key是不是NGX_HASH_WILDCARD_KEY,
  • 如果不是,则在ha->keys_hash中检查是否冲突,冲突就返回NGX_BUSY,否则,就将这一项插入到ha->keys中。
  • 如果是,就判断通配符类型,支持的统配符有三种"*.example.com", ".example.com", and "www.example.*",然后将第一种转换为"com.example."并插入到ha->dns_wc_head中,将第三种转换为"www.example"并插入到ha->dns_wc_tail中,对第二种比较特殊,因为它等价于"*.example.com"+"example.com",所以会一份转换为"com.example."插入到ha->dns_wc_head,一份为"example.com"插入到ha->keys中。当然插入前都会检查是否冲突。

调用ngx_hash_wildcard_init初始化后,生成的哈希表结构(是个决策树吧)如下图所示:

初始化之后清楚了不

注释:

/* * 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中有使用。

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"的区别。

这篇写的杂乱。。哎。。。

转载于:https://my.oschina.net/7gaoxing/blog/110738

你可能感兴趣的文章
BCG控件初步领略
查看>>
Visual Studio 2017 安装Windows SDK 失败的解决办法
查看>>
【JavaScript框架封装】使用原生js封装的类似于JQuery的框架及核心源码分享(多文件版本)...
查看>>
链课堂01 | 解密超级账本-Fabric运行机制
查看>>
SpringBoot从数据库加载配置信息
查看>>
前端碎语(1)
查看>>
一个很easy的脚本--php获取服务器端的相关信息
查看>>
sync
查看>>
Java之关于JSTL引入问题
查看>>
javascript 闭包
查看>>
貌似有点难——实验吧
查看>>
验证码识别--资料收集
查看>>
Confluence 6 使用 LDAP 授权连接一个内部目录 - 用户 Schema 设置
查看>>
hanlp源码解析之中文分词算法
查看>>
亿万级数据处理的高效解决方案
查看>>
HTML5,不只是看上去很美 (第一弹:彩虹爆炸图)
查看>>
用ES6重写《JavaScript Patterns》中的设计模式
查看>>
AngularJs学习笔记-第二章-函数
查看>>
我在移动web开发中遇到的各种问题
查看>>
面向对象,二
查看>>