NGINX源码之:ngx_hash

老规矩,先来看下ngx_hash大概的结构图:
在这里插入图片描述

注意: name的剩余字节是不在ngx_hash_elt_t的结构体占用内存中的,这里是个奇技淫巧,一定程度上保证key的不定长和内存浪费

在进入源码分析之前,先上一段debug用的示例代码

#include <stdio.h>
#include <string.h>
#include "nginx.h"
#include "ngx_hash.h"
#include "ngx_string.h"
#include "ngx_config.h"
#include "ngx_conf_file.h"
#include "ngx_core.h"
#include "ngx_palloc.h"
#define POOL_SIZE 2000

typedef struct Test_2
{
    int a ;
    int b ;
    ngx_queue_t testq;
} test2;

void testHash(){
    //demo中没有其他位置在启动时设置缓存行大小,此处调用cpuinfo()设置缓存行
    //在后面元素和bucket内存对齐时需要用到缓存行
    ngx_cpuinfo();
    u_short *test = test = ngx_alloc(4 * sizeof(u_short), NULL);;
    test[0] = 58;
    test[1] = 23;
    test[2] = 121;
    test[3] = 140;
    int a = 0;
    for( a = 0; a<4;a++){
        printf("test: %d \n" , test[a]);
        test[a] = (u_short) (ngx_align(test[a], ngx_cacheline_size));
        printf("test: %d \n" , test[a]);
    }

    ngx_pool_t * pool = ngx_create_pool(POOL_SIZE,NULL);
    ngx_array_t *arr = ngx_array_create(pool, 32, sizeof(ngx_hash_key_t));

    ngx_hash_keys_arrays_t haArr;
    ngx_memzero(&haArr, sizeof(ngx_hash_keys_arrays_t));
    haArr.temp_pool = pool;
    ngx_hash_keys_array_init(&haArr,NGX_HASH_LARGE_ASIZE);
    printf("size of ngx_hash_elt_t: %d \n" , sizeof(ngx_hash_elt_t) );
    printf("size of ngx_hash_key_t: %d \n" , sizeof(ngx_hash_key_t) );
    printf("size of u_short: %d \n" , sizeof(u_short) );
    printf("size of void*: %d \n" , sizeof(void*) );
    printf("size of char: %d \n" , sizeof(u_char) );


    test2 value[5];
    ngx_str_t key0 = ngx_string("bbbbb-key");
    ngx_str_t key1 = ngx_string("aaaaa-key");
    ngx_str_t key2 = ngx_string("ccccc-key");
    ngx_str_t key3 = ngx_string("ggggg-key");
    ngx_str_t key4 = ngx_string("kkkkk-key");

    ngx_uint_t       i;
    for(i = 0;i<5;i++){
        value[i].a = i;
        value[i].b = i*i;
    }

    ngx_hash_init_t *hash;
    hash = ngx_palloc(pool, sizeof(ngx_hash_init_t));
    hash->pool = pool;
    hash->name = "test_2_hash";
    hash->key = ngx_hash_key;
    hash->max_size = 128;
    hash->bucket_size = 64/*sizeof(test2) *8*/;

    ngx_hash_add_key(&haArr,&key0,&value[0],NGX_HASH_READONLY_KEY);
    ngx_hash_add_key(&haArr,&key1,&value[1],NGX_HASH_READONLY_KEY);
    ngx_hash_add_key(&haArr,&key2,&value[2],NGX_HASH_READONLY_KEY);
    ngx_hash_add_key(&haArr,&key3,&value[3],NGX_HASH_READONLY_KEY);
    ngx_hash_add_key(&haArr,&key4,&value[4],NGX_HASH_READONLY_KEY);

    int testsize = hash->bucket_size / (2 * sizeof(void *));

    ngx_hash_key_t *names = haArr.keys.elts;
    test2 *value00 = names[0].value;
    test2 *value01 = names[1].value;
    ngx_hash_init(hash,haArr.keys.elts,haArr.keys.nelts);

    test2 *node = ngx_hash_find(hash->hash, names[2].key_hash, key2.data, key2.len);
}
void testHashWC(){
    ngx_cpuinfo();

    u_char a[10] = "hElLo";
    u_char b[10] = "";
    u_char *c = a;
    u_char *d = b;
    int n = ngx_strlen(a);
    while (n--) {
        u_char e = *c;
        *d = ngx_tolower(e);
        c++;
        d++;
    }
    ngx_strlow(b,a,3);
//    每个地址对应8bit的内存空间(最小内存单元,1字节)
//    在64位系统中,内存对齐8字节(跨8个地址),每分配两段8字节相邻的内存对齐后的相邻指针相差8,因此指针二进制的后三位为0(32位系统,后两位为0);
//    因此获取的指针地址都是8的倍数
    void *ptra = (void *) ((uintptr_t) a );
    void *ptr = (void *) ((uintptr_t) a | 1);
    void *ptr1 = (void *) ((uintptr_t) a | 2);
    void *ptr2 = (void *) ((uintptr_t) a | 3);

    ngx_pool_t * pool = ngx_create_pool(POOL_SIZE,NULL);

    ngx_hash_keys_arrays_t haArr;
    ngx_memzero(&haArr, sizeof(ngx_hash_keys_arrays_t));
    haArr.temp_pool = pool;
    ngx_hash_keys_array_init(&haArr,NGX_HASH_LARGE_ASIZE);
    test2 value[6];

//    ngx_str_t *key0 = ngx_pcalloc(pool, sizeof(ngx_str_t));
//    key0->data = "*.com.cn"; //ngx_strlow不能改变常量,因此data的值不能是常量
//    key0->len = strlen("*.com.cn");
    ngx_str_t key0 = ngx_string("*.com.cn");
    key0.data = ngx_pcalloc(pool, key0.len);
    ngx_memcpy(key0.data,"*.com.cn",key0.len);

    ngx_str_t key1 = ngx_string("*.a.com.cn");
    key1.data = ngx_pcalloc(pool, key1.len);
    ngx_memcpy(key1.data,"*.a.com.cn",key1.len);

    ngx_str_t key2 = ngx_string(".b.com.cn");
    key2.data = ngx_pcalloc(pool, key2.len);
    ngx_memcpy(key2.data,".b.com.cn",key2.len);

    ngx_str_t key3 = ngx_string("cn.org.*");
    key3.data = ngx_pcalloc(pool, key3.len);
    ngx_memcpy(key3.data,"cn.org.*",key3.len);

    ngx_str_t key4 = ngx_string("*.test.cc");
    key4.data = ngx_pcalloc(pool, key4.len);
    ngx_memcpy(key4.data,"*.test.cc",key4.len);

    ngx_str_t key5 = ngx_string(".com.cn");
    key5.data = ngx_pcalloc(pool, key5.len);
    ngx_memcpy(key5.data,".com.cn",key5.len);

    ngx_str_t key6 = ngx_string("*.c.com.cn");
    key6.data = ngx_pcalloc(pool, key6.len);
    ngx_memcpy(key6.data,"*.c.com.cn",key6.len);

    ngx_uint_t       i;
    for(i = 0;i<6;i++){
        value[i].a = i;
        value[i].b = i*i;
    }

    ngx_hash_init_t *hash;
    hash = ngx_palloc(pool, sizeof(ngx_hash_init_t));
    hash->pool = pool;
    hash->temp_pool = pool;
    hash->name = "test_2_hash";
    hash->key = ngx_hash_key;
    hash->max_size = 128;
    hash->bucket_size = 64/*sizeof(test2) *8*/;

    ngx_hash_add_key(&haArr,&key0,&value[0],NGX_HASH_WILDCARD_KEY);
    //*.com.cn与.com.cn在dns_wc_head_hash中都是com.cn。因此这里.com.cn不能添加
    ngx_hash_add_key(&haArr,&key5,&value[5],NGX_HASH_WILDCARD_KEY);
    ngx_hash_add_key(&haArr,&key1,&value[1],NGX_HASH_WILDCARD_KEY);
    ngx_hash_add_key(&haArr,&key2,&value[2],NGX_HASH_WILDCARD_KEY);
    ngx_hash_add_key(&haArr,&key3,&value[3],NGX_HASH_WILDCARD_KEY);
    ngx_hash_add_key(&haArr,&key4,&value[4],NGX_HASH_WILDCARD_KEY);
    ngx_hash_add_key(&haArr,&key6,&value[6],NGX_HASH_WILDCARD_KEY);


    ngx_hash_key_t *names = haArr.dns_wc_head.elts;
    test2 *value00 = names[0].value;
    test2 *value01 = names[1].value;
    ngx_hash_wildcard_init(hash,haArr.dns_wc_head.elts,haArr.dns_wc_head.nelts);
    
    ngx_str_t test_0 = ngx_string("e.com.cn");
    test2 *node = ngx_hash_find_wc_head(hash->hash,test_0.data,test_0.len);

    ngx_str_t test_1 = ngx_string("a.a.com.cn");
    test2 *node1 = ngx_hash_find_wc_head(hash->hash,test_1.data,test_1.len);

    ngx_str_t test_2 = ngx_string(".com.cn");
    test2 *node2 = ngx_hash_find_wc_head(hash->hash,test_2.data,test_2.len);
//    ngx_hash_wildcard_init(hash,haArr.dns_wc_tail.elts,haArr.dns_wc_tail.nelts);
}
void main(){
//    testHash();
    testHashWC();
}

