张超:又拍云系统开发高级工程师,负责又拍云 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 一样,而非直觉上所想的 http://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 变量和变量插值的更多相关文章

  1. 快速掌握Nginx(三) —— Nginx+Systemd托管netcore应用

    以前dotnet web应用程序开发完成后,我们都是使用IIS部署在Windows Server上,如今netcore技术发展迅速,因为其跨平台的特性,将dotnet web应用程序部署在更方便部署和 ...

  2. Nginx(三):日志文件管理

    一.Nginx日志描述 通过访问日志,你可以得到用户地域来源.跳转来源.使用终端.某个URL访问量等相关信息: 通过错误日志,你可以得到系统某个服务或server的性能瓶颈等.因此,将日志好好利用,你 ...

  3. linux 安装nginx -查看 linux的环境变量

    我发现在linux上面安装linux很简单 在CentOS release 6.5 上面先看一下操作系统的版本: lsb_release -a 直接执行 yum install nginx 系统自动的 ...

  4. Nginx教程(三) Nginx日志管理

    Nginx教程(三) Nginx日志管理 1 日志管理 1.1 Nginx日志描述 通过访问日志,你可以得到用户地域来源.跳转来源.使用终端.某个URL访问量等相关信息:通过错误日志,你可以得到系统某 ...

  5. nginx(三)反向代理和负载均衡

    nginx(三)反向代理和负载均衡 正向代理概念:比如在学校要上网,在学校内网是一个内网ip,需要连上公网就需要一个正向代理服务器. 反向代理概念: 看下图(Nginx只做请求的转发,后台有多个htt ...

  6. 【nginx】nginx配置文件结构,内置变量及参数调优

    Nginx的配置文件是一个纯文本文件,它一般位于Nginx安装目录的conf目录下,整个配置文件是以block的形式组织的.每个block一般以一个大括号“{”来表示.block 可以分为几个层次,整 ...

  7. 使用正则表达式来截取nginx中的内置变量

    nginx 中的内置变量都可以通过 if 指令 + 正则表达式来进行截取,截取之后的结果通过正则表达式的分组来进行引用 比如:从请求中传过来的一个名为 ssl_client_s_dn 的变量,它的值是 ...

  8. Nginx三种模式的虚拟主机(附Apache基于域名的虚拟主机)

    1.安装nginx # pcre中文"perl兼容正则表达式",安装pcre库是为了让nginx支持具备URL重写功能 # 的Rewrite模块,rewrite可以实现动态页面转成 ...

  9. nginx多层反代配置变量proxy_set_header

    Nginx多层反代配置变量proxy_set_header过程记录 第一层代理: (1)路径: $ vim /data/soft/nginx/conf/vhost/xixi.conf (2)内容:(注 ...

随机推荐

  1. 使用Swashbuckle构建RESTful风格文档

    本次和大家分享的是Swagger to WebApi的nuget包Swashbuckle:因为项目需要统一api文档的风格,并要支持多种开发语言(C#,java,python),所以首先想到的是swa ...

  2. error LNK2038: 检测到“RuntimeLibrary”的不匹配项: 值“MTd_StaticDebug”不匹配值“MDd_DynamicDebug

    属性1. 在工程上右键->属性->c/c++->代码生成->运行库 四个选项及含义分别如下: 1.1 /MDd:MD_DynamicDebug,我理解是 "共享DLL ...

  3. ArcCore重构-头文件引用问题的初步解决

    基于官方arc-stable-9c57d86f66be,AUTOSAR版本3.1.5   基本问题 1. 头文件引用混乱,所有头文件通过从搜索路径(-I)中引用,存在名称污染问题,需加入路径信息:   ...

  4. margin-right没有效果的问题

    margin-right其实有效果的,只是在默认即标准流的情况的下显示不出来效果.如果脱离标准流呢?想到这个,就立马在css文件中加了一个:float:right;然后在测试的时候就能看到margin ...

  5. kibana-Request Timeout after 30000ms故障解决

    etc在日志系统搭建起来后大半年一直没有出现大的问题,在上个月的某段时间,我慢慢发现有这个问题的存在了,首先是自己遇到过,后面也有人反应这个问题.于是就开始对这个问题进行分析: 1.因为服务器是放在国 ...

  6. JAVA小记 (1)

    JVM: Java虚拟机  JVM个数取决于同时执行的程序个数 JDK:JAVA 开发工具包 Java利用JVM实行跨平台 JRE:Java运行环境 JavaSE:企业版 GC:垃圾回收机制 命名规范 ...

  7. git merge与 git rebase区别及实例

    接Git分支创建与合并,在分支合并时,有两种方式:git merge 和git rebase. git merge:将两个分支,合并提交为一个新提交,并且新提交有2个parent. git rebas ...

  8. js算法初窥01(排序算法01-冒泡、选择、插入)

    排序,我想大家一定经历过或者正在经历着.或许你不懂算法,对排序算法一无所知,但是你一定用过一些第三方库的api来一键排序,那么,在你享受便捷的同时,你是否想过它的底层是如何实现的?这样的算法实现方式是 ...

  9. java 回调函数解读

    模块间调用 在一个应用系统中,无论使用何种语言开发,必然存在模块之间的调用,调用的方式分为几种: (1)同步调用 同步调用是最基本并且最简单的一种调用方式,类A的方法a()调用类B的方法b(),一直等 ...

  10. JSTL varStatus属性

    JSTL核心标签库中c:forEach 的 varStatus属性 varStatus属性  类型:String   描述:循环的状态信息,可以取值index\count\first\last\cur ...