目录

一、数据结构定义

1. ngx_hash_elt_t hash表的元素结构

2. ngx_hash_t hash表结构

3. ngx_hash_init_t hash表初始化结构

二、数据结构图

三、具体函数实现

1. 查找一个元素 ngx_hash_find

2. 创建一个hash表 ngx_hash_init

Nginx的hash表结构和我们之前阅读memcached的时候看到的会有很大的差别。笔者在阅读Nginx的hash模块的时候,阅读了好几天,比较不容易理解,但是Nginx的hash模块包含了对内存利用最大化、CPU利用最大化的很多设计细节,非常值得推荐和学习。

Nginx的hash表结构主要几个特点:

静态只读。当初始化生成hash表结构后,是不能动态修改这个hash表结构的内容。
将内存利用最大化。Nginx的hash表,将内存利用率发挥到了极致,并且很多设计上面都是可以供我们学习和参考的。
查询速度快。Nginx的hash表做了内存对齐等优化。
主要解析配置数据。
一、数据结构定义
1. ngx_hash_elt_t hash表的元素结构

/*** 存储hash的元素*/
typedef struct {void             *value;    /* 指向value的指针 */u_short           len;      /* key的长度 */u_char            name[1];  /* 指向key的第一个地址,key长度为变长(设计上的亮点)*/
} ngx_hash_elt_t;

2. ngx_hash_t hash表结构

/*** Hash的桶*/
typedef struct {ngx_hash_elt_t  **buckets;  /* hash表的桶指针地址值 */ngx_uint_t        size;   /* hash表的桶的个数*/
} ngx_hash_t;

3. ngx_hash_init_t hash表初始化结构

/*** hash表主体结构*/
typedef struct {ngx_hash_t       *hash; /* 指向hash数组结构 */ngx_hash_key_pt   key;  /* 计算key散列的方法 */ngx_uint_t        max_size;     /* 最大多少个 */ngx_uint_t        bucket_size;   /* 桶的存储空间大小 */char             *name; /* hash表名称 */ngx_pool_t       *pool; /* 内存池 */ngx_pool_t       *temp_pool; /* 临时内存池*/
} ngx_hash_init_t;

二、数据结构图

说明:

Nginx的hash表主要存放在ngx_hash_t数据结构上。ngx_hash_t主要存放桶的指针值和桶的个数。
Nginx的hash表中桶的个数会在初始化的时候进行“探测”,会探测出合适的痛的个数。
Nginx的hash表在初始化的时候就决定了hash表的桶的个数以及元素个数和大小,所以所有元素都会被分配到一个大的连续的内存块上。
每个bucket的长度会根据元素个数的实际长度决定,并且每个bucket之间通过NULL指针进行分割。
每个桶都保存了桶的第一个元素ngx_hash_elt_t的指针值。
NULL指针会在查找元素的时候用到,具体看下面的源码阅读。
ngx_hash_elt_t存储每个元素的数据结构,并且key的长度是非定长的。
三、具体函数实现
1. 查找一个元素 ngx_hash_find

/*** 从hash表中读取一个元素*/
void *
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;#if 0ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0, "hf:\"%*s\"", len, name);
#endif/* 获取对应的桶 */elt = hash->buckets[key % hash->size];if (elt == NULL) {return NULL;}/* 在桶的链表上,查找具体的值;elt元素最后一个elt->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]) {goto next;}}return elt->value;next:/* 因为在内存池上申请内存,并且是自己处理整块内存,为了CPU读取速度更快,进行了内存对齐 */elt = (ngx_hash_elt_t *) ngx_align_ptr(&elt->name[0] + elt->len,sizeof(void *));continue;}return NULL;
}

2. 创建一个hash表 ngx_hash_init


获取元素大小的宏定义。

/*** 获取元素的大小* 元素大小主要是ngx_hash_elt_t结构,包括:* 1. name的长度  (name)->key.len* 2. len的长度   其中的"+2"是要加上该结构中len字段(u_short类型)的大小* 3. value指针的长度    "sizeof(void *)"相当于 value的长度*/
#define NGX_HASH_ELT_SIZE(name)                                               \(sizeof(void *) + ngx_align((name)->key.len + 2, sizeof(void *)))

ngx_hash_init初始化一个hash表

