vlambda博客
学习文章列表

Nginx 变量和脚本引擎实现原理

变量使用场景

nginx.conf 配置文件中使用美元符号 $ 定义和使用变量,如下示例:

log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $server_name "$http_user_agent"';
set $name ${host}abc;

本文讲述以上两个示例中两种变量的工作原理,并且该工作原理基于 nginx 作为 HTTP 服务器的使用场景。


内置变量实现原理

内置变量为 nginx 内部实现的变量,所有内置变量可参考 nginx 官方文档:
http://nginx.org/en/docs/varindex.html。
以上示例中log_format main 中使用的变量均为内置变量。

变量类型

内置变量分为两种,一种可以在 nginx 的配置文件中使用的变量,一种不可以在 nginx 的配置文件中使用,即 nginx 模块定义且仅在模块之间使用的变量。

可以在 nginx 的配置文件中使用的变量又可分为两种,一种是名称固定的内置变量,如 $remote_addr,另一种是前缀变量,如获取请求头部的 $http_变量,$http_user_agent,表示请求头域 User-Agent 的头域值。

其中 HTTP 前缀变量可分为五种,说明如下:

变量前缀
说明
示例
arg_ 用户请求 url 参数 $arg_user
http_ 用户请求头域 $http_x_real_ip
send_http_ nginx 响应头域 $send_http_server
cookie_ 请求 cookie 中的某个值 $cookie_sessionid
upstream_http_ 上游服务器响应头域 $upstream_http_date

内部变量分类图示如下:

原理说明

  1. nginx 中的每个模块定义自己的内置变量,包括变量名、获得变量值的函数(get_handler)、设置变量值的函数(set_handler)等

  2. 一般在 nginx 进行配置解析之前(preconfiguration)将当前模块的所有内置变量根据变量类型添加到全局数组中,如将前缀变量添加到全局数组 prefix_variables中;名称固定的变量添加到全局数组 variables 中,并将其保存在全局哈希数组 variables_keys

  3. 在所有 HTTP 模块配置解析完成之后,根据全局哈希数组 variables_keys 构造全局哈希表 variables_hash(此时哈希数组 variables_keys 就功成身退了,后面不会再使用)

  4. 当每个 HTTP 请求到来时,nginx 都会创建一个 ngx_http_request_t *r, r->variables 数组,该数组下标和上述步骤2中的全局数组 variables 下标是一一对应的。这样在获得变量值之后,可以将变量的值缓存在该数组中,下次获得相同变量的值时,可以直接从该请求级别的数组中获得

根据以上数据结构,可以看到若要获得一个变量取值,有两种方法:
1) 根据变量名称通过全局哈希表 variables_hash 查找
2) 根据变量索引通过全局数组 variables 查找

Q&A

如何获得变量的索引呢?

nginx 内部提供可以根据变量名称获得变量索引的函数 ngx_http_get_variable_index(对于仅在 nginx 模块之间使用,不对外提供的变量,通常不会将变量保存在全局哈希表中,只能通过索引的方式获得变量值,可以使用该函数获得变量索引,然后利用索引获得变量值)。

内置变量是否进行惰性求值?

是惰性求值。变量只有在使用时才会进行求值,而不是 HTTP 请求创建时,对所有变量进行求值。

内置变量值如何进行缓存?

当获得内置变量值之后,会将变量取值保存在ngx_http_request_t *rvariables 数组中,这样下次想要再次获得相应变量值时,会首先从ngx_http_request_t *rvariables 数组中索引变量的值,提高变量求值的执行效率。

内存布局

内存布局如下:

Nginx 变量和脚本引擎实现原理

源码索引

  • 模块定义的变量散落在模块文件中,如 ngx_http_realip_module 模块添加变量的函数为 ngx_http_realip_module.c/ngx_http_realip_add_variables

  • nginx_http_core_module 模块会被首先调用,它的内置变量源码位置为 ngx_http_core_module.c/ngx_http_core_preconfiguration

  • ngx_http_variables.c/ngx_http_get_variable_index 函数根据变量名获得变量在全局变量 variables 中的索引

  • ngx_http_variables.c/ngx_http_get_indexed_variable  函数根据变量索引从全局数组 variable 中获得变量值

  • ngx_http_variables.c/ngx_http_get_variable 函数根据变量名从全局哈希表 variables_hash 中获得变量值


外部变量实现原理

外部变量是指通过 ngx_http_rewrite_module 模块中提供的 set 指令定义的变量,由用户在 nginx 配置文件中设置。
set 指令使用方法官方文档:
http://nginx.org/en/docs/http/ngx_http_rewrite_module.html#set
更为详细说明,可参考春哥(agentzh 章亦春)的文章:
https://openresty.org/download/agentzh-nginx-tutorials-en.html

原理说明

这里以 set $name ${host}abc; 为例,讲述自定义变量的实现原理。

  1. 将变量名 name 作为 key,变量值相关信息作为 value,保存在全局哈希数组 variables_keys

  2. 将变量名 name 保存在全局数组 variables

  3. 编译变量值:将获得变量值相关的函数保存在栈/数组中,数据结构如下:

    4. 执行变量值:当需要使用变量 name 时,执行栈中的函数,获得变量取值
        1) 调用左边数组中的元素0,该函数的实现为依次调用右边函数栈中所有函数,获得变量值的总长度,并且申请内存,用于保存变量值

        2) 依次调用左边函数栈的元素1和元素2,获得变量取值,保存在步骤1)申请的内存中

        3) 调用左边函数栈的元素3,将变量值缓存在

ngx_http_request_t *r 的 variables 数组中

Q&A

外部变量是否进行惰性求值?

以上步骤1.2.3在 nginx 启动时执行,步骤4在每个请求处理时,且执行到对应的配置上下文(对应配置文件中的 server、location)时才会对变量进行求值,可以将其理解为惰性求值。

执行的变量值如何缓存?

步骤4在求值时,首先从ngx_http_request_t *rvariables 数组中索引变量的值,如果无值,则进行变量取值,并将变量取值缓存在ngx_http_request_t *rvariables 数组中。

源码索引

  • ngx_http_rewrite_module.c/ngx_http_rewrite_set 函数在nginx 解析配置文件,遇到set 指令时执行,该函数功能对应上述步骤1.2.3

  • 当执行请求时,在 rewrite 阶段调用 ngx_http_rewrite_module 模块的句柄 ngx_http_rewrite_handler 调用当前请求上下文的函数栈(上述步骤3中初始化的函数数组),对变量进行求值


往期文章