【nginx源码】nginx哈希表详解

【nginx源码】nginx哈希表详解

https://blog.csdn.net/luoyu_/article/details/84071025

图解nginx CYCLE的四级指针

https://blog.csdn.net/VMA_LMA/article/details/52163020

nginx源码分析之模块初始化

https://www.cnblogs.com/chengxuyuancc/p/3792258.html

https://www.cnblogs.com/chengxuyuancc/p/3981077.html

1.基本概念
哈希表是一种存储键值对(key-value)的数据结构,根据键查找值的时间复杂度为O(1)。我们经常见到的哈希表都是采用数组+链表实现(即所谓的拉链法)。

与普通哈希表不同,nginx哈希表有以下特点:

创建nginx哈希表时,所有的key-value键值对已经确定,哈希表创建完成后不允许再添加或者删除键值对
nginx哈希表并没有采用拉链法,而是开放地址法;
nginx哈希表支持前缀与后缀通配符,比如说“.example.com”、“www.example.”,其实现了类似于最大前缀匹配与最大后缀匹配的功能。
第一点简化了nginx哈希表的实现;第二点开放地址法可能很多人都不太熟悉;而最大前缀匹配和最大后缀匹配功能难度较大。

最大前缀匹配与最大后缀匹配最容易实现的方式就是遍历了,此时时间复杂度为O(N)。

而nginx在location匹配时,同样存在最大前缀匹配功能,比如“location /uri {}”,想想这是怎么实现的?location配置被组织成为了一棵三叉树,如图1所示。节点node可能会有三个子节点,left、right与tree;left节点的uri小于node节点的uri,且left节点的uri不是node节点uri的子字符串;right节点的uri大于node节点的uri,且node节点的uri不是right节点uri的子字符串;node节点的uri是tree节点的子字符串。此时location最大前缀匹配时间复杂度为O(logN)。

图1:location树示意图

为什么location最大前缀匹配不能使用哈希表实现呢?复杂度不是可以达到O(1)吗?

需要注意的是nginx哈希表支持前缀与后缀通配符并不是无任何限制的,只支持这三种格式:"*.example.com"、".example.com"和"www.example.*"。想想这种格式与location配置有什么不同呢?location只是普通的字符串匹配,而这里匹配的通常是类似于host这种使用点号分割的,点号将匹配字符串分割成为了很有限的子字符串,可以为这些子字符串建立哈希表,这时候的哈希表也成为了多级哈希表。示意图如图2所示:

图2:多级通配符哈希表示意图

这里再思考一个问题:"*.example.com"和 “.example.com"都用于前缀匹配,有什么区别吗?这里的*其实表示的是至少一个字符,即输入字符串"example.com"时,只能匹配到”.example.com";而输入字符串"www.example.com"时,是能同时匹配到"*.example.com"和 ".example.com"的。读者可以做以下实验,配置多个server,验证host最终会匹配到哪个server_name。

2. nginx哈希表基本数据结构
1)结构体ngx_hash_elt_t表示哈希表的元素,即key-value键值对:

typedef struct {
    void             *value;
    u_short           len;
    u_char            name[1];
} ngx_hash_elt_t;

 

 


value指向值对象;key通常为字符串,len为key长度,name柔性数组存储key内容。

2)普通哈希表(没有前缀匹配符与后缀匹配功能)使用结构体ngx_hash_t表示:

typedef struct {
    ngx_hash_elt_t  **buckets;
    ngx_uint_t        size;
} ngx_hash_t;

 


buckets桶数组,数组中每个元素的类型为ngx_hash_elt_t *,指向当前桶的第一个哈希元素;size为桶数目,hash值对size取模计算桶索引。

所有的哈希元素连续存储在一个字节数组中,当hash冲突时桶,bucket可能需要存储多个哈希元素,这时候如何处理?观察ngx_hash_elt_t结构,元素长度是很容易计算的,所以多个哈希元素连续存储即可,且每个桶的最后都有一个8字节的NULL。

普通哈希表结构如图3所示(元素类型即为ngx_hash_elt_t):

图3:普通哈希表结构

3)nginx多级通配符哈希表与图2非常类似(包含一个头部value与普通哈希表),使用结构ngx_hash_wildcard_t表示,其定义如下:

typedef struct {
    ngx_hash_t        hash;
    void             *value;
} ngx_hash_wildcard_t;

 


