本节我们讲述一下nginx中hash。nginx中hash有三种类型:
-
不带通配符的hash
-
带前向通配符的hash
-
带后向通配符的hash
注意: 不支持同时带有前向通配符与后向通配符的hash
1. 函数ngx_hash_wildcard_init()
本函数用于初始化带通配符的hash表。在具体介绍本函数之前,我们先给出一副整体的示意图:
上图只显示了二级Hash,实际上可能会出现多级Hash的情况。这里我们再举一个例子来加深理解,假设有下面的键值对<key,value>:
再调用后面的ngx_hash_add_key()
函数添加到dns_wc_head
数组中时,该数组值最后会被处理成如下所示:
{key = ("com.", 4 ), key_hash = 0, value = "220.181.111.147"}
{key = ("cn.com.baidu.", 13), key_hash = 0, value = "220.181.111.147"}
{key = ("com.baidu.", 10), key_hash = 0, value = "220.181.111.147"}
{key = ("com.google.", 11), key_hash = 0, value = "58.63.236.35"}
然后将dns_wc_head
数组中的值添加到hash表中之前,一般都会先调用qsort()
函数进行排序,处理成为:
{key = ("cn.com.baidu.", 13), key_hash = 0, value = "220.181.111.147"}
{key = ("com.", 4 ), key_hash = 0, value = "220.181.111.147"}
{key = ("com.baidu.", 10), key_hash = 0, value = "220.181.111.147"}
{key = ("com.google.", 11), key_hash = 0, value = "58.63.236.35"}
这样处理完成后,所有相同前缀的元素都会排在一起。
接下来我们分成好几个部分来讲解一下ngx_hash_wildcard_init()函数:
1) 初始化两个数组
其中第一个数组curr_names
用于处理上图中的一级Hash元素
; 而next_names
用于处理子级Hash元素
(二级以上)。
2) 构建Hash表
下面给出上面所举例子的一个Hash图:
2. 函数ngx_hash_key()
本函数用于对一个字符串求Hash:
key = (ngx_uint_t) key * 31 + data[i];
3. 函数ngx_hash_key_lc()
这里将data字符串转换为小写来求Hash
4. 函数ngx_hash_strlow()
这里在求Hash的同时,将src转化成小写.
5. 函数ngx_hash_keys_array_init()
本函数用于初始化ngx_hash_keys_arrays_t
数据结构, 分别初始化如下数据:
-
ha->keys
: 不带通配符的ngx_hash_key_t
数组
-
ha->dns_wc_head
: 带前向通配符的ngx_hash_key_t
数组
-
ha->dns_wc_tail
: 带后向通配符的ngx_hash_key_t
数组
-
ha->keys_hash
: 这是一个二维数组。该值在调用的过程中用来保存和检测是否有冲突的key值,也就是是否有重复
-
ha->dns_wc_head_hash
: 这是一个二维数组。该值在调用的过程中用来保存和检测是否有冲突的key值,也就是是否有重复
-
ha->dns_wc_tail_hash
: 这是一个二维数组。该值在调用的过程中用来保存和检测是否有冲突的key值,也就是是否有重复
6. 函数ngx_hash_add_key()
本函数用于添加元素到ngx_hash_keys_arrays_t
数据结构中。下面我们分几个部分来说明一下该函数:
1) 判断元素添加到哪一种类的Hash表中
这里首先根据flags
提示当前是否要添加的key
元素是否含有通配。如果有的话,则执行if条件的相关操作。这里支持的通配含如下三种形式:
/*
* supported wildcards:
* "*.example.com", ".example.com", and "www.example.*"
*/
然后判断所传入的带通配的key
元素是否合法,如果合法是属于上述三种的哪一种通配:
-
skip=1
: 表示.example.com
这种通配类型
-
skip=2
: 表示*.example.com
这种通配类型
-
skip=0
: 表示www.example.*
这种通配类型,此时将last
值进行调整last-=2
,即减去后面.*
两个字符的长度。
2) 插入元素到exact hash中
这里插入步骤如下:
-
求可以的hash值。 这里如果flags
标明的该key不是NGX_HASH_READONLY_KEY
不是readonly类型的话,会先将该key转换成小写,然后再求hash值。
-
判断该key
在ha->keys
数组当中是否有重复。 这里可以看到ha->hash_keys
哈希表的用处了,就是用于快速判断是否有重复。
-
如果没有重复,则将当前元素<key,value>
插入到无通配符的hash表中
3) 检查.example.com
类型通配是否在exact hash中
这里首先求出所要通配的key的hash值,然后看其映射到哪一个桶。如果skip=1
,即key是属于.example.com
这种通配类型的时候,需要判断是否该key也存在于exact hash
中。 如果在exact hash
中也存在,直接返回NGX_BUSY
; 否则将其在ha->keys_hash
中做一个标记,后续禁止往ha->keys
中插入该key(注意这里并没有插入ha->keys
)。
4) 对通配字符串进行相应的格式转换
这里对通配字符串进行格式转换:
skip>0
: 表示前置通配字符串。此时*.example.com
会被转换成com.example.\0
; .example.com
会被转换成 com.example\0
。
skip=0
: 表示后置通配符。此时www.example.*
会被转换成www.example\0
。
5) 在wildcard hash中检查是否有冲突
注意在ha->dns_wc_head_hash
或ha->dns_wc_head_hash
中存放的是没有经过上述4)步骤
所转换的key。例如对于:
-
*.example.com
: 存放的在ha->dns_wc_head_hash
中的值为example.com
-
.example.com
: 存放在ha->dns_wc_head_hash
中的值也为example.com
-
www.example.*
: 存放在ha->dns_wc_tail_hash
中的值为www.example
6) 添加转换后的通配字符串到数组中
这里添加到hk
中的是经过步骤4)
所转换过的字符串。例如对于:
-
*.example.com
: 添加到hk
中为com.example.
-
.example.com
: 添加到hk
中为com.example
-
www.example.*
: 添加到hk
中为www.example
[参看]
-
nginx源码分析之hash的实现
-
nginx通配符哈希表