nginx 变量的保存结构和优化

本文深入探讨了Nginx中变量的获取与保存机制,分析了ngx_lua模块如何通过转换变量名为小写并计算hash值来高效获取变量。同时,文章详细解释了nginx内部hash表的工作原理,包括hashbucket的构建过程及其对变量访问性能的影响。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在使用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);
112
113    lowcase = lua_newuserdata(L, len);
114
115    hash = ngx_hash_strlow(lowcase, p, len);
116
117    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

valuelenname
point to ngx_http_core_srv_conf_tkey.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 启动。
后续的填充逻辑有机会再详细分析。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值