leevis.com
leevis.com copied to clipboard
nginx hash表源码分析
nginx hash表源码分析
nginx中,http请求头、变量、虚拟主机用到了hash表。hash表的实现在在ngx_hash.h和ngx_hash.c文件中。
根据使用场景,nginx的hash表只有初始化hash表和查找指定key的元素两个方法,不需要增加删除元素。在nginx程序启动时初始化hash表,处理请求过程中查找hash表。
nginx的hash表是通过挂链法解决冲突。因为不需要增加删除元素,所以在实现上不是通过链表而是计算对应bucket的元素个数一次性分配对应大小的数组存储冲突元素的。在分配数组时考虑了cpu 缓存的大小,从而加快查找速度。
通用hash表(不带通配符)
使用例子
// 初始化hash table(headers_in_hash)
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;
}
// ngx_http_headers_in 是定义在ngx_http_request.c文件的一个全局变量的数组。
// 下面hash表的value还是指向该数组的元素。
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.hash = &cmcf->headers_in_hash;
hash.key = ngx_hash_key_lc;
// hash table bucket 最大个数
hash.max_size = 512;
// bucket 所能分配的内存
hash.bucket_size = ngx_align(64, ngx_cacheline_size); // 对齐到CPU cache line,为了整个bucket链表能缓存到CPU缓存中
hash.name = "headers_in_hash";
hash.pool = cf->pool;
hash.temp_pool = NULL;
// 根据数组元素初始化hash表
if (ngx_hash_init(&hash, headers_in.elts, headers_in.nelts) != NGX_OK) {
return NGX_ERROR;
}
return NGX_OK;
}
代码分析
// hash表中的元素类型
typedef struct {
void *value;
u_short len;
u_char name[1];
} ngx_hash_elt_t;
// 初始化hash表所要准备的数组元素类型。
typedef struct {
ngx_str_t key;
ngx_uint_t key_hash;
void *value;
} ngx_hash_key_t;
// 初始化hash表所需要的init结构体
typedef struct {
ngx_hash_t *hash; // hash表结果
ngx_hash_key_pt key;
ngx_uint_t max_size; // 最大bucket个数
ngx_uint_t bucket_size; // 单一bucket内存最大限度
char *name;
ngx_pool_t *pool;
ngx_pool_t *temp_pool;
} ngx_hash_init_t;
// hash 表桶定义
typedef struct {
ngx_hash_elt_t **buckets;
ngx_uint_t size; // 桶大小
} ngx_hash_t;
// 通过ngx_hash_key_t计算ngx_hash_elt_t大小
// *value指针的字节数+name需要分配的字节数+name的长度2个字节u_short类型的len
#define NGX_HASH_ELT_SIZE(name) \
(sizeof(void *) + ngx_align((name)->key.len + 2, sizeof(void *)))
// 根据元素数组初始化hash table
// names 是数组首地址,nelts是数组元素个数
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 bucket 最大数不能为0
if (hinit->max_size == 0) {
ngx_log_error(NGX_LOG_EMERG, hinit->pool->log, 0,
"could not build %s, you should "
"increase %s_max_size: %i",
hinit->name, hinit->name, hinit->max_size);
return NGX_ERROR;
}
// 检测每个bucket至少要能存下一个(ngx_hash_elt_t)元素。
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 %s, you should "
"increase %s_bucket_size: %i",
hinit->name, hinit->name, hinit->bucket_size);
return NGX_ERROR;
}
}
// 记了bucket大小的数组
test = ngx_alloc(hinit->max_size * sizeof(u_short), hinit->pool->log);
if (test == NULL) {
return NGX_ERROR;
}
// 可以存元素的bucket数量,最后一个bucket为null,用来标志bucket的结尾。
bucket_size = hinit->bucket_size - sizeof(void *);
// hash nelts 多个元素,至少需要的bucket数量。
// bucket数量=总元素个数/每个bucket可以存的元素个数
// 2*sizeof(void*)可以转换为sizeof(ngx_hash_elt_t)
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;
}
// start 是最少需要的桶的数量
// 尽量保证每个桶内元素占用的内存不超过bucket_size。
// 注意:超过也可以,这么做应该是保证每个桶不至于元素太多吧。
// size是最终计算出需要的桶的个数
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;
// 该key下标对应的bucket的总字节数=原字节数+本次插入元素的字节数
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
// bucket 分配的内存不能存储hash到该下标的所有元素,需要增加bucket数量。
if (test[key] > (u_short) bucket_size) {
goto next;
}
}
goto found;
next:
continue;
}
size = hinit->max_size;
ngx_log_error(NGX_LOG_WARN, hinit->pool->log, 0,
"could not build optimal %s, you should increase "
"either %s_max_size: %i or %s_bucket_size: %i; "
"ignoring %s_bucket_size",
hinit->name, hinit->name, hinit->max_size,
hinit->name, hinit->bucket_size, hinit->name);
found:
// 已确定bucket最小数量了
// bucket 元素结束标志null 指针。
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;
// 对应key下标的bucket上存储元素总字节。
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 {
// bucket 数组
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;
}
// 为bucket分配内存
buckets[i] = (ngx_hash_elt_t *) elts;
elts += test[i];
}
// 清理test数组,test数组下面的用途是记录对应bucket内存已使用字节数。
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]);
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);
// 更新记录对应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]);
elt->value = NULL;
}
ngx_free(test);
// hash桶 数组首地址
hinit->hash->buckets = buckets;
// hash桶大小
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;
}
画了一个图,看下hash表内存结构。
// 从hash table中查找元素
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
// bucket 首元素
elt = hash->buckets[key % hash->size];
// bucket 没有元素
if (elt == NULL) {
return NULL;
}
// bucket 元素值指针不为NULL
while (elt->value) {
// key 的长度是否相同
if (len != (size_t) elt->len) {
goto next;
}
// key的内容是否相同
for (i = 0; i < len; i++) {
if (name[i] != elt->name[i]) {
goto next;
}
}
// 找到key返回对应的value指针。
return elt->value;
next:
// 下一个元素
elt = (ngx_hash_elt_t *) ngx_align_ptr(&elt->name[0] + elt->len,
sizeof(void *));
continue;
}
return NULL;
}
参考:http://blog.chinaunix.net/uid-27767798-id-3766755.html
通配符hash表
通配符只允许以下几种: .liwq.com 除了会匹配xxx.liwq.com还会匹配liwq.com *.applinzi.com 只匹配xxx.liwq.com www.liwq.* 匹配www.liwq.xxx
在ngx_http.c文件的ngx_http_server_names函数中使用了。
例子
首先会分配一个ngx_hash_keys_arrays_t
结构体,并调用ngx_hash_keys_array_init
初始化。调用ngx_hash_add_key
添加元素。
例如,ngx_http_variables_add_core_vars这个方法的代码,分配结构体并初始化。
......
cmcf->variables_keys = ngx_pcalloc(cf->temp_pool,
sizeof(ngx_hash_keys_arrays_t));
if (cmcf->variables_keys == NULL) {
return NGX_ERROR;
}
cmcf->variables_keys->pool = cf->pool;
cmcf->variables_keys->temp_pool = cf->pool;
if (ngx_hash_keys_array_init(cmcf->variables_keys, NGX_HASH_SMALL)
!= NGX_OK)
{
return NGX_ERROR;
}
......
例如,ngx_http_add_variable这个方法的代码,添加元素
......
v = ngx_palloc(cf->pool, sizeof(ngx_http_variable_t));
if (v == NULL) {
return NULL;
}
v->name.len = name->len;
v->name.data = ngx_pnalloc(cf->pool, name->len);
if (v->name.data == NULL) {
return NULL;
}
ngx_strlow(v->name.data, name->data, name->len);
v->set_handler = NULL;
v->get_handler = NULL;
v->data = 0;
v->flags = flags;
v->index = 0;
rc = ngx_hash_add_key(cmcf->variables_keys, &v->name, v, 0);
......
源码
ngx_hash_keys_arrays_t结构体定义:
typedef struct {
// hash表 bucket的大小,直接决定了查找速度和内存占用。
ngx_uint_t hsize;
ngx_pool_t *pool;
ngx_pool_t *temp_pool;
// 精确匹配
// 要添加要精确匹配查找hash表的原始元素
ngx_array_t keys; // ngx_hash_key_t 类型的数组,最终会被ngx_hash_init函数用到。
// hash表,ngx_str_t类型的 数组的数组,为了调用ngx_hash_add_key插入元素时候验证是否key有冲突
ngx_array_t *keys_hash;
// 前置通配符
// ngx_hash_key_t类型的数组,会把*.test.com 转为 com.test./0 作为key。
ngx_array_t dns_wc_head;
// 前置匹配hash表,保存的是原始的字符串,为了检测冲突。
ngx_array_t *dns_wc_head_hash;
// 后置通配符
// 原始元素
ngx_array_t dns_wc_tail;
// 后置匹配hash表
ngx_array_t *dns_wc_tail_hash;
} ngx_hash_keys_arrays_t;
ngx_hash_keys_array_init方法定义:
ngx_int_t
ngx_hash_keys_array_init(ngx_hash_keys_arrays_t *ha, ngx_uint_t type)
{
ngx_uint_t asize; // 存储原始元素数组的预设大小,当调用ngx_array_push添加元素时,会动态增加
if (type == NGX_HASH_SMALL) {
asize = 4;
ha->hsize = 107;
} else {
asize = NGX_HASH_LARGE_ASIZE;
ha->hsize = NGX_HASH_LARGE_HSIZE;
}
// 存储精确匹配查找元素,用来初始化精确匹配hash表
if (ngx_array_init(&ha->keys, ha->temp_pool, asize, sizeof(ngx_hash_key_t))
!= NGX_OK)
{
return NGX_ERROR;
}
if (ngx_array_init(&ha->dns_wc_head, ha->temp_pool, asize,
sizeof(ngx_hash_key_t))
!= NGX_OK)
{
return NGX_ERROR;
}
if (ngx_array_init(&ha->dns_wc_tail, ha->temp_pool, asize,
sizeof(ngx_hash_key_t))
!= NGX_OK)
{
return NGX_ERROR;
}
ha->keys_hash = ngx_pcalloc(ha->temp_pool, sizeof(ngx_array_t) * ha->hsize);
if (ha->keys_hash == NULL) {
return NGX_ERROR;
}
ha->dns_wc_head_hash = ngx_pcalloc(ha->temp_pool,
sizeof(ngx_array_t) * ha->hsize);
if (ha->dns_wc_head_hash == NULL) {
return NGX_ERROR;
}
ha->dns_wc_tail_hash = ngx_pcalloc(ha->temp_pool,
sizeof(ngx_array_t) * ha->hsize);
if (ha->dns_wc_tail_hash == NULL) {
return NGX_ERROR;
}
return NGX_OK;
}
添加元素ngx_hash_add_key代码
ngx_int_t
ngx_hash_add_key(ngx_hash_keys_arrays_t *ha, ngx_str_t *key, void *value,
ngx_uint_t flags)
{
size_t len;
u_char *p;
ngx_str_t *name;
ngx_uint_t i, k, n, skip, last;
ngx_array_t *keys, *hwc;
ngx_hash_key_t *hk;
last = key->len;
// 带通配符匹配查找
if (flags & NGX_HASH_WILDCARD_KEY) {
/*
* supported wildcards:
* "*.example.com", ".example.com", and "www.example.*"
*/
n = 0;
for (i = 0; i < key->len; i++) {
if (key->data[i] == '*') {
if (++n > 1) {
// 多个通配符返回错误
return NGX_DECLINED;
}
}
if (key->data[i] == '.' && key->data[i + 1] == '.') {
return NGX_DECLINED;
}
if (key->data[i] == '\0') {
return NGX_DECLINED;
}
}
if (key->len > 1 && key->data[0] == '.') {
skip = 1;
goto wildcard;
}
if (key->len > 2) {
if (key->data[0] == '*' && key->data[1] == '.') {
skip = 2;
goto wildcard;
}
if (key->data[i - 2] == '.' && key->data[i - 1] == '*') {
skip = 0;
last -= 2;
goto wildcard;
}
}
if (n) {
// 有通配符,通配符在不合理的位置(不是前置或后置通配符),则返回错误。
return NGX_DECLINED;
}
}
// flag是表示带通配符,但实际配置没有通配符,则向下执行添加到精确匹配hash表中。
/* exact hash */
// 精确匹hash表
k = 0;
for (i = 0; i < last; i++) {
if (!(flags & NGX_HASH_READONLY_KEY)) {
key->data[i] = ngx_tolower(key->data[i]);
}
k = ngx_hash(k, key->data[i]);
}
// k 元素所在桶的下标
k %= ha->hsize;
/* check conflicts in exact hash */
name = ha->keys_hash[k].elts;
if (name) {
for (i = 0; i < ha->keys_hash[k].nelts; i++) {
if (last != name[i].len) {
continue;
}
// 元素冲突
if (ngx_strncmp(key->data, name[i].data, last) == 0) {
return NGX_BUSY;
}
}
} else {
// 对应桶没有元素,分配动态数组存储元素
if (ngx_array_init(&ha->keys_hash[k], ha->temp_pool, 4,
sizeof(ngx_str_t))
!= NGX_OK)
{
return NGX_ERROR;
}
}
// 把元素的key存储到对应hash桶中
name = ngx_array_push(&ha->keys_hash[k]);
if (name == NULL) {
return NGX_ERROR;
}
*name = *key;
// 元素的key和val
hk = ngx_array_push(&ha->keys);
if (hk == NULL) {
return NGX_ERROR;
}
hk->key = *key;
hk->key_hash = ngx_hash_key(key->data, last);
hk->value = value;
return NGX_OK;
wildcard:
/* wildcard hash */
k = ngx_hash_strlow(&key->data[skip], &key->data[skip], last - skip);
k %= ha->hsize;
if (skip == 1) {
// 前置通配符,匹配精确匹配的。
/* check conflicts in exact hash for ".example.com" */
name = ha->keys_hash[k].elts;
if (name) {
len = last - skip;
for (i = 0; i < ha->keys_hash[k].nelts; i++) {
if (len != name[i].len) {
continue;
}
if (ngx_strncmp(&key->data[1], name[i].data, len) == 0) {
return NGX_BUSY;
}
}
} else {
if (ngx_array_init(&ha->keys_hash[k], ha->temp_pool, 4,
sizeof(ngx_str_t))
!= NGX_OK)
{
return NGX_ERROR;
}
}
name = ngx_array_push(&ha->keys_hash[k]);
if (name == NULL) {
return NGX_ERROR;
}
name->len = last - 1;
name->data = ngx_pnalloc(ha->temp_pool, name->len);
if (name->data == NULL) {
return NGX_ERROR;
}
ngx_memcpy(name->data, &key->data[1], name->len);
}
if (skip) {
// 前置匹配
/*
* convert "*.example.com" to "com.example.\0"
* and ".example.com" to "com.example\0"
*/
p = ngx_pnalloc(ha->temp_pool, last);
if (p == NULL) {
return NGX_ERROR;
}
len = 0;
n = 0;
for (i = last - 1; i; i--) {
if (key->data[i] == '.') {
ngx_memcpy(&p[n], &key->data[i + 1], len);
n += len;
p[n++] = '.';
len = 0;
continue;
}
len++;
}
if (len) {
ngx_memcpy(&p[n], &key->data[1], len);
n += len;
}
p[n] = '\0';
hwc = &ha->dns_wc_head;
keys = &ha->dns_wc_head_hash[k];
} else {
// 后置通配符
/* convert "www.example.*" to "www.example\0" */
last++;
p = ngx_pnalloc(ha->temp_pool, last);
if (p == NULL) {
return NGX_ERROR;
}
ngx_cpystrn(p, key->data, last);
hwc = &ha->dns_wc_tail;
keys = &ha->dns_wc_tail_hash[k];
}
/* check conflicts in wildcard hash */
name = keys->elts;
if (name) {
len = last - skip;
for (i = 0; i < keys->nelts; i++) {
if (len != name[i].len) {
continue;
}
if (ngx_strncmp(key->data + skip, name[i].data, len) == 0) {
return NGX_BUSY;
}
}
} else {
if (ngx_array_init(keys, ha->temp_pool, 4, sizeof(ngx_str_t)) != NGX_OK)
{
return NGX_ERROR;
}
}
name = ngx_array_push(keys);
if (name == NULL) {
return NGX_ERROR;
}
name->len = last - skip;
name->data = ngx_pnalloc(ha->temp_pool, name->len);
if (name->data == NULL) {
return NGX_ERROR;
}
ngx_memcpy(name->data, key->data + skip, name->len);
/* add to wildcard hash */
hk = ngx_array_push(hwc);
if (hk == NULL) {
return NGX_ERROR;
}
hk->key.len = last - 1;
hk->key.data = p;
hk->key_hash = 0;
hk->value = value;
return NGX_OK;
}