ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

ngx学习——hash

2021-08-18 09:33:21  阅读:189  来源: 互联网

标签:hash hinit 学习 key ngx elt size


1. 思想

ngx的hash更确切的描述是使用散列实现的表,在构建表时将数据按照 key,value 的方式录入表,查表时按照key返回value.

ngx的hash用表项在运行前已经确定,所以可以确定整个表占用的最小空间。

对于表项动态变化,ngx使用 树作为容器

ngx的散列支持通配符,不过这里不考虑

2. 实现

// 桶中元素
// 分为两类:普通元素和哨兵
// 普通元素 value, len ,name 皆有用
// 哨兵:只是用value, value= NULL
typedef struct {
    void             *value;    // value同时做哨兵,所以放在首个
    u_short           len;
    u_char            name[1];
} ngx_hash_elt_t;
// 所有桶
// size : 桶的数量
typedef struct {
    ngx_hash_elt_t  **buckets;
    ngx_uint_t        size;
} ngx_hash_t;

// 辅助结构体,不会保存在 ngx_hash_t中
// key  ,元素的 key
// key_hash,  使用 key 计算出的 key_hash 码
// value 元素的 value
typedef struct {
    ngx_str_t         key;
    ngx_uint_t        key_hash;
    void             *value;
} ngx_hash_key_t;

// 计算key_hash的方法
typedef ngx_uint_t (*ngx_hash_key_pt) (u_char *data, size_t len);
typedef struct {
    ngx_hash_t       *hash;
    ngx_hash_key_pt   key;

    ngx_uint_t        max_size; //最多支持的桶的数量
    ngx_uint_t        bucket_size; // 最多支持的桶的大小 

    char             *name;     // hash名称,本身不会保存到hash桶中,作为调试信息使用
    ngx_pool_t       *pool; 
    ngx_pool_t       *temp_pool;
} ngx_hash_init_t;

void *ngx_hash_find(ngx_hash_t *hash, ngx_uint_t key, u_char *name, size_t len);
ngx_int_t ngx_hash_init(ngx_hash_init_t *hinit, ngx_hash_key_t *names,
    ngx_uint_t nelts);
typedef struct {
    ...
    ngx_hash_t                 headers_in_hash; 
    ...
} ngx_http_core_main_conf_t;

ngx_http_header_t  ngx_http_headers_in[] = {
    { ngx_string("Host"), offsetof(ngx_http_headers_in_t, host),
                 ngx_http_process_host },

    { ngx_string("Connection"), offsetof(ngx_http_headers_in_t, connection),
                 ngx_http_process_connection },
    ...
};

static ngx_int_t
ngx_http_init_headers_in_hash(ngx_conf_t *cf, ngx_http_core_main_conf_t *cmcf)
{
    ngx_array_t         headers_in;
    ngx_hash_key_t     *hk;
    ngx_hash_init_t     hash;
    ngx_http_header_t  *header;

    if (ngx_array_init(&headers_in, cf->temp_pool, 32, sizeof(ngx_hash_key_t))
        != NGX_OK)
    {
        return NGX_ERROR;
    }

    for (header = ngx_http_headers_in; header->name.len; header++) {
        hk = ngx_array_push(&headers_in);
        if (hk == NULL) {
            return NGX_ERROR;
        }

        hk->key = header->name;
        hk->key_hash = ngx_hash_key_lc(header->name.data, header->name.len);
        hk->value = header;
    }

    hash.hash = &cmcf->headers_in_hash; // 哈希句柄, 说明hash.hash是输出参数
    hash.key = ngx_hash_key_lc;         // 求key的方法
    hash.max_size = 512;                // 最大能接受的桶的数量
    hash.bucket_size = ngx_align(64, ngx_cacheline_size);   // 每个桶最多能接受的字节数
    hash.name = "headers_in_hash";
    hash.pool = cf->pool;
    hash.temp_pool = NULL;

    if (ngx_hash_init(&hash, headers_in.elts, headers_in.nelts) != NGX_OK) {
        return NGX_ERROR;
    }

    return NGX_OK;
}

关键的是 数组元素类型必须是 ngx_hash_key_t

typedef struct {
    ngx_str_t         key;
    ngx_uint_t        key_hash;
    void             *value;
} ngx_hash_key_t;    

因为 这里的 表项是一旦建立就不会有增删,而ngx为了追求极致的内存使用,会预先计算需要的最小空间

// sizeof(void *) 是 value 成员的空间
// (name)->key.len 是 name的空间
// 2 是 len的空间, u_short
#define NGX_HASH_ELT_SIZE(name)                                               \
    (sizeof(void *) + ngx_align((name)->key.len + 2, sizeof(void *)))

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;

    // 一个桶至少需要容纳一个元素,所以桶的容量至少 大于 下面的值
    // 还可以看出一个桶 需容纳 n个元素 和 一个void *指针, void *指针做次桶的哨兵
    // 元素空间又 一个 void * 成员 和 一定长度的 char * 组成
    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是测量桶的表, max_size 说明 test 能表示最多使用 max_size个桶
    test = ngx_alloc(hinit->max_size * sizeof(u_short), hinit->pool->log);
    if (test == NULL) {
        return NGX_ERROR;
    }
    // 所有桶的大小 减少 sizeof(void *), 对应 下面求桶数量处, 少加一个 sizeof(void *)
    bucket_size = hinit->bucket_size - sizeof(void *);
    
    // nelts * (2*sizeof(void *)) : 所有元素最少使用空间(假设都没有key))
    // start * bucket_size : start为桶的数量,bucket_size 为单个桶的 容积,所以这个表示所有桶的容积
    // start *bucket_size == nelts * (2*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++) {

        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 (test[key] > (u_short) bucket_size) {
                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:
    // 找到合适 桶的数量 : 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;
        test[key] = (u_short) (test[key] + NGX_HASH_ELT_SIZE(&names[n]));
    }

    // 计算所有桶的总空间,考虑对齐
    len = 0;

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

    // 分配句柄空间
    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;
        }

        key = names[n].key_hash % size;
        elt = (ngx_hash_elt_t *) ((u_char *) buckets[key] + test[key]); // test[key]为该桶已经使用的空间大小,buckets[key]为该桶的起始地址,所以得到桶的下一可用空间的地址,紧密排布

        elt->value = names[n].value;
        elt->len = (u_short) names[n].key.len;              // elt->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]));
    }

    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;

    return NGX_OK;
}

构建完表后,之后查表方法

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) {
            goto next;
        }
        // 没有用 strcmp ,可见ngx在细节的优化
        for (i = 0; i < len; i++) {
            if (name[i] != elt->name[i]) {
                goto next;
            }
        }

        return elt->value;

    next:

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

    return NULL;
}

标签:hash,hinit,学习,key,ngx,elt,size
来源: https://www.cnblogs.com/yangxinrui/p/15155335.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有