nginx代码分析-基本结构-哈希表ngx_hash_t

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;

imageimage

如图
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;

 image

image

如图
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. 这样做有啥统计依据么, 不懂啊

image

如图, 初始化后的结构, 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)

image

如图为一个bucket中存放的ngx_hash_elt_t的可能布局, 因为初始化时已经对齐,  取下一个ngx_hash_elt_t时, 需要以void * 对齐.
next:位置的代码将指针移动到bucket中的下一个元素.

转载于:https://my.oschina.net/chenzhuo/blog/177866

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值