Nginx的hash表是只读的,所以在初始化的时候就会生成固定的hash表。
初始化过程中,先会根据实际key的大小来进行“探测”,得出一个合适的桶的个数。
然后根据元素的大小,来确定每个桶具体的大小,并且分配完整的元素大内存块。
然后将元素切割成ngx_hash_elt_t的结构,装入每一个bucket桶上。
每个bucket的结尾都会有一个NULL空指针作为标识符号,该标识符号会强制换成ngx_hash_elt_t结构,并且设置value=NULL,在查询的时候用于判断桶的结尾部分。

/*** 初始化一个hash表*/
ngx_int_t 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;/*** 先检查每个元素是否会超过bucket_size的限制* 如果超过限制,则说明需要重新处理* hash表的每一个bucket桶中的元素elt都是被分配到一块完整的内存块上的,* 每个bucket的内存块结尾会有一个void *的空指针作为表示符号用于分隔bucket*/for (n = 0; n < nelts; 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是用来做探测用的,探测的目标是在当前bucket的数量下,冲突发生的是否频繁。* 过于频繁则需要调整桶的个数。* 检查是否频繁的标准是:判断元素总长度和bucket桶的容量bucket_size做比较*/test = ngx_alloc(hinit->max_size * sizeof(u_short), hinit->pool->log);if (test == NULL) {return NGX_ERROR;}/*** 每个桶的元素实际所能容纳的空间大小* 需要减去尾部的NULL指针结尾符号*/bucket_size = hinit->bucket_size - sizeof(void *);/*** 通过一定的小算法,计算得到从哪个桶开始test(探测)*/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;}/*** 这边就是真正的探测逻辑* 探测会遍历所有的元素,并且计算落到同一个bucket上元素长度的总和和bucket_size比较* 如果超过了bucket_size,则说明需要调整* 最终会探测出比较合适的桶的个数 :size*/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 0ngx_log_error(NGX_LOG_ALERT, hinit->pool->log, 0,"%ui: %ui %ui \"%V\"",size, key, test[key], &names[n].key);
#endif/* 比较bucket_size和落到该bucket上的元素长度总和*/if (test[key] > (u_short) bucket_size) {goto next;}}goto found;next:continue;}ngx_log_error(NGX_LOG_EMERG, hinit->pool->log, 0,"could not build the %s, you should increase ""either %s_max_size: %i or %s_bucket_size: %i", hinit->name,hinit->name, hinit->max_size, hinit->name, hinit->bucket_size);ngx_free(test);return NGX_ERROR;/*** 探测成功,则size为bucket桶的个数*/found:/*** 为了确定bucket的实际长度,初始化每个桶的长度计数器,初始值为一个NULL空指针长度* 前面说过,每个bucket的内存块之间,使用一个NULL空指针进行分割,所以长度需要加上去*/for (i = 0; i < size; i++) {test[i] = sizeof(void *);}/*** 通过遍历,计算每个桶的大小。并且将每个桶的大小存储在test[n]数组上*/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];}/*** 分配一块内存空间,存储:ngx_hash_t *hash和ngx_hash_elt_t ** ngx_hash_elt_t用于存储桶。指针指向元素地址*/if (hinit->hash == NULL) {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;}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 = 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); //内存对齐/*** 将elts元素的内存,分割到buckets桶上*/for (i = 0; i < size; i++) {if (test[i] == sizeof(void *)) {continue;}buckets[i] = (ngx_hash_elt_t *) elts;elts += test[i];}/*** 将test清空,利用test于元素填充计数器*/for (i = 0; i < size; i++) {test[i] = 0;}/*** 往bucket的元素位上填充数据*/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;/* 拷贝key数据,并且小写 */ngx_strlow(elt->name, names[n].key.data, names[n].key.len);/* test计数器计算新元素需要存放的位置 */test[key] = (u_short)(test[key] + NGX_HASH_ELT_SIZE(&names[n]));}/***  设置bucket桶上最后一个元素设置为value为NULL*/for (i = 0; i < size; i++) {if (buckets[i] == NULL) {continue;}/*** 这边的设计 Nice!!!* test[i] 其实是bucket的元素块的结束位置* 由于前面bucket的处理中多留出了一个指针的空间,而此时的test[i]是bucket中实际数据的共长度,* 所以bucket[i] + test[i]正好指向了末尾null指针所在的位置。处理的时候,把它当成一个ngx_hash_elt_t结构看,* 在该结构中的第一个元素,正好是一个void指针,我们只处理它,别的都不去碰,所以没有越界的问题。*/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;
}

转载地址:

1.https://initphp.blog.csdn.net/article/details/50675500

