Chinaunix首页 | 论坛 | 博客
  • 博客访问: 113709
  • 博文数量: 22
  • 博客积分: 596
  • 博客等级: 中士
  • 技术积分: 165
  • 用 户 组: 普通用户
  • 注册时间: 2010-10-17 15:32
文章分类

全部博文(22)

文章存档

2012年(1)

2011年(20)

2010年(1)

分类: LINUX

2011-07-16 14:28:26

在nginx源码中提供了一个比较重要的hash结构,可以为我们带来高效的kv查找。该hash的实现比较简单,但却非常的高效。该hash结构是只读的,在创建之后,以后只能提供查询功能。

该hash结构体,刚开始理解起来比较费劲,而且在使用时也会有不爽的感觉,需要好几个结构体,以及好几个函数配合才能完成初始化及查找。在本文中,对于通配符的使用,我们先不作介绍。

 

我们先看看如何使用吧。

创建一个hash结构体的过程是:

1. 构造一个ngx_hash_key_t为成员的数组,然后用我们需要hash的key、value以及计算出来的hash值来初始化该数组的每一个成员。

2. 构建一个 ngx_hash_init_t结构体的变量, 其中包含了ngx_hash_t 的成员, 为hash的结构体, 还包括一些其他初始设置,如bucket的大小,内存池等。该hash结构体会在ngx_hash_init中创建及初始化。

3. 调用 ngx_hash_init 传入 ngx_hash_init_t 结构, ngx_hash_key_t 的数组,和数组的长度, 进行初始化,这样 ngx_hash_init_t的hash成员就是我们要的hash结构体。

再看看查找:

1. 计算出key的hash值。

2. 使用 ngx_hash_find 进行查找,需要同时传入 hash值和key ,返回的就是value的指针。

 

看起来似乎还比较简单,示例代码来段:

  1. #include   
  2. #include "ngx_config.h"  
  3. #include "ngx_conf_file.h"  
  4. #include "nginx.h"  
  5. #include "ngx_core.h"  
  6. #include "ngx_string.h"  
  7. #include "ngx_palloc.h"  
  8. #include "ngx_array.h"  
  9. #include "ngx_hash.h"   
  10. volatile ngx_cycle_t  *ngx_cycle;  
  11. void ngx_log_error_core(ngx_uint_t level, ngx_log_t *log, ngx_err_t err, const char *fmt, ...) { }  
  12. static ngx_str_t names[] = {ngx_string("rainx"),  
  13.                             ngx_string("xiaozhe"),  
  14.                             ngx_string("zhoujian")};  
  15. static char* descs[] = {"rainx's id is 1","xiaozhe's id is 2","zhoujian's id is 3"};  
  16. // hash table的一些基本操作   
  17. int main()  
  18. {  
  19.     ngx_uint_t          k; //, p, h;   
  20.     ngx_pool_t*         pool;  
  21.     ngx_hash_init_t     hash_init;  
  22.     ngx_hash_t*         hash;  
  23.     ngx_array_t*        elements;  
  24.     ngx_hash_key_t*     arr_node;  
  25.     char*               find;  
  26.     int                 i;  
  27.     ngx_cacheline_size = 32;  
  28.     // hash key cal start   
  29.     ngx_str_t str       = ngx_string("hello, world");  
  30.     k                   = ngx_hash_key_lc( str.data, str.len);  
  31.     pool                = ngx_create_pool(1024*10, NULL);  
  32.     printf("caculated key is %u /n", k);  
  33.     // hask key cal end   
  34.     //   
  35.     hash = (ngx_hash_t*) ngx_pcalloc(pool, sizeof(hash));  
  36.     hash_init.hash      = hash;                      // hash结构   
  37.     hash_init.key       = &ngx_hash_key_lc;          // hash算法函数   
  38.     hash_init.max_size  = 1024*10;                   // max_size   
  39.     hash_init.bucket_size = 64; // ngx_align(64, ngx_cacheline_size);   
  40.     hash_init.name      = "yahoo_guy_hash";          // 在log里会用到   
  41.     hash_init.pool           = pool;                 // 内存池   
  42.     hash_init.temp_pool      = NULL;  
  43.     // 创建数组   
  44.     elements = ngx_array_create(pool, 32, sizeof(ngx_hash_key_t));  
  45.     for(i = 0; i < 3; i++) {  
  46.         arr_node            = (ngx_hash_key_t*) ngx_array_push(elements);  
  47.         arr_node->key       = (names[i]);  
  48.         arr_node->key_hash  = ngx_hash_key_lc(arr_node->key.data, arr_node->key.len);  
  49.         arr_node->value     = (void*) descs[i];  
  50.         //    
  51.         printf("key: %s , key_hash: %u/n", arr_node->key.data, arr_node->key_hash);  
  52.     }  
  53.     if (ngx_hash_init(&hash_init, (ngx_hash_key_t*) elements->elts, elements->nelts)!=NGX_OK){  
  54.         return 1;  
  55.     }  
  56.     // 查找   
  57.     k    = ngx_hash_key_lc(names[0].data, names[0].len);  
  58.     printf("%s key is %d/n", names[0].data, k);  
  59.     find = (char*)  
  60.         ngx_hash_find(hash, k, (u_char*) names[0].data, names[0].len);  
  61.     if (find) {  
  62.         printf("get desc of rainx: %s/n", (char*) find);  
  63.     }  
  64.     ngx_array_destroy(elements);  
  65.     ngx_destroy_pool(pool);  
  66.     return 0;  
  67. }  
