nginx实现的hash表特点是构建一次, 初始化后无法动态的增删.之后就用于<k,v>查找.
相关接口如下:
/* hash表查询 */ void *ngx_hash_find(ngx_hash_t *hash, ngx_uint_t key, u_char *name, size_t len); void *ngx_hash_find_wc_head(ngx_hash_wildcard_t *hwc, u_char *name, size_t len); void *ngx_hash_find_wc_tail(ngx_hash_wildcard_t *hwc, u_char *name, size_t len); void *ngx_hash_find_combined(ngx_hash_combined_t *hash, ngx_uint_t key, u_char *name, size_t len); /* hash表初始化 */ ngx_int_t ngx_hash_init(ngx_hash_init_t *hinit, ngx_hash_key_t *names,ngx_uint_t nelts); ngx_int_t ngx_hash_wildcard_init(ngx_hash_init_t *hinit, ngx_hash_key_t *names, ngx_uint_t nelts); /* hash函数 */ #define ngx_hash(key, c) ((ngx_uint_t) key * 31 + c) ngx_uint_t ngx_hash_key(u_char *data, size_t len); ngx_uint_t ngx_hash_key_lc(u_char *data, size_t len); ngx_uint_t ngx_hash_strlow(u_char *dst, u_char *src, size_t n); ngx_int_t ngx_hash_keys_array_init(ngx_hash_keys_arrays_t *ha, ngx_uint_t type); ngx_int_t ngx_hash_add_key(ngx_hash_keys_arrays_t *ha, ngx_str_t *key, void *value, ngx_uint_t flags);
1. 数据结构
// hash表(ngx_hash_t)结构中存的实际元素<key,value> typedef struct { void *value; // value的值, 为null代表该元素为当前bucket最尾元素 u_short len; // key的长度 u_char name[1]; // key的值 } ngx_hash_elt_t; // hash表结构, 开链式处理碰撞 typedef struct { ngx_hash_elt_t **buckets; ngx_uint_t size; // hash表长度, buckets个数 } ngx_hash_t;
如图
1. ngx_hash_elt_t为hash表中的元素, 代表一个<K,V>.
2. ngx_hash_t为hash表的结构, 二维指针buckets为一个ngx_hash_elt_t *数组, 分别指向hash表index对应的bucket, bucket里存放了hash值相同的<K,V>.
3. Hash函数对Key进行运算, 得到<K,V>在hash表中的index.
// 构建hash表时用到的结构体 typedef struct { ngx_hash_t *hash; // 指向待生成的hash表ngx_hash_t ngx_hash_key_pt key; // hash函数指针 ngx_uint_t max_size; // 用户配置, 最大可能生成的buckets数目 ngx_uint_t bucket_size; // 每个bucket所占的最大空间 char *name; // 该hash表的名字 ngx_pool_t *pool; ngx_pool_t *temp_pool; } ngx_hash_init_t; // <key,value>以及对应的hash值, 构建hash表时使用 typedef struct { ngx_str_t key; // key ngx_uint_t key_hash; // hash值, 由hash函数(如ngx_hash_key_lc())根据key计算得到 void *value; // value值 } ngx_hash_key_t;
如图
1. ngx_hash_key_t为生成Hash表元素的初始结构, 存放<k,v>以及key的hash值
2. ngx_hash_init_t存放初始化hash表时的相关信息,
2. 初始化
/* 根据传入的name ( ngx_hash_key_t * ) 计算对应的ngx_hash_elt_t大小, */ #define NGX_HASH_ELT_SIZE(name) \ (sizeof(void *) + ngx_align((name)->key.len + 2, sizeof(void *))) // name->key.len 等于 ngx_hash_elt_t->name数组长度 // 2代表ngx_hash_elt_t-->len ( short )长度
初始化hash表时, 用来根据ngx_hash_key_t计算实际的ngx_hash_elt_t占用空间.
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; // 遍历所有的待hash的key for (n = 0; n < nelts; n++) { if (hinit->bucket_size < NGX_HASH_ELT_SIZE(&names[n]) + sizeof(void *)) { // 该name对应的ngx_hash_elt_t对象占用空间超过bucket_size 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; } } // 分配max_size个u_short元素, 保存当前bucket已经使用的空间 test = ngx_alloc(hinit->max_size * sizeof(u_short), hinit->pool->log); if (test == NULL) { return NGX_ERROR; } // 实际可用空间为定义的bucket_size减去末尾的void *(结尾标识) bucket_size = hinit->bucket_size - sizeof(void *); 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; } for (size = start; size < hinit->max_size; size++) { // start为预估的hash表长度, 如果不满足要求,则增加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])); // 累积计算hash到这个index上的元素大小和 #if 0 ngx_log_error(NGX_LOG_ALERT, hinit->pool->log, 0, "%ui: %ui %ui \"%V\"", size, key, test[key], &names[n].key); #endif if (test[key] > (u_short) bucket_size) { // 当前元素总和超过邋bucket_size goto next; } } goto found; next: continue; } // 调整允许的hash表最大长度max_size和*_bucket_size,避免hash表初始化失败 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; found: // 创建长度为size的hash表, 可以将所有元素装进去 for (i = 0; i < size; i++) { test[i] = sizeof(void *); // 尾节点的void *标识占用的空间 } for (n = 0; n < nelts; n++) { if (names[n].key.data == NULL) { continue; } key = names[n].key_hash % size; // 找到元素对应index test[key] = (u_short) (test[key] + NGX_HASH_ELT_SIZE(&names[n])); } // 完成后, test数组记录了hash表每个bucket占用的实际大小 len = 0; for (i = 0; i < size; i++) { if (test[i] == sizeof(void *)) { continue; // 这个bucket为空 } // 以cpu cache line 长度内存对齐 test[i] = (u_short) (ngx_align(test[i], ngx_cacheline_size)); // len为hash表所有bucket所占空间总和 len += test[i]; } 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)); // 不分配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); // 为每个有元素的bucket划分实际的空间 for (i = 0; i < size; i++) { if (test[i] == sizeof(void *)) { // 该bucket中无元素, buckets[i]为null continue; } buckets[i] = (ngx_hash_elt_t *) elts; elts += test[i]; } for (i = 0; i < size; i++) { test[i] = 0; } // 每个ngx_hash_key_t转为ngx_hash_elt_t装入hash表 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); // 当前bucket的偏移 test[key] = (u_short) (test[key] + NGX_HASH_ELT_SIZE(&names[n])); } for (i = 0; i < size; i++) { if (buckets[i] == NULL) { continue; } elt = (ngx_hash_elt_t *) ((u_char *) buckets[i] + test[i]); // bucket中最后的节点,置为null, 标识该bucket结束 elt->value = NULL; } ngx_free(test); hinit->hash->buckets = buckets; hinit->hash->size = size; return NGX_OK; }
传入参数ngx_hash_init_t *hinit为待初始化hash表相关数据, ngx_hash_key_t *names为待放入元素, ngx_uint_t nelts为元素个数.
初始化步骤
1. 遍历待初始化的ngx_hash_key_t数组, 保证占用空间最大的ngx_hash_elt_t元素可以装进bucket_size大小空间
2. 预估一个可以装入所有元素的hash表长度start, 判断是否可以将所有元素装进这个size大小的hash表
3. 装不下, 增加size, 如果size达到max_size仍然不能创建这个hash表, 则失败. 否则确定了要构建的hash表长度(buckets个数)
4. found:处开始,, 计算所有元素占用总空间, 为hash表的各个bucket分配各自的空间
5. 将ngx_hash_key_t数组元素分别放入对应的bucket中
其中第2步中怎么计算初始的可能hash表的大小start?
start = nelts / (bucket_size / (2 * sizeof(void *)));
也即认为一个bucket最多放入的元素个数为bucket_size / (2 * sizeof(void *));
64位机器上, sizeof(void *) 为8 Bytes, sizeof(unsigned char)为2Bytes, sizeof(name)为1 Byte, sizeof(ngx_hash_elt_t)为16Bytes, 正好与2 * sizeof(void *)相等.
// 对于配置项: // server_names_hash_max_size 20000; // server_names_hash_bucket_size 128; // 假设配置文件中某一固定的ip:port有5000个server_names需要初始化hash表, 则start=5000/ (128/16) = 625. if (hinit->max_size > 10000 && nelts && hinit->max_size / nelts < 100) { start = hinit->max_size - 1000; }
判断后被赋值为19000. 这样做有啥统计依据么, 不懂啊
如图, 初始化后的结构, buckets为hash表实际的存储结构, 共有size个bucket指针, 并且实际的元素时紧挨着的, 以末尾节点的value=null为标识. 空的bucket指向null不占空间.
这里ngx_hash_elt_t的存储涉及很多内存对齐的工作.
3.查找
// key为待查找<k,v>的hash值, name为<k,v>的key, len为key的长度, 返回<k,v>的v地址 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 0 ngx_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; } while (elt->value) { if (len != (size_t) elt->len) { // key长度不服 goto next; } for (i = 0; i < len; i++) { if (name[i] != elt->name[i]) { // key内容不服 goto next; } } // 命中<k,v>, 返回v return elt->value; next: // 指针指向bucket中下一个元素, 以8bytes(64位机器)对齐 elt = (ngx_hash_elt_t *) ngx_align_ptr(&elt->name[0] + elt->len, sizeof(void *)); continue; } return NULL; }
nginx_find用来在hash表中查找<k,v>, 将key的hash值取摸size, 找到对应的buckets[i], 其buckets长度应该为初始化时的test[i], 遍历直到末尾的ngx_hash_elt_t->value==NULL ( void * 类型), 比对每个ngx_hash_elt_t的name字段(<K,V>中的K), 如果相等则命中, 返回value指向的内存(<K,V>中的V)
如图为一个bucket中存放的ngx_hash_elt_t的可能布局, 因为初始化时已经对齐, 取下一个ngx_hash_elt_t时, 需要以void * 对齐.
next:位置的代码将指针移动到bucket中的下一个元素.