多级通配符哈希表通常使用下面代码分配内存空间:

hinit->hash = ngx_pcalloc(hinit->pool, sizeof(ngx_hash_wildcard_t) + size * sizeof(ngx_hash_elt_t *));

buckets = (ngx_hash_elt_t **)((u_char *) hinit->hash + sizeof(ngx_hash_wildcard_t));

hinit->hash->buckets = buckets;

 

 


其中size为桶bucket数目;上面代码创建的通配符哈希表结构如图4所示:

图4 通配符哈希表示意图

假设需要创建一个后缀通配符哈希表,包含key-value为:

apache.* => value1
nginx.* => value2
nginx.doc.* => value3
nginx.api.* => value4
nginx.api.test.* =>value5

 


此时最终创建的多级通配符哈希表如图5所示,后面会详细介绍创建过程。


图5 多级通配符哈希表

4)第一节提到,创建nginx哈希表时,所有的key-value键值对已经确定;因此可以预先计算合适的桶bucket数目,以及每个bucket负责存储哪些key-value等,从而为每个bucket分配足够的内存空间。

但是,如何确保这些key-value键值对的key没有重复呢?针对这些已有的key-value需要有一个去重过程,去重后的元素即为最终哈希表的所有元素;去重过程会创建一个临时的数据结构ngx_hash_keys_arrays_t,该结构用于保存去重后的所有key-value,待创建完成哈希表后这个结构会被释放。

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;

 

 


pool与temp_pool表示内存池,去重过程需要一些临时变量会在temp_pool分配空间,去重过程结束后会释放temp_pool内存池的所有空间;keys、dns_wc_head和dns_wc_tail是三个数组,分别存储普通字符串key、前缀通配符key和后缀通配符key(这些key都是去重后的);注意这三个字段keys_hash、dns_wc_head_hash、dns_wc_tail_hash,这是三个哈希表,采用的是拉链法,毕竟采用哈希表实现去重的复杂度可以达到O(1)。

ngx_hash_keys_arrays_t结构如图4所示:

图6:ngx_hash_keys_arrays_t结构示意图

3.nginx哈希表源码分析
nginx哈希表相关API可以分为3类:

1)哈希键数组相关

ngx_int_t ngx_hash_keys_array_init(ngx_hash_keys_arrays_t *ha, ngx_uint_t type);
ngx_int_t ngx_hash_add_key(ngx_hash_keys_arrays_t *ha, ngx_str_t *key, void *value, ngx_uint_t flags);
1
2
函数ngx_hash_keys_array_init初始化哈希键数组;函数ngx_hash_add_key向哈希键数组中添加键并实现去重功能(包括普通键,前缀与后缀键),其输入第一个参数为哈希键数组。

2)哈希表创建

ngx_int_t ngx_hash_init(ngx_hash_init_t *hinit, ngx_hash_key_t *names, ngx_uint_t nelts);
ngx_int_t ngx_hash_wildcard_init(ngx_hash_init_t *hinit, ngx_hash_key_t *names, ngx_uint_t nelts);
1
2
函数ngx_hash_init创建一个普通哈希表;函数ngx_hash_wildcard_init创建的是多级通配符哈希表。两个函数的输入参数names为哈希键ngx_hash_key_t数组,nelts为哈希键数目。第一个输入参数hinit的类型为ngx_hash_init_t,其存储创建哈希表所需的一些变量,定义如下:

typedef struct {
    ngx_hash_t       *hash;
    ngx_hash_key_pt   key;

    ngx_uint_t        max_size;
    ngx_uint_t        bucket_size;

    char             *name;
    ngx_pool_t       *pool;
    ngx_pool_t       *temp_pool;
} ngx_hash_init_t;

 

 


其中hash指向最终创建的哈希表;key是一个函数指针,用于计算键哈希值;max_sizewei最大桶数目;bucket_size为每个桶最大字节大小;name为哈希表的名称;pool和temp_pool为内存池,只是temp_pool表示的是临时内存池,创建完哈希表会被回收。

3)查找哈希表

void *ngx_hash_find(ngx_hash_t *hash, ngx_uint_t key, u_char *name, size_t len);
void *ngx_hash_find_wc_head(ngx_hash_wildcard_t *hwc, u_char *name, size_t len);
void *ngx_hash_find_wc_tail(ngx_hash_wildcard_t *hwc, u_char *name, size_t len);
1
2
3
这三个函数分别实现了普通哈希表查找、前缀通配符哈希表查找和后缀通配符哈希表查找功能。