#include
#include "ngx_config.h"
#include "ngx_conf_file.h"
#include "nginx.h"
 #include "ngx_core.h"
#include "ngx_string.h"
#include "ngx_palloc.h"
#include "ngx_array.h"
#include "ngx_hash.h"
 
volatile ngx_cycle_t *ngx_cycle;
 
 void ngx_log_error_core(ngx_uint_t level, ngx_log_t *log, ngx_err_t err, const char *fmt, ...) { }
 
static ngx_str_t names[] =
{ngx_string("rainx"), ngx_string("xiaozhe"), ngx_string("zhoujian")};
static char* descs[] = {"rainx's id is 1","xiaozhe's id is 2","zhoujian's id is 3"}; // hash table的一些基本操作
int main()
{
      ngx_uint_t k; //, p, h;
      ngx_pool_t* pool;
      ngx_hash_init_t hash_init;
      ngx_hash_t* hash;
      ngx_array_t* elements;
      ngx_hash_key_t* arr_node;
      char* find;
      int i;
 
      ngx_cacheline_size = 32; // hash key cal start
      ngx_str_t str = ngx_string("hello, world");
      k = ngx_hash_key_lc( str.data, str.len);
      pool = ngx_create_pool(1024*10, NULL);
      printf("caculated key is %u /n", k); // hask key cal end //
     
      hash = (ngx_hash_t*) ngx_pcalloc(pool, sizeof(hash));
      hash_init.hash = hash; // hash结构
      hash_init.key = &ngx_hash_key_lc; // hash算法函数
      hash_init.max_size = 1024*10; //
    
      max_size hash_init.bucket_size = 64; //
      ngx_align(64, ngx_cacheline_size);
 
      hash_init.name = "yahoo_guy_hash"; // 在log里会用到
      hash_init.pool = pool; // 内存池
      hash_init.temp_pool = NULL;
 
// 创建数组 elements = ngx_array_create(pool, 32, sizeof(ngx_hash_key_t));
 for(i = 0; i < 3; i++)
 {
     arr_node = (ngx_hash_key_t*) ngx_array_push(elements);
     arr_node->key = (names[i]);
     arr_node->key_hash = ngx_hash_key_lc(arr_node->key.data, arr_node->key.len);
     arr_node->value = (void*) descs[i]; //
     printf("key: %s , key_hash: %u/n", arr_node->key.data, arr_node->key_hash);
 }
 
 if (ngx_hash_init(&hash_init, (ngx_hash_key_t*) elements->elts, elements->nelts)!=NGX_OK) 
  {
       return 1;
  }
 
// 查找
     k = ngx_hash_key_lc(names[0].data, names[0].len);
     printf("%s key is %d/n", names[0].data, k);
   
     find = (char*) ngx_hash_find(hash, k, (u_char*) names[0].data, names[0].len);
 
     if (find)
     {
         printf("get desc of rainx: %s/n", (char*) find); }
         ngx_array_destroy(elements);
         ngx_destroy_pool(pool);
         return 0;
     } 

接下来我们来分析下源码,先介绍几个结构体:

