Chinaunix首页 | 论坛 | 博客
  • 博客访问: 65903
  • 博文数量: 15
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 160
  • 用 户 组: 普通用户
  • 注册时间: 2014-11-04 17:12
个人简介

It

文章分类

全部博文(15)

文章存档

2015年(13)

2014年(2)

我的朋友

分类: LINUX

2015-06-03 13:05:36

1. 概述

nginx 中的hash表主要是用处理静态key/value的查询,也就是根据静态数据初始化hash表后,不再增加删除数据到hash表中,用于根据key获得value的查询。这与redis等NoSQL数据库中动态hash表不同,nginx初始化静hash表一次,就把所有的静态数据初始化到hash表中,供后面查询使用,所以初始化的速度影响不大,只要保证后面查询效率高即可。

另外,hash表还有一个碰撞问题。解决碰撞问题,主要有三种:

  1. 线性探测;
  2. 二次哈希;
  3. 开放链表。

nginx的hash表解决碰撞问题使用的第三种方式,但不是链表,而是一个数组。

2. 数据结构


点击(此处)折叠或打开

  1. typedef struct {
  2.     // 指向用户数据,必须保证value为该结构的第一个成员,后面的初始化及查找需要value作为第一个结构成员
  3.     void *value;

  4.     // 与name的长度
  5.     u_short len;
  6.     // 用户数据的key保存在name中,不定长
  7.     u_char name[1];
  8. } ngx_hash_elt_t;


  9. typedef struct {
  10.     // ngx_hash_elt_t 数组的首址
  11.     ngx_hash_elt_t **buckets;
  12.     // 数组的大小
  13.     ngx_uint_t size;
  14. } ngx_hash_t;

  15. // hash表初始化时的辅助数据结构
  16. typedef struct {
  17.     // key->data 所指向的数据保存到 ngx_hash_elt_t 的 name 中;
  18.     // key->len 保存到 ngx_hash_elt_t 的 len
  19.     ngx_str_t key;
  20.     // 根据 ngx_hash_key_pt 哈希函数计算出的值
  21.     ngx_uint_t key_hash;

  22.     // 指向 key对应的数据
  23.     void *value;
  24. } ngx_hash_key_t;

  25. // 哈希函数
  26. typedef ngx_uint_t (*ngx_hash_key_pt) (u_char *data, size_t len);
  27. // 初始化一个 hash 表的相关参数的数据结构
  28. typedef struct {
  29.     // 需要初始化的hash表,调用 ngx_hash_init 后,即可以完成对hash表的初始化
  30.     ngx_hash_t *hash;
  31.     // 计算 key 的 key_hash 的函数指针,不是很准确,应该 key_hash % hash->size
  32.     // 得到真正的hash值
  33.     ngx_hash_key_pt key;

  34.     // 初始化限制因子,即 hash->size 应 <= max_size;
  35.     ngx_uint_t max_size;
  36.     // 初始化限制因子, 即 hash 表中每个桶的大小不超过bucket_size,
  37.     // 即hash表的每个元素不能大于bucket_size - sizeof(void *)
  38.     // ( 桶的结束符还占用 sizeof(void *) 大小
  39.     ngx_uint_t bucket_size;

  40.     // 桶的名字,主要用于记录日志,方便阅读
  41.     char *name;
  42.     // 如果 hash 指针为空, 则 hash 表结构从该内存池分配内存;
  43.     // 所有hash 元素的内存从该内存池pool分配内存
  44.     ngx_pool_t *pool;
  45.     // 分配临时内存用的内存池
  46.     ngx_pool_t *temp_pool;
  47. } ngx_hash_init_t;


3. 接口


点击(此处)折叠或打开

  1. // hash表初始化
  2. ngx_int_t ngx_hash_init(ngx_hash_init_t *hinit, ngx_hash_key_t *names,
  3.     ngx_uint_t nelts);
  4. // 根据key从hash表查询value
  5. void *ngx_hash_find(ngx_hash_t *hash, ngx_uint_t key, u_char *name, size_t len);

  6. // 计算 ngx_hash_elt_t 所占用的内存的大小,并要求对齐按照 sizeof(*) 对齐
  7. // 其中 第一个 sizeof(void *) 是 value 指针所占的内存大小
  8. // name->key.len 是 ngx_hash_elt_t的数组name所占用的内存大小,
  9. // 2 表示 ngx_hash_elt_t的len成员占用的字节
  10. // 并按照 sizeof(void *) 对齐
  11. #define NGX_HASH_ELT_SIZE(name) \
  12.     (sizeof(void *) + ngx_align((name)->key.len + 2, sizeof(void *)))



4. 接口解析

4.1 ngx_hash_init hash表初始化解析