3.1 哈希键数组初始化
函数ngx_hash_keys_array_init只是初始化结构体ngx_hash_keys_arrays_t,比较简单,这里不做详述。

函数ngx_hash_add_key向哈希键数组中添加键并去重,其主要包括以下6个过程:

1)判断键类型(普通键/前缀通配符/后缀通配符)

if (key->len > 1 && key->data[0] == '.') {
    skip = 1;
    goto wildcard;
}

if (key->len > 2) {

    if (key->data[0] == '*' && key->data[1] == '.') {
        skip = 2;
        goto wildcard;
    }

    if (key->data[i - 2] == '.' && key->data[i - 1] == '*') {
        skip = 0;
        last -= 2;
        goto wildcard;
    }
}

 

 


变量skip表示跳过的字符串数目;变量last表示字符串结束索引;通过skip可以区分出键字符串的类型。

键字符串    分类类型
www.example.com    无
*.example.com    skip=2
.example.com    skip=1
www.example.*    skip=0
2)普通键去重

判断该键是否已经存在keys_hash哈希表中,如果不存在则添加该键到keys_hash哈希表与keys键数组中;如果存在则返回NGX_BUSY;

3)skip=1第一次去重

当键字符串格式为".example.com"时,skip=1,第一节就提到其可以匹配输入字符串"example.com"。那假如普通键中存在着"example.com"呢?岂不是冲突了?所以对于skip=1这种类型,同样需要校验是否已经存在keys_hash哈希表中,如果存在则返回NGX_BUSY;

4)前缀通配符转换

前缀通配符格式"*.example.com"和 “.example.com”,无法直接通过哈希表实现前缀匹配功能,键格式需要做一步转换过程,如下表:

原始字符串    转换结果
*.example.com    com.example.\0
.example.com    com.example\0
转换后的字符串通过最后一个字符是否为点号可以区分两种前缀通配符格式。转换代码如下:

for (i = last - 1; i; i--) {
    if (key->data[i] == '.') {
        ngx_memcpy(&p[n], &key->data[i + 1], len);
        n += len;
        p[n++] = '.';
        len = 0;
        continue;
    }

    len++;
}

if (len) {
    ngx_memcpy(&p[n], &key->data[1], len);
    n += len;
}

 


5)后缀通配符转换

后缀通配符"www.example.*“会转换为"www.example\0”;(后缀通配符只有一种格式,因此点号不需要保留)

6)通配符键去重

校验前缀通配符键是否已经存在dns_wc_head_hash哈希表,如果存在则返回NGX_BUSY;如果不存在则将转换后的键字符串添加到dns_wc_head_hash哈希表和dns_wc_head键数组;

校验后缀通配符键是否已经存在dns_wc_tail_hash哈希表,如果存在则返回NGX_BUSY;如果不存在则将转换后的键字符串添加到dns_wc_tail_hash哈希表和dns_wc_tail键数组;

3.2 创建普通哈希表
nginx哈希表一大特点就是在创建哈希表时所有的key-value都已确定,且创建后不会再添加或者删除哈希元素。因此创建哈希表时可以预先计算好合适的桶桶数目,以及每个桶存储哪些元素,以及每个桶的字节大小等。因此创建普通哈希表可以分为以下7个过程:

1)首先需要校验桶最大字节大小是否可以至少存储一个哈希元素

for (n = 0; n < nelts; n++) {
    if (hinit->bucket_size < NGX_HASH_ELT_SIZE(&names[n]) + sizeof(void *)){
     
    }
}

 


哈希元素字节长度比较容易计算,需要按照8字节对齐,在GDB打印桶中元素时需要也别注意。

#define NGX_HASH_ELT_SIZE(name)                                               \
    (sizeof(void *) + ngx_align((name)->key.len + 2, sizeof(void *)))
1
2
2)选取合适的桶数目。什么样的桶数目才算是满足条件的呢?通数目已知,可以计算出每个哈希元素的桶索引,统计每个桶存储的哈希元素长度之和,只要没有超过桶最大字节大即可。

计算合适的桶数目需要遍历,首先会粗略计算出一个最小桶数目start,最大桶数目max_size,在此范围内遍历即可。

每个哈希元素最小为16字节,因此计算最小桶数目为(每个桶最后都有8字节的NULL表示桶结束):