// 存放在桶中的每个元素   
  1. typedef struct {  
  2.     void             *value;    // 具体存放的值,对应于value   
  3.     u_char            len;      // name的长度   
  4.     u_char            name[1];  // 对应于小写的key   
  5. } ngx_hash_elt_t;                               
  6.                                                 
  7. // hash结构体                                    
  8. typedef struct {                                
  9.     ngx_hash_elt_t  **buckets;  // 指向桶的实际空间   
  10.     ngx_uint_t        size;                            
  11. } ngx_hash_t;                                          
  12.                                                        
  13. /* 通配符 */                                           
  14. typedef struct {                                       
  15.     ngx_hash_t        hash;     // 这里会包含hash,所以在分配空间是不需特别分配给他   
  16.     void             *value;                           
  17. } ngx_hash_wildcard_t;                                 
  18.                                                        
  19.                                                        
  20. /* kv对,包含 hash值 */                                
  21. typedef struct {                                       
  22.     ngx_str_t         key;                             
  23.     ngx_uint_t        key_hash;                        
  24.     void             *value;                           
  25. } ngx_hash_key_t;                                      
  26.                                                        
  27.                                                        
  28. /* hash 函数指针 */  
  29. typedef ngx_uint_t (*ngx_hash_key_pt) (u_char *data, size_t len);  
 
// 包含hash的初始化信息                                 
  1. typedef struct {                                       
  2.     ngx_hash_t       *hash; // 指向我们实际的hash结构体   
  3.     ngx_hash_key_pt   key;  // hash 函数    
  4.     ngx_uint_t        max_size; // 最大元素个数   
  5.     ngx_uint_t        bucket_size;// 桶的大小   
  6.     char             *name; // 在log中会用到   
  7.     ngx_pool_t       *pool; // 内存池   
  8.     ngx_pool_t       *temp_pool;  
  9. } ngx_hash_init_t;  

在熟悉了结构体之后,我先给大家看看hash在内存中的存放布局,看图:

 

图:nginx hash结构