1、key数组初始化:ngx_hash_keys_array_init
key数组的初始化,主要是给ngx_hash_keys_arrays_t中的keys,dns_wc_head,dns_wc_tail,几个数组申请内存空间,ngx_hash_keys_arrays_t的主要成员:

    ngx_uint_t        hsize;            //hash表bucket(槽)个数
    
    ngx_array_t       keys;             //普通(非通配符)key数组
    ngx_array_t      *keys_hash;        //数组的指针,可以指向多个数组,将keys,按hash % hsize分组存放。
                                        //实际上是一个二维数组。

    ngx_array_t       dns_wc_head;     //前缀通配符的key数组,如:*.com.cn
    ngx_array_t      *dns_wc_head_hash;

    ngx_array_t       dns_wc_tail;     //后缀通配符的key数组,如:test.com*
    ngx_array_t      *dns_wc_tail_hash;

在这里插入图片描述
2、添加key:ngx_hash_add_key
添加key有两种类型的,一个是NGX_HASH_READONLY_KEY普通的key,一个是
NGX_HASH_WILDCARD_KEY通配符的key。

ngx_hash_add_key(&haArr,&key0,&value[0],NGX_HASH_READONLY_KEY);
//or
ngx_hash_add_key(&haArr,&key0,&value[0],NGX_HASH_WILDCARD_KEY);