bucket_size = hinit->bucket_size - sizeof(void *);

start = nelts / (bucket_size / (2 * sizeof(void *)));
start = start ? start : 1;


其中nelts为哈希元素数目。

这里还需要统计每个桶存储的哈希元素长度之和,因此分配test数组(用于计算桶元素总长度):

test = ngx_alloc(hinit->max_size * sizeof(u_short), hinit->pool->log);
1
遍历过程如下:


for (size = start; size <= hinit->max_size; size++) {

    ngx_memzero(test, size * sizeof(u_short));

    for (n = 0; n < nelts; n++) {
        if (names[n].key.data == NULL) {
            continue;
        }

        key = names[n].key_hash % size;
        test[key] = (u_short) (test[key] + NGX_HASH_ELT_SIZE(&names[n]));

        if (test[key] > (u_short) bucket_size) {
            goto next;
        }
    }

    goto found;

    next:
        continue;
}

 

 

 


这块代码还是比较简单的。需要注意的是这里for循环使用的是goto next,不能直接continue吗?当然肯定是不行的。

3)计算每个桶存储的所有哈希元素总长度,并分配空间

for (i = 0; i < size; i++) {
        test[i] = sizeof(void *);
    }

for (n = 0; n < nelts; n++) {
    if (names[n].key.data == NULL) {
        continue;
    }

    key = names[n].key_hash % size;
    test[key] = (u_short) (test[key] + NGX_HASH_ELT_SIZE(&names[n]));
}

 

 


size即为上面计算的桶数目。这里需要注意的是每个哈希桶最后肯定都有一个8字节的NULL表示桶结束。

for (i = 0; i < size; i++) {
    if (test[i] == sizeof(void *)) {
        continue;
    }

    test[i] = (u_short) (ngx_align(test[i], ngx_cacheline_size));

    len += test[i];
}

elts = ngx_palloc(hinit->pool, len + ngx_cacheline_size);

 

 


遍历test数组,计算所有桶总长度,并分配内存空间。注意这里在分配空间时,按照ngx_cacheline_size大小字节对齐了;CPU在加载内存中数据到高速缓存时,是一个加载一个数据块,数据块大小称之为cacheline_size,通常为64字节;分配内存时按照cacheline_size字节对齐加载到高速缓存效率较高。

4)前面已经计算出每个桶所有元素总长度,因此这里可以将桶指针指向每个桶第一个元素首地址;

for (i = 0; i < size; i++) {
    if (test[i] == sizeof(void *)) {
        continue;
    }

    buckets[i] = (ngx_hash_elt_t *) elts;
    elts += test[i];

}

 


5)存储每个哈希元素到响应桶位置;

for (n = 0; n < nelts; n++) {
    if (names[n].key.data == NULL) {
        continue;
    }

    key = names[n].key_hash % size;
    elt = (ngx_hash_elt_t *) ((u_char *) buckets[key] + test[key]);

    elt->value = names[n].value;
    elt->len = (u_short) names[n].key.len;

    ngx_strlow(elt->name, names[n].key.data, names[n].key.len);

    test[key] = (u_short) (test[key] + NGX_HASH_ELT_SIZE(&names[n]));
}

 

 


6)每个桶最后添加8字节NULL结束标志;

for (i = 0; i < size; i++) {
        if (buckets[i] == NULL) {
            continue;
        }

        elt = (ngx_hash_elt_t *) ((u_char *) buckets[i] + test[i]);

        elt->value = NULL;
    }

7)一些收尾工作

ngx_free(test);

    hinit->hash->buckets = buckets;
    hinit->hash->size = size;


3.3 创建前缀通配符哈希表
前缀通配符哈希表重在分级,即按照点号将键字符串切割为多个子字符串,为第一个子字符串创建第一级哈希表,第二个子字符串创建第二级哈希表……;多级哈希表之间还之前了类似于链式引用。

前缀通配符哈希表的创建函数为ngx_hash_wildcard_init,遍历所有哈希键,将其按照点号分割,并创建多级哈希表。函数通过递归调用实现多级哈希表的创建以及引用功能。

将每个哈希键按照点号分割后,每一级的子字符串可能还需要做聚合操作。比如"com.api."、“com.doc.”、“com.doc.nginx.”、“com.aoc.apache.”,分割后第一级子字符串4个都是com,第二级子字符串1个为api,三个为doc,第三级子字符串分别为nginx何apache;每一级子字符串相等时需要做聚合操作。

