Nginx基础. Nginx基本哈希表
来源:互联网 发布:网站源代码怎么修改seo 编辑:程序博客网 时间:2024/05/16 14:32
根据以往的学习经验, 比如STL中的哈希表, 利用开链法, vector+list作为容器, 当hashtable中的元素总数超过一定数量时, 选择扩充vector.
再比如libevent中的哈希表, 与STL中的哈希表类似, 但比较复杂, 每个bucket中都可能有一个链表,每个链表元素中也可能存在一个链表. 但理解起来都并不复杂.
现在看的Nginx中的哈希表, 则与上面谈到的哈希有很明显的不同之处.
在nginx中, 存储server_name和ngx_http_core_srv_conf_t的映射时用到了hash结构.
配置server_names_hash_max_size可以控制bucket的最大数量,server_names_hash_bucket_size可以控制每个bucket的大小
下图, 就是nginx存储不含通配符的server_name时使用的hash结构:
同时, 每个bucket的大小必须能保证存放至少一个元素.无论这个元素的大小是多少.
好了, 大致的介绍就到这里. 下面是详细的代码分析.
一、 数据结构的定义
1、基本哈希表的元素
每个元素都是 key-value的形式
2、基本哈希表结构
3、支持通配符的哈希表结构
其实也就是多了一个额外的value指针, 当使用ngx_hash_wildcard_t通配符哈希表作为容器元素时,可以使用value指向用户数据。
这里暂时不做多解释
4、该结构也主要用来保存要hash的数据,即键-值对<key,value>
在实际使用中,一般将多个键-值对保存在ngx_hash_key_t结构的数组中,作为参数传给ngx_hash_init()或ngx_hash_wildcard_init()函数
用于表示即将添加到哈希表中的元素
下面这张图来自:http://blog.csdn.net/chen19870707/article/details/40794285 (若有侵犯, 立刻删除)
二、基本哈希表的初始化
在分析初始化哈希表的代码之前, 还要提一件事. 就是每个bucket中每个元素的大小. 既然它使用了1长度数组的策略, 那么求其占用内存的大小就不再是单纯的使用sizeof了
先加上一个void指针(指向value)的大小, 在64位机器上即为8字节. 然后再加上key的大小, key的大小使用len来表示了, 再加上short类型的2字节, 但是单纯的相加显然不符合内存地址对齐的规则.
比如我们定义了下面这个结构体:
所以这个宏就顺利的求出了某元素占用的内存大小的值.
下面就是哈希表初始化的详细代码以及解析
三、基本哈希表的查找.
最后, 贴上借鉴的几篇博客:
http://blog.csdn.net/livelylittlefish/article/details/6636229
http://blog.csdn.net/chen19870707/article/details/40794285
http://www.linuxidc.com/Linux/2012-08/67040.htm
http://blog.chinaunix.net/uid-27767798-id-3766755.html
感谢!
再比如libevent中的哈希表, 与STL中的哈希表类似, 但比较复杂, 每个bucket中都可能有一个链表,每个链表元素中也可能存在一个链表. 但理解起来都并不复杂.
现在看的Nginx中的哈希表, 则与上面谈到的哈希有很明显的不同之处.
在nginx中, 存储server_name和ngx_http_core_srv_conf_t的映射时用到了hash结构.
配置server_names_hash_max_size可以控制bucket的最大数量,server_names_hash_bucket_size可以控制每个bucket的大小
下图, 就是nginx存储不含通配符的server_name时使用的hash结构:
进行解释之前, 需要提一下, nginx中的哈希表一个特别之处在于, 这个hash表是静态只读的,即不能在运行时动态添加新元素的,一切的结构和数据都在配置初始化的时候就已经规划完毕,所以“init”过程的优劣,对运行时查找的性能影响非常大
我们假设哈希表的bucket数量为size , 每个元素的哈希值为 [hash%size], 从上图可以看到, 此哈希表解决冲突的办法类似开链, 但又并不是使用链表来存储具有相同 [hash%size] 值的元素, 而是令具有相同[hash%size]值的元素存放在一块连续的内存中, 以一个NULL指针作为结尾. 上面我们说到了两个可以控制bucket的最大数量和每个bucket的大小的配置, 但并没有说这个哈希表的bucket数量到底是多少. 试想, 当前我们的bucket大小是确定的, 假如冲突太多, 即有多个元素被存放在同一个bucket, 这样肯定会导致某bucket"超载". 那么解决的办法就是扩大bucket的数量, 即size的值(这样不仅能使原来具有相同[hash%size]的元素不再相同, 还能使冲突发生的更稀疏). 看看能不能保证所有存放多个相同 [hash%size]的元素的bucket不"超载". 但是既然我们设定了bucket的最大数量, 一旦size达到这个值还没能满足我们的要求, 那么就返回错误.同时, 每个bucket的大小必须能保证存放至少一个元素.无论这个元素的大小是多少.
好了, 大致的介绍就到这里. 下面是详细的代码分析.
一、 数据结构的定义
1、基本哈希表的元素
每个元素都是 key-value的形式
typedef struct { void *value; //即为key-value中对应的value u_short len; //为key-value中key的长度 u_char name[1]; //为key的首地址. 使用长度为1的数组是为了将来申请的len大小的空间是连续的(详细的请搜索 "0或1长度数组")} ngx_hash_elt_t;
2、基本哈希表结构
typedef struct { ngx_hash_elt_t **buckets; //即为哈希表 ngx_uint_t size; //哈希表中bucket的个数} ngx_hash_t;
3、支持通配符的哈希表结构
其实也就是多了一个额外的value指针, 当使用ngx_hash_wildcard_t通配符哈希表作为容器元素时,可以使用value指向用户数据。
这里暂时不做多解释
typedef struct { ngx_hash_t hash; void *value;} ngx_hash_wildcard_t;
4、该结构也主要用来保存要hash的数据,即键-值对<key,value>
在实际使用中,一般将多个键-值对保存在ngx_hash_key_t结构的数组中,作为参数传给ngx_hash_init()或ngx_hash_wildcard_init()函数
用于表示即将添加到哈希表中的元素
typedef struct { ngx_str_t key; ngx_uint_t key_hash; //由哈希函数根据key计算出的值. 将来此元素代表的结构体会被插入bucket[key_hash % size] void *value;} ngx_hash_key_t;
5、哈希表初始化用的结构体
typedef struct { ngx_hash_t *hash; ngx_hash_key_pt key; //即哈希函数 ngx_uint_t max_size; //bucket的最大数量 ngx_uint_t bucket_size; //每个bucket的容量 char *name; //log用 ngx_pool_t *pool; //是在pool中进行内存处理的 ngx_pool_t *temp_pool;} ngx_hash_init_t;上面的5个结构体之间的关系必须搞清楚, 所以下面提前附上一张图. 如果看不懂图, 也没关系, 总之在上面的结构体之间关系有个印象后, 继续下面的源代码分析.
下面这张图来自:http://blog.csdn.net/chen19870707/article/details/40794285 (若有侵犯, 立刻删除)
二、基本哈希表的初始化
在分析初始化哈希表的代码之前, 还要提一件事. 就是每个bucket中每个元素的大小. 既然它使用了1长度数组的策略, 那么求其占用内存的大小就不再是单纯的使用sizeof了
typedef struct { void *value; //即为key-value中对应的value u_short len; //为key-value中key的长度 u_char name[1]; //为key的首地址. 使用长度为1的数组是为了将来申请的len大小的空间是连续的(详细的请搜索 "0或1长度数组")} ngx_hash_elt_t;于是有了下面这个宏:
#define NGX_HASH_ELT_SIZE(name) \ (sizeof(void *) + ngx_align((name)->key.len + 2, sizeof(void *)))该宏求大小的过程是:
先加上一个void指针(指向value)的大小, 在64位机器上即为8字节. 然后再加上key的大小, key的大小使用len来表示了, 再加上short类型的2字节, 但是单纯的相加显然不符合内存地址对齐的规则.
比如我们定义了下面这个结构体:
struct test{ int x; char y;};那么sizeof(struct test)的大小在编译器内存地址对齐处理过后结果就是8了. 但是这里是我们自己管理内存, 所以内存对齐的事情必须我们自己完成. 所以既然当前的指针占8个字节, 如果key的len加上2的值为13的话, 就应该被调整为16个字节; 如果值为17的话, 就应该被调整为24字节大小.
所以这个宏就顺利的求出了某元素占用的内存大小的值.
下面就是哈希表初始化的详细代码以及解析
ngx_int_t//传入的参数分别是:// 初始化用的结构体// 用于表示即将添加到哈希表中的元素, key-value组成的数组// 即将添加到哈希表中的元素的个数ngx_hash_init(ngx_hash_init_t *hinit, ngx_hash_key_t *names, ngx_uint_t nelts){ u_char *elts; size_t len; u_short *test; ngx_uint_t i, n, key, size, start, bucket_size; ngx_hash_elt_t *elt, **buckets; //这个for循环的主要作用是, 判断配置选项配置的每个bucket的容量是否能装的下任意一个元素(元素之间len可以不同). (换句话说, 就是要求每个bucket至少能装一个元素, 无论它是哪个元素) //如果不符合, 就会返回错误 for (n = 0; n < nelts; n++) { //下面这个判断就是说, name[n]中的key-value如果被装进ngx_hash_elt_t结构体(哈希表的每个元素的结构), 那么最后此元素的大小必须小于bucket的容量 //否则, 说明我们设置的bucket容量太小 //在调用宏之后还要加上一个指针的大小, 可以根据文章开头给出的图看出来, 每个bucket最后都会有个NULL指针作为n个元素的结尾 if (hinit->bucket_size < NGX_HASH_ELT_SIZE(&names[n]) + sizeof(void *)) { ngx_log_error(NGX_LOG_EMERG, hinit->pool->log, 0, "could not build the %s, you should " "increase %s_bucket_size: %i", hinit->name, hinit->name, hinit->bucket_size); return NGX_ERROR; } } //下面这个test的用处多多. 它的大小是max_size, 是允许的bucket的最大个数. //它的大小就表明, 以后使用这个test数组, 它与bucket是一一对应的. 即bucket[i]与test[i]是相关的 test = ngx_alloc(hinit->max_size * sizeof(u_short), hinit->pool->log); if (test == NULL) { return NGX_ERROR; } //bucket_size表示给定的每个bucket的容量. //减去的就是最后的那个NULL指针的大小. 这个元素是个哨兵元素,用来判断当前bucket是否还有元素 bucket_size = hinit->bucket_size - sizeof(void *); //既然我们不知到bucket的个数, 那么我们当然是从最小的size开始找(能少就少...) //什么才是最小的size呢? 根据地址对齐, 一个ngx_hash_elt_t元素最少也要2*8个字节, 那么就拿这个值来找最小的size. start = nelts / (bucket_size / (2 * sizeof(void *))); start = start ? start : 1; //根据实战经验做调整...无法理解 if (hinit->max_size > 10000 && nelts && hinit->max_size / nelts < 100) { start = hinit->max_size - 1000; } //现在, 我们已经得到了最小的可能size值. size个bucket能把nelts个元素全装下, 假设每个elt的大小都只占16字节. 但假设是不成立的, 所以必须找更合适的size for (size = start; size <= hinit->max_size; size++) { //这个test数组中存放每个bucket的当前容量,如果某个bucket[i]的容量test[i]大于了规定的最大容量就意味着需要加大hash桶的个数size了 //利用memzero将每个bucket的当前容量test[i]设置为0 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; //累加要被存放在bucket[key]的内存占用大小 test[key] = (u_short) (test[key] + NGX_HASH_ELT_SIZE(&names[n])); //一旦超过了, 说明size数目的bucket是不够的 if (test[key] > (u_short) bucket_size) { goto next; } } goto found; next: continue; } size = hinit->max_size; ngx_log_error(NGX_LOG_WARN, hinit->pool->log, 0, "could not build optimal %s, you should increase " "either %s_max_size: %i or %s_bucket_size: %i; " "ignoring %s_bucket_size", hinit->name, hinit->name, hinit->max_size, hinit->name, hinit->bucket_size, hinit->name);found://这里表明我们已经成功找到了满足条件的size大小. //依旧是test[i]对应bucket[i], 现在我们要求出所有元素总共需要多少内存. 最后申请出这块内存, 并一一分配给每个bucket //首先, 我们算出每个bucket要存放的内存容量, 记录在test数组中 //此时NULL指针就需要算上了 for (i = 0; i < size; i++) { test[i] = sizeof(void *); } //算出每个bucket要存放的内存容量, 记录在test数组中 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])); } len = 0; //得到所有元素总共需要的内存, 记录在len中 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]; } //如果初始化结构体中的hash表不存在, 那我们需要手动申请一下. if (hinit->hash == NULL) { //值得注意的是, 这里申请的并不是单纯的基本哈希表结构的内存, 而是包含基本哈希表的通配符哈希表. //之所以这样设计, 我认为是为了满足将来可能要init通配符哈希表的需求. 既然ngx_hash_wildcard_t中包含基本哈希表, 且使用起来并没有任何麻烦, //那么这样是不是要显得更好呢? hinit->hash = ngx_pcalloc(hinit->pool, sizeof(ngx_hash_wildcard_t) + size * sizeof(ngx_hash_elt_t *)); if (hinit->hash == NULL) { ngx_free(test); return NGX_ERROR; } //一开始就定义的二级指针bucket, 指向哈希表中 ngx_hash_elt_t * 构成的数组. 即bucket构成的数组 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 *)); if (buckets == NULL) { ngx_free(test); return NGX_ERROR; } } //将所需的所有元素的内存都申请到elts中. elts = ngx_palloc(hinit->pool, len + ngx_cacheline_size); if (elts == NULL) { ngx_free(test); return NGX_ERROR; } elts = ngx_align_ptr(elts, ngx_cacheline_size); //下面就为每个bucket分配内存. 之前已经用test记录了每个bucket应该得到的内存大小. for (i = 0; i < size; i++) { if (test[i] == sizeof(void *)) { continue; } buckets[i] = (ngx_hash_elt_t *) elts; //根据记录好的每个bucket的大小来分配内存 elts += test[i]; } //既然每个bucket拥有了它应该有的内存, 那么现在就将key-value数据搬进去 //现在依旧是test[i]对应bucket[i]. 此时的test数组用于记录当前的某bucket已经有多少内存被初始化了. //如果这个元素已经搬到这个bucket中, 下一个元素首地址就是从当前元素首地址加上test[i]开始. for (i = 0; i < size; i++) { test[i] = 0; } 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])); } //在每个bucket最后加上NULL指针. 处理的时候,把它当成一个ngx_hash_elt_t结构看,在该结构中的第一个元素,正好是一个void指针,我们只处理它,别的都不去碰,所以没有越界的问题。 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; } ngx_free(test); hinit->hash->buckets = buckets; hinit->hash->size = size; return NGX_OK;}对于内存分配, 内存地址对齐, cacheline对齐这些我依然有些迷惑的地方, 所以一些地方没有做解释. 主要还是我基础不够扎实!共勉!
三、基本哈希表的查找.
//由key,name,len信息在hash指向的hash table中查找该key对应的value uvoid *ngx_hash_find(ngx_hash_t *hash, ngx_uint_t key, u_char *name, size_t len){ ngx_uint_t i; ngx_hash_elt_t *elt; elt = hash->buckets[key % hash->size]; if (elt == NULL) { return NULL; } //对该bucket进行搜索. 直到该ngx_hash_elt_t结构中的value为NULL while (elt->value) { if (len != (size_t) elt->len) { //先判断长度 goto next; } for (i = 0; i < len; i++) { if (name[i] != elt->name[i]) { //接着比较name的内容, 可以看到这里的比较很直接 goto next; } } return elt->value; next: //这里的地址偏移到下一个ngx_hash_elt_t结构 elt = (ngx_hash_elt_t *) ngx_align_ptr(&elt->name[0] + elt->len, sizeof(void *)); continue; } return NULL;}这一段没有做过多解释, 理解起来也没有什么难点.
最后, 贴上借鉴的几篇博客:
http://blog.csdn.net/livelylittlefish/article/details/6636229
http://blog.csdn.net/chen19870707/article/details/40794285
http://www.linuxidc.com/Linux/2012-08/67040.htm
http://blog.chinaunix.net/uid-27767798-id-3766755.html
感谢!
0 0
- Nginx基础. Nginx基本哈希表
- Nginx基础. Nginx基本哈希构成
- nginx基本
- nginx基础
- nginx基础
- Nginx基础
- Nginx基础
- nginx 基础
- Nginx基础
- Nginx基础
- Nginx基础
- Nginx基础. Nginx配置解析
- Nginx基础. Nginx模块上下文
- Nginx基础. Nginx通配散列表
- 精通Nginx基础篇之基本操作和信号控制
- Nginx的基本配置
- Nginx Location基本语法
- Nginx Location基本语法
- 数据结构 (一)——宏观导论
- 犀牛——第8章函数 8.1 函数定义
- FrameLayout的使用——android开发之xml布局文件
- 浅谈iOS中MVVM的架构设计与团队协作
- TPC-W安装与配置(威斯康星大学Java版)
- Nginx基础. Nginx基本哈希表
- CCF模拟题部分题目解题思路与AC代码
- Android中如何获取系统应用程序列表与AndroidManifest.xml信息
- 软件开发工具(六)--宏观总结
- httpd反向代理
- java环境变量
- [BZOJ3238][Ahoi2013]差异
- 编写android HAL代码
- UVA216 ——dfs