在这里插入图片描述
通配符key的处理:
在这里插入图片描述
demo结果如图:
在这里插入图片描述
在这里插入图片描述

3、普通hash表初始化:ngx_hash_init

方法的最开始,对几个参数做了限制:
max_size不能为0;单个bucket大小不能超过65536-缓存行大小,同时由于bucket之间有个void指针相隔,因此单个bucket的大小不能小于一个ngx_hash_elt_t+一个void指针的大小。由于做了指针对齐,因此ngx_hash_elt_t的大小最小都要两个void指针的大小。bucket的大小最小都要三个void指针大小;64位系统中,一个指针大小8个字节,因此bucket最小3*8 = 24;
在这里插入图片描述
在这里插入图片描述

对齐方法解析
#define ngx_align(d, a) (((d) + (a - 1)) & ~(a - 1))//a为2的n次幂
假设a=8,那么a-1二进制为00000111,d的低四位任一一位只要为1,d+(a-1)后,d的第四位或者高于第四位的某一位肯定进1。也就是d的4位之前的值肯定是8的倍数。如d = 17;二进制为00010001,
c = 00010011 + 00000111 == 00011010,第四位之前的00011000值为24,为8的倍数,那么怎么把c的后三位清零了:c & ~(a-1)即可: 00011010 & 11111000 = = 00011000

几个结构体区分
ngx_hash_t,ngx_hash_wildcard_t这两个是hash表,表结构体
ngx_hash_key_t这个是建立hash表前的每个key的结构体
ngx_hash_elt_t 这个是建立hash表后,每个bucket中的元素的结构体。

4、普通hash表查询元素:ngx_hash_find
在这里插入图片描述

5、通配符hash初始化:ngx_hash_wildcard_init
方法开始先初始化了两个数组curr_names与next_names,curr_names一个记录当前同级的每个name; next_names则记录当前name第一个点的后续字符串。
下面是通配符hash生成过程:
在这里插入图片描述
效果如下:
在这里插入图片描述
注意这里:cn.com.c中间有个cc.test.从源码看没有在cn.com时一起处理。而是以完成的cn.com.c另起炉灶创建通配符hash表。计算hash时,都是以cn开头计算hash,则不管最开始的names需要多少个bucket,cn.com.c的通配符hash与cn.com都是放在同一个bucket里面。(即hash冲突场景)

关于对齐指针的后两位为00:
每个地址对应8bit的内存空间(最小内存单元,1字节)
在64位系统中,内存对齐8字节(跨8个地址),每分配两段8字节相邻的内存对齐后的相邻指针相差8,因此指针二进制的后三位为0(32位系统,后两位为0);
因此获取的指针地址都是8的倍数

6、通配符hash表元素查找:ngx_hash_find_wc_head(ngx_hash_find_wc_tail)
这里仅以ngx_hash_find_wc_head为例:
从上面的图可以知道,通配符字符串的每一点分级后,每个分级的结构为:
hwc(或hhint)= =>ngx_hash_t = =>*bucket = =>elt.value= =>下一级hwc(或匹配的数据)
在这里插入图片描述

  • 当hash表以 *.com.cn构建:即从name = cn.com.开始:一并构建hash的name还有cn.com.a. 与cn.com.b
    查找输入为 com.cn : 无法匹配到hash节点;
    e.com.cn : 查找到com所在hwc节点的value;即最长匹配到.com.cn
    .com.cn : 同e.com.cn
    cn : 直接查找第一级的普通hash,但value肯定是null;
    a.a.com.cn: 最长匹配到a,匹配到a数据结点的value;
  • 当hash表以 .com.cn构建:
    查找输入为com.cn :查找到com所在hwc节点的value;即最长匹配到.com.cn
    其他的输入与*.com.cn构建的hash表一样。

总的来说,通配符的hash,ngx做得挺啰嗦的,个人感觉以点开头或结尾的通配符配置方式没有必要存在,只需要处理*号开头或者结尾的通配字符串,毕竟约定大于配置,搞那么多兼容的配置代码复杂,配置也复杂。不过也可能跟毛子的习惯有关。

参考:
菜鸟nginx源码剖析
Nginx源码分析

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值