【nginx源码】nginx哈希表详解

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)。

image

图1:location树示意图

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

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

image

图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):

image

图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所示:

image

图4 通配符哈希表示意图

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

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

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

image
图5 多级通配符哈希表

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

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值