另外需要注意一点的是,在调用函数ngx_hash_wildcard_init创建通配符哈希表之前,所有的哈希键都会进行排序,比如:

ngx_qsort(ha.dns_wc_head.elts, (size_t) ha.dns_wc_head.nelts,
                  sizeof(ngx_hash_key_t), ngx_http_cmp_dns_wildcards);
1
2
函数ngx_http_cmp_dns_wildcards为键比较函数,键按照字母序比较。

为了更好的理解ngx_hash_wildcard_init函数实现,请参照下图7多级通配符哈希结构图表。

字符串分割,聚合功能代码实现如下:


for (n = 0; n < nelts; n = i) {
    for (len = 0; len < names[n].key.len; len++) {
        if (names[n].key.data[len] == '.') {
            dot = 1;
            break;
        }
    }
    
    //当前级子字符串存储在变量curr_names;
    //用于创建当前级哈希表
    name = ngx_array_push(&curr_names);
    name->key.len = len;
    name->key.data = names[n].key.data;
    name->value = names[n].value;

    //后续子字符串存储在变量next_names;
    //用于递归调用函数ngx_hash_wildcard_init创建多级哈希表
    if (names[n].key.len != len) {
        next_name = ngx_array_push(&next_names);
        if (next_name == NULL) {
            return NGX_ERROR;
        }

        next_name->key.len = names[n].key.len - len;
        next_name->key.data = names[n].key.data + len;
        next_name->value = names[n].value;
    }

    //注意在查找后续键字符串中当前级子字符串与当前键当前级子字符串相等的键
    for (i = n + 1; i < nelts; i++) {
        if (ngx_strncmp(names[n].key.data, names[i].key.data, len) != 0) {
            break;
        }

        if (!dot
            && names[i].key.len > len
            && names[i].key.data[len] != '.'){
            break;
        }

        next_name = ngx_array_push(&next_names);
        

        next_name->key.len = names[i].key.len - dot_len;
        next_name->key.data = names[i].key.data + dot_len;
        next_name->value = names[i].value;
    }
}

 

 

 


变量curr_names存储的是当前级的子字符串,用于创建当前级哈希表;next_names存储的是下一级子字符串,用于递归调用函数ngx_hash_wildcard_init创建下一级哈希表。

注意在查找后续键字符串中当前级子字符串与当前键当前级子字符串相等的键时,一旦发现子字符串不相等直接break结束循环,不需要遍历所有键字符串吗?肯定是不需要的,因为在调用函数ngx_hash_wildcard_init创建通配符哈希表之前,所有的哈希键都进行了排序。

上面代码进行了键字符串的分割,子字符串的聚合,接下来会递归调用函数ngx_hash_wildcard_init创建下一级哈希表,并实现哈希表链级引用


for (n = 0; n < nelts; n = i) {
    if (next_names.nelts) {

    h = *hinit;
    h.hash = NULL;
    //注意调用ngx_hash_wildcard_init函数时,都会初始化h.hash为null,待会分析为什么需要这么做
    if (ngx_hash_wildcard_init(&h, (ngx_hash_key_t *) next_names.elts,
                               next_names.nelts)
        != NGX_OK)
    {
        return NGX_ERROR;
    }

    wdc = (ngx_hash_wildcard_t *) h.hash;

    //当前键已经处理完毕时,下一级哈希表头部value字段需要赋值
    if (names[n].key.len == len) {
        wdc->value = names[n].value;
    }

    //指向下一级哈希表首地址
    name->value = (void *) ((uintptr_t) wdc | (dot ? 3 : 2));

    } else if (dot) {
    //指向哈希元素值对象
        name->value = (void *) ((uintptr_t) name->value | 1);
    }
}

 

 

 


这里需要重点分析两点:

1)调用ngx_hash_wildcard_init函数创建通配符哈希表时,为什么h.hash一定是NULL呢?原因在于函数ngx_hash_init创建哈希表时,会有这么一步操作:

if (hinit->hash == NULL) {
    hinit->hash = ngx_pcalloc(hinit->pool, sizeof(ngx_hash_wildcard_t)+ size * sizeof(ngx_hash_elt_t *));
        
    buckets = (ngx_hash_elt_t **) ((u_char *) hinit->hash + sizeof(ngx_hash_wildcard_t));

    
}else {
        buckets = ngx_pcalloc(hinit->pool, size * sizeof(ngx_hash_elt_t *));
}

 

 