接下来,我们来看看复杂的ngx_hash_init函数:

  1. // 初始化一个hash结构体,第一个参数是我们hash结构体的一些参数,第二个参数是我们需要hash的kv值数组   
  2. // 第三个是元素个数   
  3. // 对齐之后的元素大小  
  4. #define NGX_HASH_ELT_SIZE(name)                                               /   
  5.     (sizeof(void *) + ngx_align((name)->key.len + 1, sizeof(void *)))  
  6. // 初始化一个hash   
  7. ngx_int_t  
  8. ngx_hash_init(ngx_hash_init_t *hinit, ngx_hash_key_t *names, ngx_uint_t nelts)  
  9. {  
  10.     u_char          *elts;  
  11.     size_t           len;  
  12.     u_short         *test;  
  13.     ngx_uint_t       i, n, key, size, start, bucket_size;  
  14.     ngx_hash_elt_t  *elt, **buckets;  
  15.     for (n = 0; n < nelts; n++) {  
  16.         // key不能大于255   
  17.         if (names[n].key.len >= 255) {  
  18.             ngx_log_error(NGX_LOG_EMERG, hinit->pool->log, 0,  
  19.                           "the /"%V/" value to hash is to long: %uz bytes, "  
  20.                           "the maximum length can be 255 bytes only",  
  21.                           &names[n].key, names[n].key.len);  
  22.             return NGX_ERROR;  
  23.         }  
  24.         // 判断每个元素所占的空间是否都小于桶的大小   
  25.         if (hinit->bucket_size < NGX_HASH_ELT_SIZE(&names[n]) + sizeof(void *))  
  26.         {  
  27.             ngx_log_error(NGX_LOG_EMERG, hinit->pool->log, 0,  
  28.                           "could not build the %s, you should "  
  29.                           "increase %s_bucket_size: %i",  
  30.                           hinit->name, hinit->name, hinit->bucket_size);  
  31.             return NGX_ERROR;  
  32.         }  
  33.     }  
  34.     // 用于记录每个桶的临时大小   
  35.     test = ngx_alloc(hinit->max_size * sizeof(u_short), hinit->pool->log);  
  36.     if (test == NULL) {  
  37.         return NGX_ERROR;  
  38.     }  
  39.     // 得到每个桶去掉指针之后的实际大小    
  40.     // 为什么会多一个指针大小呢?这里主要还是为了后面将每个元素对齐到指针   
  41.     bucket_size = hinit->bucket_size - sizeof(void *);  
  42.     // 这里我还没有弄懂呢!!高人指教一下    
  43.     start = nelts / (bucket_size / (2 * sizeof(void *)));  
  44.     start = start ? start : 1;  
  45.     if (hinit->max_size > 10000 && hinit->max_size / nelts < 100) {  
  46.         start = hinit->max_size - 1000;  
  47.     }  
  48.  // 以下为计算实际用桶数   
  49.     for (size = start; size < hinit->max_size; size++) {  
  50.         ngx_memzero(test, size * sizeof(u_short));  
  51.         // 处理每一个key   
  52.         for (n = 0; n < nelts; n++) {  
  53.             // key为空   
  54.             if (names[n].key.data == NULL) {  
  55.                 continue;  
  56.             }  
  57.             // 得到该key应该存放的桶   
  58.             key = names[n].key_hash % size;  
  59.             // 增加该桶的test的大小    
  60.             test[key] = (u_short) (test[key] + NGX_HASH_ELT_SIZE(&names[n]));  
  61. #if 0   
  62.             ngx_log_error(NGX_LOG_ALERT, hinit->pool->log, 0,  
  63.                           "%ui: %ui %ui /"%V/"",  
  64.                           size, key, test[key], &names[n].key);  
  65. #endif   
  66.             // 如果当前桶存放的元素过多,则表明应该放到下一个桶中去   
  67.             // 跳出后,继续执行循环,并将实际用桶个数增加   
  68.             if (test[key] > (u_short) bucket_size) {  
  69.                 goto next;  
  70.             }  
  71.         }  
  72.         // 当前桶数可以满足要求   
  73.         goto found;  
  74.         // 应该增加实际用桶的个数   
  75.     next:  
  76.         continue;  
  77.     }  
  78.     // 否是,最大桶数也不能满足实际用桶数的需求,出错   
  79.     ngx_log_error(NGX_LOG_EMERG, hinit->pool->log, 0,  
  80.                   "could not build the %s, you should increase "  
  81.                   "either %s_max_size: %i or %s_bucket_size: %i",  
  82.                   hinit->name, hinit->name, hinit->max_size,  
  83.                   hinit->name, hinit->bucket_size);  
  84.     ngx_free(test);  
  85.     return NGX_ERROR;  
  86. found:  
  87.     //    
  88.     for (i = 0; i < size; i++) {  
  89.         test[i] = sizeof(void *);  
  90.     }  
  91.     // 得到每个桶的实际使用大小   
  92.     for (n = 0; n < nelts; n++) {  
  93.         if (names[n].key.data == NULL) {  
  94.             continue;  
  95.         }  
  96.         key = names[n].key_hash % size;  
  97.         test[key] = (u_short) (test[key] + NGX_HASH_ELT_SIZE(&names[n]));  
  98.     }  
  99.     len = 0;  
  100.     // 向每个桶的实际大小对应到cacheline上,并得到所有桶的总大小   
  101.     for (i = 0; i < size; i++) {  
  102.         if (test[i] == sizeof(void *)) {  
  103.             continue;  
  104.         }  
  105.         test[i] = (u_short) (ngx_align(test[i], ngx_cacheline_size));  
  106.         len += test[i];  
  107.     }  
  108.     if (hinit->hash == NULL) {  
  109.         // 这里似乎看起来很奇怪,既然是hash,为什么分配空间的大小又跟hash结构体一点关联都没有呢   
  110.         // 这里很有意思,因为ngx_hash_wildchard_t包含hash这个结构体,所以就一起分配了   
  111.         // 并且把每个桶的指针也分配在一起了,这种思考跟以前学的面向对象思想很不一样,但这样会很高效   
  112.         hinit->hash = ngx_pcalloc(hinit->pool, sizeof(ngx_hash_wildcard_t)  
  113.                                              + size * sizeof(ngx_hash_elt_t *));  
  114.         if (hinit->hash == NULL) {  
  115.             ngx_free(test);  
  116.             return NGX_ERROR;  
  117.         }  
  118.         buckets = (ngx_hash_elt_t **)  
  119.                       ((u_char *) hinit->hash + sizeof(ngx_hash_wildcard_t));  
  120.     } else {  
  121.         buckets = ngx_pcalloc(hinit->pool, size * sizeof(ngx_hash_elt_t *));  
  122.         if (buckets == NULL) {  
  123.             ngx_free(test);  
  124.             return NGX_ERROR;  
  125.         }  
  126.     }  
  127.    // 内存对齐到cache行   
  128.     elts = ngx_palloc(hinit->pool, len + ngx_cacheline_size);  
  129.     if (elts == NULL) {  
  130.         ngx_free(test);  
  131.         return NGX_ERROR;  
  132.     }  
  133.     elts = ngx_align_ptr(elts, ngx_cacheline_size);  
  134.     for (i = 0; i < size; i++) {  
  135.         if (test[i] == sizeof(void *)) {  
  136.             continue;  
  137.         }  
  138.         // 指向每个桶的空间   
  139.         buckets[i] = (ngx_hash_elt_t *) elts;  
  140.         elts += test[i];  
  141.     }  
  142.     // 清空重新计算   
  143.     for (i = 0; i < size; i++) {  
  144.         test[i] = 0;  
  145.     }  
  146.     // 向每个kv对应到相应的桶中相应的位置上去   
  147.     for (n = 0; n < nelts; n++) {  
  148.         if (names[n].key.data == NULL) {  
  149.             continue;  
  150.         }  
  151.         // 得到当前桶,及当前桶应该存放的内存地址   
  152.         key = names[n].key_hash % size;  
  153.         elt = (ngx_hash_elt_t *) ((u_char *) buckets[key] + test[key]);  
  154.         // 设置当前元素的值与大小   
  155.         elt->value = names[n].value;  
  156.         elt->len = (u_char) names[n].key.len;  
  157.         // 得到key的小写,并保存到桶中去   
  158.         ngx_strlow(elt->name, names[n].key.data, names[n].key.len);  
  159.         test[key] = (u_short) (test[key] + NGX_HASH_ELT_SIZE(&names[n]));  
  160.     }  
  161.     // 设置每个桶的结束元素为NULL   
  162.     for (i = 0; i < size; i++) {  
  163.         if (buckets[i] == NULL) {  
  164.             continue;  
  165.         }  
  166.         elt = (ngx_hash_elt_t *) ((u_char *) buckets[i] + test[i]);  
  167.         elt->value = NULL;  
  168.     }  
  169.     ngx_free(test);  
  170.     hinit->hash->buckets = buckets;  
  171.     hinit->hash->size = size;  
  172.     return NGX_OK;  
  173. }  

