nginx hash 结构

本文讲述: nginx hash结构

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;

    /* nginx hash结构大致是这样:
           hash结构中有N个桶, 每个桶存放N个元素(即<k,v>),在内存中,
        用一个指针数组记录N个桶的地址,每个桶又是一个 ngx_hash_elt_t 数组
        指针数组 和 ngx_hash_elt_t 数组 在一个连续的内存中.
        优点: 使用数组提高寻址速度
        缺点: hash表初始化后,只能查询,不能修改.

        当然hash结构本来就是数组形式,但对于冲突的元素大多是用链表形式存放,再挂载到hash数组上.
        nginx的hash结构过程:
        首先 每个桶的空间大小固定 通过 ngx_hash_init_t.bucket_size 指定;
        然后 根据元素的个数和桶的固定大小计算出需要多少个桶.
        然后 计算哪些元素存放到哪个桶中,方法就是 (元素的hash值 % 桶的个数)
        这时 需要多少个桶,这些桶需要多少内存空间,每个桶存放多少元素,需要多少内存空间就知道,
        申请所有桶的内存空间,即为 ngx_hash_init_t.hash.buckets 指针数组.
        申请每个桶存放元素的存储空间 = 该桶元素占用的内存空间 + void指针
        为了提高查询效率,申请一个连续内存空间存放 所有桶的元素.
        然后把这片连续的内存空间映射到 ngx_hash_init_t.hash.buckets 指针数组.
        然后为每个桶的元素赋值.
        最后将每个桶的"结束元素"置为NULL

        void指针的用途: 为桶的结束标记, 在 ngx_hash_find() 遍历桶是判断 etl->value 是否为NULL时用到.
        你可能有以为 "结束元素"只有一个void*指针的空间转换成 ngx_hash_elt_t 后会不会越界操作?
        答案是不会的, void*指针的空间转换成 ngx_hash_elt_t 后只操作 ngx_hash_elt_t.value ,
        而 ngx_hash_elt_t.value 刚好只占 void指针空间大小.

        指定桶的大小的好处: 保证每个桶存放元素的个数不超过一定值,目的是为了提高查询效率.
        
     */

    /* 每个桶至少能存放一个元素 + 一个void指针,
       指针的目的是为了
     */
    for (n = 0; n < nelts; n++) {
        if (hinit->bucket_size < NGX_HASH_ELT_SIZE(&names[n]) + sizeof(void *))
        {
            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;
        }
    }

    /* 用于记录每个桶的临时大小 */
    test = ngx_alloc(hinit->max_size * sizeof(u_short), hinit->pool->log);
    if (test == NULL) {
        return NGX_ERROR;
    }

    bucket_size = hinit->bucket_size - sizeof(void *);

    /* 计算需要桶数目的下界
       每个元素最少需要 NGX_HASH_ELT_SIZE(&name[n]) > (2*sizeof(void*)) 的空间
       因此 bucket_size 大小的桶最多能容下 bucket_size/(2*sizeof(void*)) 个元素
       因此 nelts 个元素就最少需要start个桶。
     */
    start = nelts / (bucket_size / (2 * sizeof(void *)));
    start = start ? start : 1;

    if (hinit->max_size > 10000 && hinit->max_size / nelts < 99) {
        start = hinit->max_size - 1000;
    }

    /* 从最小桶数目开始试,计算容下 nelts 个元素需要多少个桶 */
    for (size = start; size < hinit->max_size; 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]));

#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) { /* 说明 size 个桶,容不下 nelts 个元素 */
                goto next;
            }
        }

        goto found;

    next:

        continue;
    }

    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:

    /* 执行到这里就得到了 容下 nelts 个元素需要 size 个桶 */
    for (i = 0; i < size; i++) {
        test[i] = sizeof(void *); /* 初始化每个桶的大小 */
    }

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

        key = names[n].key_hash % size; /* 计算第N个元素放置到哪个桶中 */
        test[key] = (u_short) (test[key] + NGX_HASH_ELT_SIZE(&names[n])); /* 这个桶的空间扩大到 容下第 n 个元素 */
    }

    len = 0;

    /* 将每个桶的实际大小对应到 cacheline 并计算数所用桶的总大小 */
    for (i = 0; i < size; i++) {
        if (test[i] == sizeof(void *)) { /* 说明这个桶中没有插入元素 */
            continue;
        }

        test[i] = (u_short) (ngx_align(test[i], ngx_cacheline_size));

        len += test[i];
    }

    /* 如果hash为NULL, 就分配 ngx_hash_wildcard_t 结构,
       照 ngx_hash_init_t 的结构来说 应该分配 ngx_hash_t.
       这里分配 ngx_hash_wildcard_t 不知道有啥额外的用途.
     */
    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));

    } 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);

    for (i = 0; i < size; i++) {
        if (test[i] == sizeof(void *)) {
            continue;
        }

        /* 为指针数组的每个元素 赋值 桶的起始地址 */
        buckets[i] = (ngx_hash_elt_t *) elts;
        elts += test[i];

    }

    for (i = 0; i < size; i++) {
        test[i] = 0;
    }

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

        /* 将 names 中的元素 赋值给 对应桶中的 ngx_hash_elt_t 数组中的元素,
           names的元素在 ngx_hash_elt_t 数组的位置是无序的.
         */
        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;

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

        test[key] = (u_short) (test[key] + NGX_HASH_ELT_SIZE(&names[n]));
    }

    /* 设置每个桶的结束元素为NULL */
    for (i = 0; i < size; i++) {
        if (buckets[i] == NULL) {
            continue;
        }

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

        elt->value = NULL;
    }

    ngx_free(test);

    hinit->hash->buckets = buckets;
    hinit->hash->size = size;

#if 0

    for (i = 0; i < size; i++) {
        ngx_str_t   val;
        ngx_uint_t  key;

        elt = buckets[i];

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

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

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

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

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

#endif

    return NGX_OK;
}


  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值