hinit->hash等于NULL时创建的是通配符哈希表结构;否则创建的是通用哈希表结构。

2)创建完成下一级哈希表时,多级哈希表需要形成链式结构串起来。因此name->value字段存储的要么是下一级哈希表的首地址,要么直接是哈希元素值对象。

注意这里判断变量dot是否为true,以及是否有下一级哈希表,会对value按位或上0、1、2或者3。令flag为value低位2比特,其标记value字段存储的是什么数据。

当flag为0或者1时,表示value指向的是哈希元素值对象;当flag等于2或者3时,表示value指向的是下一级哈希表。

那为什么需要判断遍历dot呢?还记得之前讲述的"*.example.com"和 ".example.com"前缀匹配的区别吗?这两者键字符串分别会被转换为"com.example.“和"com.example”,最后一个字符点号用于区分这两种类型,这里需要通过flag标记,否则查找时将无法区分了。

想想为什么可以value按位或上0、1、2和3呢?不会修改value的值吗?因为value存储的是地址,且这块内存通常都会按照8字节或者其他字节对齐;即value的低2比特恒为0。

下一级哈希表创建完毕后,会调用函数ngx_hash_init当前级哈希表,该函数已经讲过,这里不再赘述。

假设需要创建一个前缀通配符哈希表,包含key-value为:

*.apache => value1
*.nginx => value2
*.doc.nginx => value3
*.api.nginx => value4
*.test.api.nginx =>value5

 

 


最终创建的多级通配符哈希表如图7所示:

图7 多级通配符哈希表结构

3.4 查找前缀通配符哈希表
前缀通配符查找时,同样需要将输入字符串按照点号倒序分割,如example.com会分割为com和example两个子字符串;先使用com子字符串在第一级哈希表中查找,判断查找到的value类型是哈希元素值对象还是下一级哈希表地址,以及是"*.example.com"还是 ".example.com"前缀匹配,以此决定继续查找下一级哈希表,还是返回NUll,还是返回value。

下面代码实现了输入字符串倒序分割的功能

while (n) {
        if (name[n - 1] == '.') {
            break;
        }

        n--;
    }

    key = 0;

    for (i = n; i < len; i++) {
        key = ngx_hash(key, name[i]);
    }

 


分割并计算该子字符串hash值后,会按照普通哈希表查找方式查找:

value = ngx_hash_find(&hwc->hash, key, &name[n], len - n);
1
函数ngx_hash_find实现比较简单,这里不做详述;难点应该在于value标志位的判定。

//value标志位为2或者3,说明指向的是下一级哈希表
if ((uintptr_t) value & 2) {

    //当前输入字符串已经遍历结束
    if (n == 0) {

        //value标志位为3;说明前缀匹配格式应该为"*.example.com";
        //此时不满足条件,返回NULL
        if ((uintptr_t) value & 1) {
            return NULL;
        }

        //value标志位为2;说明前缀匹配格式应该为".example.com";
        //返回头部value
        hwc = (ngx_hash_wildcard_t *)
                                  ((uintptr_t) value & (uintptr_t) ~3);
        return hwc->value;
    }

    //没有遍历完输入字符串;
    //继续查找下一级哈希表,输入字符串同时被修改了
    hwc = (ngx_hash_wildcard_t *) ((uintptr_t) value & (uintptr_t) ~3);

    value = ngx_hash_find_wc_head(hwc, name, n - 1);

    if (value) {
        return value;
    }

    return hwc->value;
}

//value标志位为1,指向的是哈希元素值对象;
//且前缀匹配格式应该为"*.example.com"
if ((uintptr_t) value & 1) {

    if (n == 0) {

        /* "example.com" */

        return NULL;
    }

    return (void *) ((uintptr_t) value & (uintptr_t) ~3);
}

//value标志位为0,指向的是哈希元素值对象;
//且前缀匹配格式应该为".example.com";
return value;

 



参照图7多级通配符哈希表结构图,标志位的判断以及value的取值应该比较容易理解了。

总结
nginx哈希表基于开放地址法,且支持前缀后缀通配符哈希表,本文主要对普通哈希表与前缀通配符符哈希表做了详细介绍,后缀通配符哈希表同理可得。
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值