我眼中的 Nginx(三):Nginx 变量和变量插值

张超:又拍云系统开发高级工程师,负责又拍云?CDN?平台相关组件的更新及维护。Github?ID:?tokers,活跃于?OpenResty?社区和?Nginx?邮件列表等开源社区,专注于服务端技术的研究;曾为?ngx_lua?贡献源码,在?Nginx、ngx_lua、CDN?性能优化、日志优化方面有较为深入的研究。
如果读者曾配置过 Nginx,那么一定知道 Nginx 允许我们在配置文件里嵌入”变量”,这些变量由 Nginx 的各个模块定义,其目的是为了提升配置的灵活性,如这一段配置:
location = /t {
set $my_addr "127.0.0.1:8081";
proxy_pass http://$my_addr/index.html;
}
我们可以通过操作变量?$my_addr?来动态指定 upstream。
认识 Nginx 变量
Nginx 的变量和 perl、php 等语言的类似,由美元符号?$?开头,随后跟着一个字符串,代表这个变量的名称,例如?$name,可选地,这个字符串可以用花括号包围,譬如?${name}?。在 Nginx 世界里,合法的变量名可用字符集为?[a-zA-Z0-9_],特别地,Nginx 支持正则子组,即?$1,$2?这样的变量。注意变量值只有字符串这一种类型。
另外还存在一类特殊的变量,它们的名称是不固定的,更确切地说,它们描述的是一群变量。譬如?$http_,描述对应请求的请求头;$sent_http?则描述对应请求的响应头。
在实现上,一个变量由其名称、读/取处理程序和一些标志位组成。这些标志位定义了变量的属性,例如表明一个变量是否可缓存、是否可进行索引。
变量拥有两种存放方式,第一种是储存在一个全局的 hash 表里,使用该变量时需要进行查询;第二种则是储存在一个全局动态数组里,每个变量存在一个唯一的索引。相比较而言,第二种方式在性能上更加优秀,因为它不需要计算 hash key,也不需要查找 hash 表。
变量解析是运行时的。即惰性求值,而且通常变量解析总是和请求绑定的,因此变量信息虽然只有一份,但是变量值却会有多份。
前面提到,变量是可缓存的,Nginx 模块开发者们可以视情况决定变量的可缓存性,如果一个变量设定为可缓存,则它在经历第一次求值后,其求值结果会被缓存起来,后续使用到该变量时会直接获取到缓存里的值。譬如,Nginx 的 map 模块就把变量设置为可缓存,因为在它看来这样的一次变量获取操作是足够昂贵的。
谨慎对待变量缓存。笔者曾遇到过一个因为变量缓存引起的问题:?$host?这个变量,该变量也是可缓存的。根据文档里的描述,这个变量的取值顺序为:
in this order of precedence: host name from the request line, or host name from the “Host” request header field, or the server name matching a request
通常情况下,我们可以使用?$host?获取到请求头里的?Host,然而因为它的可缓存性,该变量无法体现?Host?头部的改动,比如下面的代码:
local old_host = ngx.var.host
ngx.req.set_header("Host", "foo.example.com")
local new_host = ngx.var.host
new_host的值将和?old_host?一样,而非直觉上所想的?foo.example.com。
另外,在原生 Nginx 的实现里,主/子请求共享同一份变量缓存。如果子请求使用了某个变量,并且该变量值被缓存下来,那么主请求之后访问该变量时,就会得到子请求所产生的缓存拷贝。当然,如果模块开发者自己定义子请求的创建,也是可以绕过这一点的。譬如?ngx_lua?在创建子请求的?API?里就提供了是否指定主/子请求共享变量的选项。
变量插值
用户的需求总是多变的。譬如在使用 upstreamcache 功能(另外一个典型的例子就是?access_log)时,通常需要设计一个良好的缓存 key,此时需要考虑到的因素可能有多个,即我们的 key 不会单单由一个变量或者常量组成,而是需要设计成它们的结合体,如?mykey=$http_host&$uri&$args。
在实现上,Nginx 首先会把包含变量和常量的复杂字符串转换成一种中间形式。具体来说,Nginx 会把这个字符串按变量和常量进行拆分,分别为它们进行“包装”(包括设置好对应的取值回调函数),之后按顺序放入一个动态数组,这个动态数组就是中间形式了。
比如上段提到的缓存 key,经过处理后,得到的动态数组是这样的:

拆分的处理是在具体指令解析阶段完成的。真正取值发生在具体请求的运行当中,通过执行该动态数组每个成员的取值回调函数,将单一的值拼装在一起,最终就实现了变量插值。
发散
借鉴 Nginx 的变量插值,笔者不久前用 Golang 实现了一个小玩意(tokers/corgi),当然,还是为了好玩 :)。
变量插值的思想非常值得学习,如果能利用好这一点,程序设计上就会变得更加灵活。
《我眼中的 Nginx》系列:
我眼中的 Nginx(一):Nginx 和位运算
我眼中的 Nginx(二):HTTP/2 dynamic table size update
快 来 找 又 小 拍

推?荐 阅 读
关于技术
这样介绍 CDN,老司机也能听懂
聊聊常见的网络攻击
5G 网络与 4G 相比,有什么区别?
?大家好看才是真的好看??
给个「好看」呗?
关注公众号:拾黑(shiheibook)了解更多
[广告]赞助链接:
四季很好,只要有你,文娱排行榜:https://www.yaopaiming.com/
让资讯触达的更精准有趣:https://www.0xu.cn/
关注网络尖刀微信公众号随时掌握互联网精彩
- 1 习近平将发表二〇二六年新年贺词 7904141
- 2 2026年国补政策来了 7808738
- 3 东部战区:开火!开火!全部命中! 7712893
- 4 2026年这些民生政策将惠及百姓 7616985
- 5 小学食堂米线过期2.5小时被罚5万 7519709
- 6 解放军喊话驱离台军 原声曝光 7428214
- 7 为博流量直播踩烈士陵墓?绝不姑息 7327605
- 8 每月最高800元!多地发放养老消费券 7238391
- 9 数字人民币升级 1月1日起将计付利息 7141831
- 10 2026年1月1日起 一批新规将施行 7040675








又拍云
