Nginx高效数据结构(4)——Hash表(ngx_hash_t)

Nginx是我们学习编程的一个非常有参考价值的开源项目。良好的编码风格,高效的数据结构、架构设计。

快课网在此搜罗了一些优质资源。从本文开始讲述Nginx中常用的数据结构,主要包括Nginx的数组结构链表结构队列hash结构内存池等。

在阅读本文之前,建议先阅读文章:哈希表

0. 

本文继续介绍nginx的数据结构——hash结构。

链表实现文件:文件:./src/core/ngx_hash.h/.c.表示nginx-1.0.4代码目录,本文为/usr/src/nginx-1.0.4

1. hash结构

nginxhash结构比其listarrayqueue等结构稍微复杂一些,下图是hash相关数据结构图。下面一一介绍。

1.1 ngx_hash_t结构

nginxhash结构为ngx_hash_thash元素结构为ngx_hash_elt_t,定义如下。

其中,sizeof(ngx_hash_t) = 8sizeof(ngx_hash_elt_t) = 8。实际上,ngx_hash_elt_t结构中的name字段就是ngx_hash_key_t结构中的key。这在ngx_hash_init()函数中可以看到,请参考后续的分析。该结构在模块配置解析时经常使用。

1.2 ngx_hash_init_t结构

nginxhash初始化结构是ngx_hash_init_t,用来将其相关数据封装起来作为参数传递给ngx_hash_init()ngx_hash_wildcard_init()函数。这两个函数主要是在http相关模块中使用,例如ngx_http_server_names()函数(优化http Server Names)ngx_http_merge_types()函数(合并httptype)ngx_http_fastcgi_merge_loc_conf()函数(合并FastCGI Location Configuration)等函数或过程用到的参数、局部对象/变量等。这些内容将在后续的文章中讲述。

ngx_hash_init_t结构如下。sizeof(ngx_hash_init_t)=28

1.3 ngx_hash_key_t结构

该结构也主要用来保存要hash的数据,即键-值对<key,value>,在实际使用中,一般将多个键-值对保存在ngx_hash_key_t结构的数组中,作为参数传给ngx_hash_init()ngx_hash_wildcard_init()函数。其定义如下。

其中,sizeof(ngx_hash_key_t) = 16。一般在使用中,value指针可能指向静态数据区(例如全局数组、常量字符串)、堆区(例如动态分配的数据区用来保存value)等。可参考本文后面的例子。

关于ngx_table_elt_t结构和ngx_hash_keys_arrays_t结构,因其对于hash结构本身没有太大作用,主要是为模块配置、referer合法性验证等设计的数据结构,例如httpcore模块、map模块、referer模块、SSI filter模块等,此处不再讲述,将在后续的文章中介绍。

1.4 hash的逻辑结构

ngx_hash_init_t结构引用了ngx_pool_t结构。注:本文采用UML的方式画出该图。

 

hash01

2. hash操作

2.1 NGX_HASH_ELT_SIZE

NGX_HASH_ELT_SIZE宏用来计算上述ngx_hash_elt_t结构大小,定义如下。

32位平台上,sizeof(void*)=4(name)->key.len即是ngx_hash_elt_t结构中name数组保存的内容的长度,其中的“+2″是要加上该结构中len字段(u_short类型)的大小。

因此,NGX_HASH_ELT_SIZE(name)=4+ngx_align((name)->key.len + 2, 4),该式后半部分即是(name)->key.len+24字节对齐的大小。

2.2 hash函数

nginx-1.0.4提供的hash函数有以下几种。

hash函数都很简单,以上3个函数都会调用ngx_hash宏,该宏返回一个()整数。此处介绍第一个函数,定义如下。

因此,ngx_hash_key函数的计算可表述为下列公式。

key[len-1]即为传入的参数data对应的hash值。

2.3 hash初始化

hash初始化由ngx_hash_init()函数完成,其names参数是ngx_hash_key_t结构的数组,即键-值对<key,value>数组,nelts表示该数组元素的个数。因此,在调用该函数进行初始化之前,ngx_hash_key_t结构的数组是准备好的,如何使用,可以采用nginxngx_array_t结构,详见本文后面的例子。

该函数初始化的结果就是将names数组保存的键-值对<key,value>,通过hash的方式将其存入相应的一个或多个hash(即代码中的buckets)中,该hash过程用到的hash函数一般为ngx_hash_key_lc等。hash桶里面存放的是ngx_hash_elt_t结构的指针(hash元素指针),该指针指向一个基本连续的数据区。该数据区中存放的是经hash之后的键-值对<key’,value’>,即ngx_hash_elt_t结构中的字段<name,value>。每一个这样的数据区存放的键-值对<key’,value’>可以是一个或多个。

此处有几个问题需要说明。

问题1:为什么说是基本连续?