ngx_hash_find提供hash查找:

  1. // 查找,第一个参数是我们的hash结构体,第二个参数是我们根据hash函数生成的hash值,   
  2. // 第三个参数是我们要查找的key,第四个参数是key的长度   
  3. void *  
  4. ngx_hash_find(ngx_hash_t *hash, ngx_uint_t key, u_char *name, size_t len)  
  5. {     
  6.     ngx_uint_t       i;  
  7.     ngx_hash_elt_t  *elt;  
  8. #if 0   
  9.     ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0, "hf:/"%*s/"", len, name);  
  10. #endif   
  11.       
  12.     // 得到该元素可能存在的桶   
  13.     elt = hash->buckets[key % hash->size];  
  14.     // 如查没找到,则返回空   
  15.     if (elt == NULL) {  
  16.         return NULL;  
  17.     }  
  18.     // 遍历该桶的每个元素   
  19.     while (elt->value) {  
  20.         // 如果长度不对,则查找下一个   
  21.         if (len != (size_t) elt->len) {  
  22.             goto next;  
  23.         }  
  24.         // 然后比较key   
  25.         for (i = 0; i < len; i++) {  
  26.             if (name[i] != elt->name[i]) {  
  27.                 goto next;  
  28.             }  
  29.         }  
  30.         return elt->value;  
  31.     next:  
  32.         elt = (ngx_hash_elt_t *) ngx_align_ptr(&elt->name[0] + elt->len,  
  33.                                                sizeof(void *));  
  34.         continue;  
  35.     }  
  36.     return NULL;  
  37. }     

最后感谢所提供的帮助及图文!

阅读(4716) | 评论(0) | 转发(0) |
0

上一篇:epoll使用例子

下一篇:nginx中日志分析

给主人留下些什么吧!~~