点击(此处)折叠或打开

  1. ngx_int_t
  2. ngx_hash_init(ngx_hash_init_t *hinit, ngx_hash_key_t *names, ngx_uint_t nelts)
  3. {
  4.     u_char *elts;
  5.     size_t len;
  6.     u_short *test;
  7.     ngx_uint_t i, n, key, size, start, bucket_size;
  8.     ngx_hash_elt_t *elt, **buckets;

  9.     for (n = 0; n < nelts; n++) {
  10.         if (hinit->bucket_size < NGX_HASH_ELT_SIZE(&names[n]) + sizeof(void *))
  11.         {
  12.             // names[n] 表示的 ngx_hash_elt_t 占用的空间 + sizeof(void *) 大于 bucket_size;
  13.             // 加 sizeof(void *) 是因为每个桶的最后用 一个空指针表示该桶结束
  14.             ngx_log_error(NGX_LOG_EMERG, hinit->pool->log, 0,
  15.                           "could not build the %s, you should "
  16.                           "increase %s_bucket_size: %i",
  17.                           hinit->name, hinit->name, hinit->bucket_size);
  18.             return NGX_ERROR;
  19.         }
  20.     }

  21.     // 用于统计每个桶的大小,感觉此处使用 sizeof(ngx_uint_t) 更合适
  22.     // 猜测:可能作者认为不会有超过u_short范围的需求?
  23.     test = ngx_alloc(hinit->max_size * sizeof(u_short), hinit->pool->log);
  24.     if (test == NULL) {
  25.         return NGX_ERROR;
  26.     }

  27.     // 去掉桶的结尾大小,每个桶用一个NULL指针表示结束
  28.     bucket_size = hinit->bucket_size - sizeof(void *);


  29.     // ngx_hash_elt_t 对齐后算占用的最小空间为 min_memof(ngx_hash_elt_t) = 2 * sizeof(void *)
  30.     // max_bucket_elt_num = bucket_size / min_memof(ngx_hash_elt_t) 为一个桶的最多元素数目
  31.     // min_bucket_num = nelts / max_bucket_elt_num
  32.     start = nelts / (bucket_size / (2 * sizeof(void *)));
  33.     start = start ? start : 1;

  34.     // 该部分不懂。 猜测:
  35.     // max_size 不算小, 且 max_size / nelts < 100 不是非常非常稀疏,
  36.     // 为了效率,直接从 max_size - 1000 开始探测,也就是最多探测1000次
  37.     if (hinit->max_size > 10000 && nelts && hinit->max_size / nelts < 100) {
  38.         start = hinit->max_size - 1000;
  39.     }
  40.     // 开始探测调整size的大小,以找最小的满足要求的size
  41.     for (size = start; size < hinit->max_size; size++) {

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

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

  47.             key = names[n].key_hash % size;
  48.             // 累计计算出每个桶的大小
  49.             test[key] = (u_short) (test[key] + NGX_HASH_ELT_SIZE(&names[n]));

  50. #if 0
  51.             ngx_log_error(NGX_LOG_ALERT, hinit->pool->log, 0,
  52.                           "%ui: %ui %ui \"%V\"",
  53.                           size, key, test[key], &names[n].key);
  54. #endif

  55.             // 如果 已经大于 bucket_size ,从下个size再试探
  56.             // 此处 bucket_size 已经减去 sizeof(void *)
  57.             if (test[key] > (u_short) bucket_size) {
  58.                 goto next;
  59.             }
  60.         }

  61.         goto found;

  62.     next:

  63.         continue;
  64.     }

  65.     ngx_log_error(NGX_LOG_EMERG, hinit->pool->log, 0,
  66.                   "could not build the %s, you should increase "
  67.                   "either %s_max_size: %i or %s_bucket_size: %i",
  68.                   hinit->name, hinit->name, hinit->max_size,
  69.                   hinit->name, hinit->bucket_size);

  70.     ngx_free(test);

  71.     return NGX_ERROR;

  72. found:
  73.     // 已经找到合适的size
  74.     for (i = 0; i < size; i++) {
  75.         test[i] = sizeof(void *); // 每个桶加上结尾NULL指针的大小
  76.     }

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

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

  84.     len = 0;

  85.     for (i = 0; i < size; i++) {
  86.         if (test[i] == sizeof(void *)) {
  87.             // 该桶没有元素
  88.             continue;
  89.         }

  90.         // 为了效率,按照 cacheline 对齐
  91.         test[i] = (u_short) (ngx_align(test[i], ngx_cacheline_size));

  92.         len += test[i];
  93.     }

  94.     if (hinit->hash == NULL) {
  95.         hinit->hash = ngx_pcalloc(hinit->pool, sizeof(ngx_hash_wildcard_t)
  96.                                              + size * sizeof(ngx_hash_elt_t *));
  97.         if (hinit->hash == NULL) {
  98.             ngx_free(test);
  99.             return NGX_ERROR;
  100.         }

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

  103.     } else {
  104.         buckets = ngx_pcalloc(hinit->pool, size * sizeof(ngx_hash_elt_t *));
  105.         if (buckets == NULL) {
  106.             ngx_free(test);
  107.             return NGX_ERROR;
  108.         }
  109.     }

  110.     // 所有元素都分配在一块连续内存内,保持数据局部性,提高性能
  111.     // 加 ngx_cacheline_size 的防止 在 按照 ngx_cacheline_size 调整内存指针 elts 时
  112.     // 防止内存越界
  113.     elts = ngx_palloc(hinit->pool, len + ngx_cacheline_size);
  114.     if (elts == NULL) {
  115.         ngx_free(test);
  116.         return NGX_ERROR;
  117.     }

  118.     // 按照 ngx_cacheline_size 调整内存指针 elts
  119.     // 还是为了提高性能
  120.     elts = ngx_align_ptr(elts, ngx_cacheline_size);

  121.     // 设置每个桶的起始位置
  122.     for (i = 0; i < size; i++) {
  123.         if (test[i] == sizeof(void *)) {
  124.             continue;
  125.         }

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

  128.     }

  129.     // 设置桶的当前可用内存位置
  130.     for (i = 0; i < size; i++) {
  131.         test[i] = 0;
  132.     }

  133.     // 把每个元素放进相应的桶内的相应位置
  134.     for (n = 0; n < nelts; n++) {
  135.         if (names[n].key.data == NULL) {
  136.             continue;
  137.         }

  138.         // 找到对应的桶
  139.         key = names[n].key_hash % size;
  140.         // 找到空位置
  141.         elt = (ngx_hash_elt_t *) ((u_char *) buckets[key] + test[key]);

  142.         // 设置 elt 的值
  143.         elt->value = names[n].value;
  144.         elt->len = (u_short) names[n].key.len;

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

  146.         // 记录下一个空位置的地址
  147.         test[key] = (u_short) (test[key] + NGX_HASH_ELT_SIZE(&names[n]));
  148.     }

  149.     for (i = 0; i < size; i++) {
  150.         if (buckets[i] == NULL) {
  151.             continue;
  152.         }

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

  154.         // 虽然 elt 指向 bucket[i] 的最后4个字节,还是强制转为 (ngx_hash_elt_t *) 类型
  155.         // 而 ngx_hash_elt_t 的成员 void *value 恰好处于首部,故 赋值 value为null没问题;
  156.         // 如 ngx_hash_elt_t 的成员 void *value 不处于首部,则有问题,导致内存越界
  157.         // 查询时就是利用 elt->value == NULL 条件 来结束对该桶的查询
  158.         elt->value = NULL;
  159.     }

  160.     // 释放辅助内存
  161.     ngx_free(test);

  162.     hinit->hash->buckets = buckets;
  163.     hinit->hash->size = size;

  164. #if 0

  165.     for (i = 0; i < size; i++) {
  166.         ngx_str_t val;
  167.         ngx_uint_t key;

  168.         elt = buckets[i];

  169.         if (elt == NULL) {
  170.             ngx_log_error(NGX_LOG_ALERT, hinit->pool->log, 0,
  171.                           "%ui: NULL", i);
  172.             continue;
  173.         }

  174.         while (elt->value) {
  175.             val.len = elt->len;
  176.             val.data = &elt->name[0];

  177.             key = hinit->key(val.data, val.len);

  178.             ngx_log_error(NGX_LOG_ALERT, hinit->pool->log, 0,
  179.                           "%ui: %p \"%V\" %ui", i, elt, &val, key);

  180.             elt = (ngx_hash_elt_t *) ngx_align_ptr(&elt->name[0] + elt->len,
  181.                                                    sizeof(void *));
  182.         }
  183.     }

  184. #endif

  185.     return NGX_OK;
  186. }