——用NGX_HASH_ELT_SIZE宏计算某个hash元素的总长度时,存在以sizeof(void*)对齐的填补(padding)。因此将names数组中的键-值对<key,value>中的key拷贝到ngx_hash_elt_t结构的name[1]数组中时,已经为该hash元素分配的空间不会完全被用完,故这个数据区是基本连续的。这一点也可以参考本节后面的结构图或本文后面的例子。

问题2:这些基本连续的数据区从哪里分配的?

——当然是从该函数的第一个参数ngx_hash_init_tpool字段指向的内存池中分配的。

问题3<key’,value’><key,value>不同的是什么?

——key保存的仅仅是个指针,而key’却是key拷贝到name[1]的结果。而valuevalue’都是指针。如1.3节说明,value指针可能指向静态数据区(例如全局数组、常量字符串)、堆区(例如动态分配的数据区用来保存value)等。可参考本文后面的例子。

问题4:如何知道某个键-值对<key,value>放在哪个hash桶中?

——key = names[n].key_hash % size; 代码中的这个计算是也。计算结果key即是该键要放在那个hash桶的编号(0size-1)

该函数代码如下。一些疑点、难点的解释请参考//后笔者所加的注释,也可参考本节的hash结构图。

所谓的hash数据长度即指ngx_hash_elt_t结构被赋值后的长度。nelts个元素存放在names数组中,调用该函数对hash进行初始化之后,这nelts个元素被保存在sizehash桶指向的ngx_hash_elts_t数据区,这些数据区中共保存了neltshash元素。即hash(buckets)存放的是ngx_hash_elt_t数据区的起始地址,以该起始地址开始的数据区存放的是经hash之后的hash元素,每个hash元素的最后是以name[0]为开始的字符串,该字符串就是names数组中某个元素的key,即键值对<key,value>中的key,然后该字符串之后会有几个字节的因对齐产生的padding

一个典型的经初始化后的hash物理结构如下。具体的可参考后文的例子。

hash02

 

2.4 hash查找

 

hash查找操作由ngx_hash_find()函数完成,代码如下。//后的注释为笔者所加。

 

查找操作相当简单,由key直接计算所在的bucket,该bucket中保存其所在ngx_hash_elt_t数据区的起始地址;然后根据长度判断并用name内容匹配,匹配成功,其ngx_hash_elt_t结构的value字段即是所求。

 

3. 一个例子

本节给出一个创建内存池并从中分配hash结构、hash桶、hash元素并将键-值对<key,value>加入该hash结构的简单例子。

在该例中,将完成这样一个应用,将给定的多个url及其ip组成的二元组<url,ip>作为<key,value>,初始化时对这些<url,ip>进行hash,然后根据给定的url查找其对应的ip地址,若没有找到,则给出相关提示信息。以此向读者展示nginxhash使用方法。

3.1代码

3.2如何编译

本文编写的makefile文件如下。

3.3 运行结果

3.3.1 bucket_size=64字节

bucket_size=64字节时,运行结果如下。

以上结果是bucket_size=64字节的输出。由该结果可以看出,对于给定的7url,程序将其分到了3bucket中,详见该结果。该例子的hash物理结构图如下。

hash03

3.3.2 bucket_size=256字节

bucket_size=256字节时,运行结果如下。

以上结果是bucket_size=256字节的输出。由给结果可以看出,对于给定的7url,程序将其放到了1bucket中,即ngx_hash_init()函数中的size=1,因这7url的总长度只有140,因此,只需size=1bucket,即buckets[0]

下表是ngx_hash_init()函数在计算过程中的一些数据。物理结构图省略,可参考上图。

url

计算长度test[0]的值
www.baidu.com4+ngx_align(13+2,4)=20

20

www.sina.com.cn4+ngx_align(15+2,4)=24

44

www.google.com4+ngx_align(14+2,4)=20

64

www.qq.com4+ngx_align(10+2,4)=16

80

www.163.com4+ngx_align(11+2,4)=20

100

www.sohu.com4+ngx_align(12+2,4)=20

120

www.abo321.org4+ngx_align(14+2,4)=20

140

 4. 小结

本文针对nginx-1.0.4hash结构进行了较为全面的分析,包括hash结构、hash元素结构、hash初始化结构等,hash操作主要包括hash初始化、hash查找等。最后通过一个简单例子向读者展示nginxhash使用方法,并给出详细的运行结果,且画出hash的物理结构图,以此向图这展示hash的设计、原理;同时借此向读者展示编译测试nginx代码的方法。

系列文章:

从开源代码Nginx中学习编码风格

nginx架构初探

Nginx高效数据结构(1)——数组(ngx_array_t)

Nginx高效数据结构(2)——链表(ngx_list_t)

Nginx高效数据结构(3)——队列(ngx_queue_t)

Nginx高效数据结构(4)——Hash表(ngx_hash_t)

Nginx高效数据结构(5)——内存池(ngx_pool_t)

作者:阿波

EDITED BY:快课(www.cricode.com)

本文链接:http://cricode.com/2994.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值