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中的下一个元素.