初始化完成后,大体的内存布局如下图:ngx_hast_t内存布局示意图

4.2 ngx_hsh_find 查询方法解析

点击(此处)折叠或打开

  1. void *
  2. ngx_hash_find(ngx_hash_t *hash, ngx_uint_t key, u_char *name, size_t len)
  3. {
  4.     ngx_uint_t i;
  5.     ngx_hash_elt_t *elt;

  6. #if 0
  7.     ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0, "hf:\"%*s\"", len, name);
  8. #endif

  9.     elt = hash->buckets[key % hash->size];

  10.     // 该桶为null
  11.     if (elt == NULL) {
  12.         return NULL;
  13.     }

  14.     // 找到桶的第一个元素的地址且不为空
  15.     // elt->value = NULL 为该桶的结束标志,
  16.     // 到达该结束标志未找到,即不再hash表中
  17.     while (elt->value) {
  18.         // 先匹配长度
  19.         if (len != (size_t) elt->len) {
  20.             goto next;
  21.         }

  22.         // 再检查每个字符
  23.         for (i = 0; i < len; i++) {
  24.             if (name[i] != elt->name[i]) {
  25.                 goto next;
  26.             }
  27.         }

  28.         return elt->value;

  29.     next:

  30.         elt = (ngx_hash_elt_t *) ngx_align_ptr(&elt->name[0] + elt->len,
  31.                                                sizeof(void *));
  32.         continue;
  33.     }

  34.     // 未找到
  35.     return NULL;
  36. }

5. 其他

nginx的hash表还用来处理key带通配符的,主要用来location的查找,key的通配符又分前向、后向,即头部带通配符、尾部通配符。基本原理相似,但更复杂一些。


阅读(1601) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~