nginx 变量的保存结构和优化

来源:互联网 发布:c语言小游戏程序 编辑:程序博客网 时间:2024/06/09 14:06
在使用nginx lua 时经常会使用ngx.var.varname 来获取变量,随着业务的越复杂,变量越来越多,发现服务的速度明显变慢了本文分析了整个变量的获取和保存逻辑
  1. ngx_lua 获取变量

首先ngx_lua 获取变量调用的是 ngx_http_lua_var_get(lua_State *L)

46-static int 47ngx_http_lua_var_get(lua_State *L) 48{ 49    ngx_http_request_t          *r; 50    u_char                      *p, *lowcase; 51    size_t                       len; 52    ngx_uint_t                   hash; 53    ngx_str_t                    name; 54    ngx_http_variable_value_t   *vv; 55 56#if (NGX_PCRE) 57    u_char                      *val; 58    ngx_uint_t                   n; 59    LUA_NUMBER                   index; 60    int                         *cap; 61#endif 62 63    r = ngx_http_lua_get_req(L); 64    if (r == NULL) { 65        return luaL_error(L, "no request object found"); 66    } ... 111    p = (u_char *) lua_tolstring(L, -1, &len);112113    lowcase = lua_newuserdata(L, len);114115    hash = ngx_hash_strlow(lowcase, p, len);116117    name.len = len;118    name.data = lowcase;119    dd("variable name: %.*s", (int) len, lowcase);120    vv = ngx_http_get_variable(r, &name, hash);

可见就是调用nginx的变量获取接口, 可以看到前面将变量名全部变为小写然后计算hash,ngx_lua是不区分大小写的

  1. nginx 变量的获取
546-ngx_http_variable_value_t * 547ngx_http_get_variable(ngx_http_request_t *r, ngx_str_t *name, ngx_uint_t key) 548{ 549    ngx_http_variable_t        *v; 550    ngx_http_variable_value_t  *vv; 551    ngx_http_core_main_conf_t  *cmcf; 552 553    cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); 554 555    v = ngx_hash_find(&cmcf->variables_hash, key, name->data, name->len); 556
  1. nginx 变量的保存结构
    前面已经猜到nginx 的变量是保存在hash表中的, 主要的结构保存在ngx_hash.c 中
    这里主要分析两个函数ngx_hash_find 和 ngx_hash_init

ngx_hash_find 就是典型的拉链式hash表的处理方式,先定位bucket, 然后挨个对比,那么效率就在于hash bucket的构建上了

12-void *  13ngx_hash_find(ngx_hash_t *hash, ngx_uint_t key, u_char *name, size_t len)  14{  15    ngx_uint_t       i;  16    ngx_hash_elt_t  *elt;  17  18#if 0  19    ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0, "hf:\"%*s\"", len, name);  20#endif  21  22    elt = hash->buckets[key % hash->size];  23  24    if (elt == NULL) {  25        return NULL;  26    }  27      28    while (elt->value) {  29          30        if (len != (size_t) elt->len) {  31            goto next;  32        }  33  34        for (i = 0; i < len; i++) {  35            if (name[i] != elt->name[i]) {  36                goto next;  37            }  38        }  39          40        return elt->value;  41  42    next:  43  44        elt = (ngx_hash_elt_t *) ngx_align_ptr(&elt->name[0] + elt->len,  45                                               sizeof(void *));  46        continue;  47    }  48      49    return NULL;  50}

下面我们来看一下 hash bucket的构建

// 用于计算存储一个变量所需要的空间249#define NGX_HASH_ELT_SIZE(name)                                               \ 250    (sizeof(void *) + ngx_align((name)->key.len + 2, sizeof(void *))) 251 252-ngx_int_t 253ngx_hash_init(ngx_hash_init_t *hinit, ngx_hash_key_t *names, ngx_uint_t nelts) 254{

NGX_HASH_ELT_SIZE 是计算每个key 所占用的空间
每一个bucket 中key的保存如下
ngx_hash_elt_t

value len name point to ngx_http_core_srv_conf_t key.len(short) key.data NULL(队尾) key.len(short) key.data

因为key.len 是short 类型的所以需要在+2 然后对齐

nginx 对于变量的保存是存在hash 表中, 在配置中有两个参数
server_names_hash_bucket_size 每个bucket 的大小,决定着每个bucket 最多能放入多少个对象
server_names_hash_max_size 最大的bucket 数量
nginx 用拿现有的变量列表从一个hash size开始尝试, 及 尝试把所有符合条件 hash(key)%size 的key放到一个bucket 中,不过不能则增大size 直到 max_size,让然不行就会报错。
这中间就会有两个问题
1 这种尝试会使nginx 启动变慢
2 如果bucket size 过大会使太多的单个bucket 中存放了太多的对象导致,在运行中获取变量时的开销变大,相应的bucket 太小又会使size 很大消耗更多的内存
nginx 在这方面是选择了在参数内尽可能的减少内存的使用,但是在实际环境中增大内存来加快变量的访问时非常可取的也是。

 252-ngx_int_t 253ngx_hash_init(ngx_hash_init_t *hinit, ngx_hash_key_t *names, ngx_uint_t nelts) { ...  // 1 首先check 一下当前bucekt size 是不是能放下所有的对象  270    for (n = 0; n < nelts; n++) { 271        ngx_uint_t var_size = NGX_HASH_ELT_SIZE(&names[n]) + sizeof(void *); 272        if (hinit->bucket_size < NGX_HASH_ELT_SIZE(&names[n]) + sizeof(void *)) 273        { 274            ngx_log_error(NGX_LOG_EMERG, hinit->pool->log, 0, 275                          "could not build %s, you should " 276                          "increase %s_bucket_size: %i", 277                          hinit->name, hinit->name, hinit->bucket_size); 278            return NGX_ERROR; 279        } 280         283        if (NULL == names[n].key.data){ 284            ngx_log_error(NGX_LOG_EMERG, hinit->pool->log, 0, "ngx_hash_init error var %i", n); 285            continue; 286        }        }       ...       // 接下来开始, 顺序尝试,看能否确定一个 size 后, 看能不能把符合hash%size的key 放入同一个桶中, 如果不能则继续增大size 直至max_size 311    if (hinit->max_size > 10000 && nelts && hinit->max_size / nelts < 100) { 312        start = hinit->max_size - 1000; 313    } 314    ngx_log_error(NGX_LOG_EMERG, hinit->pool->log, 0, 315                          "ngx_hash_init start confirm size... " 316                          "name %s start(%i) max_size(%i)", 317                      hinit->name, start, hinit->max_size); 318    if (hinit->max_size < 20*nelts) 319        hinit->max_size = 20*nelts; 320    for (size = start; size <= hinit->max_size; size++) { 321 322        ngx_memzero(test, size * sizeof(u_short)); 323 324        for (n = 0; n < nelts; n++) { 325            if (names[n].key.data == NULL) { 326                continue; 327            } 328 329            key = names[n].key_hash % size; 330            test[key] = (u_short) (test[key] + NGX_HASH_ELT_SIZE(&names[n])); 331 332#if 0 333            ngx_log_error(NGX_LOG_ALERT, hinit->pool->log, 0, 334                          "%ui: %ui %ui \"%V\"", 335                          size, key, test[key], &names[n].key); 336#endif 337 338            if (test[key] > (u_short) bucket_size) { 339                goto next; 340            } 341        } 342        ngx_log_error(NGX_LOG_EMERG, hinit->pool->log, 0, 343                          "ngx_hash_init confirm size " 344                          "name %s bucket_cnt(%i) var_cnt(%i) bucket_size(%i) max_size(%i)", 345                      hinit->name, size, nelts, hinit->bucket_size, hinit->max_size); 346        goto found; 347 348    next: 349 350        continue; 351    } 352 353    size = hinit->max_size; 354 355    ngx_log_error(NGX_LOG_WARN, hinit->pool->log, 0, 356                  "could not build optimal %s, you should increase " 357                  "either %s_max_size: %i or %s_bucket_size: %i; " 358                  "ignoring %s_bucket_size", 359                  hinit->name, hinit->name, hinit->max_size, 360                  hinit->name, hinit->bucket_size, hinit->name); 361 362found: 364    for (i = 0; i < size; i++) {      // 每一个bucket 都至少有一个元素,没有value 365        test[i] = sizeof(void *); 366    }... // 确定了size 后面就开始根据元素来分配空间,创建hash表, 填充空间了 }

nginx 的hash 是静态表不能运行是添加或删除, 一种优化的方案是在构造hash表时根据所需要存的变量计算bucket size, 例如是平均值的几倍,这样避免业务变化后需要重新调整这参数,也避免了参数不匹配造成的性能问题。另一个就是在尝试size时 初始尝试一个较大值, 毕竟size 过小也会使单个bucket 中的元素过多影响访问性能,同事也能尽快的找到合适的size 加速度nginx 启动。
后续的填充逻辑有机会再详细分析。

原创粉丝点击