Nginx源码分析 - 基础数据结构篇 - hash表结构 ngx_hash.c(07)相关推荐

  1. Django源码分析9:model.py表结构的初始化概述

    django源码分析 本文环境python3.5.2,django1.10.x系列 django源码分析-model概述 Django项目中提供了内置的orm框架,只需要在models.py文件中添加 ...

  2. Nginx 源码分析

    1.工程 ngx_conf_file.c ngx_connection.c ngx_cycle.c ngx_file.h ngx_module.c ngx_open_file_cache.h ngx_ ...

  3. Nginx源码分析:核心数据结构ngx_cycle_t与内存池概述

    nginx源码分析 nginx-1.11.1 参考书籍<深入理解nginx模块开发与架构解析> 核心数据结构与内存池概述 在Nginx中的核心数据结构就是ngx_cycle_t结构,在初始 ...

  4. Nginx源码分析:master/worker工作流程概述

    nginx源码分析 nginx-1.11.1 参考书籍<深入理解nginx模块开发与架构解析> Nginx的master与worker工作模式 在生成环境中的Nginx启动模式基本都是以m ...

  5. Nginx源码分析:启动流程

    nginx源码分析 nginx-1.11.1 参考书籍<深入理解nginx模块开发与架构解析> nginx简介 Nginx的作为服务端软件,表现的主要特点是更快.高扩展.高可靠性.低内存消 ...

  6. nginx源码分析—内存池结构ngx_pool_t及内存管理

    本博客( http://blog.csdn.net/livelylittlefish)贴出作者(阿波)相关研究.学习内容所做的笔记,欢迎广大朋友指正! Content 0.序 1.内存池结构 1.1 ...

  7. Nginx源码分析-内存池

    本文转自淘宝平台http://www.tbdata.org/archives/1390,不是为了夺他人之美,只是觉得写得很好,怕淘宝万一删掉就找不到了,放在这里保存一下.大家可以直接链接过去,他们那个 ...

  8. nginx源码分析(5)——监听socket初始化

    在nginx源码分析(4)中,看到了nginx的事件模型,但其中没有介绍监听socket的初始化.而对于web server来说,需要通过监听socket来监听客户端的连接等.本篇将会具体介绍这方面的 ...

  9. nginx源码分析之网络初始化

    nginx作为一个高性能的HTTP服务器,网络的处理是其核心,了解网络的初始化有助于加深对nginx网络处理的了解,本文主要通过nginx的源代码来分析其网络初始化. 从配置文件中读取初始化信息 与网 ...

  10. Nginx源码分析链接

    nginx-0.8.38源码探秘:http://blog.csdn.net/ccdd14/article/details/5872312 nginx源码分析: http://blog.sina.com ...

最新文章

  1. html显示本地磁盘 图片,手把手教你为本地磁盘增添背景图片(图解)
  2. ajax联系人数,setInterval定时调用ajax实现在线人数统计
  3. (8)hibernate四种继承映射
  4. Oracle 12c 安装 Linuxx86_64
  5. 前端学习(2335):angular之内置结构指令ngif
  6. jumpserver 使用教程_Jumpserver之快速入门
  7. Apache HttpComponents在App里访问HTTP服务
  8. macbook pro开机键盘键盘和触摸板没反应问题
  9. 洛谷P1878 舞蹈课 贪心 堆
  10. python批量查询(excel)数据
  11. matlab编程中abs是什么意思,在程序设计中,abs是什么函数?,程序中ABS代表什么意思?...
  12. 法律对合伙企业债务承担方式的规定
  13. 生产者消费者的几种写法
  14. 使用scrapy爬取拉钩网招聘信息
  15. 二维码推广方法20种
  16. 笑对人生,坐看云起云落
  17. STM32学习笔记 | 引起电源和系统异常复位的原因
  18. 阿里百川淘宝联盟私域会员对接
  19. 操作系统——时间片轮转调度算法(RR)
  20. 大数据征信的发展背景及与传统征信的比较

热门文章

  1. L1-025 正整数A+B (15 分)—团体程序设计天梯赛
  2. Android 长按Button出现一个菜单
  3. Android电池管理系统系统分析
  4. HDU - 1520 Anniversary party (有向入门树形DP)
  5. (转)栈与堆栈的区别
  6. 关于spring集成junit4测试步骤
  7. swift 闭包简写实际参数名$0、$1等理解
  8. WPF Grid布局
  9. maven的一些依赖
  10. 程序员编程艺术第二十六章:基于给定的文档生成倒排索引(含源码下载)