点赞
评论
收藏
分享
举报
NGINX脚本语言原理及源码分析(一)
发表于2021-03-15 19:06

浏览 3.4k

概述

NGINX本身提供了简单的脚本解析功能来提高配置使用的灵活性。与常用的语言一样,可以通过在NGINX配置文件中使用变量和指令来完成对NGINX的编程,来实现特定的功能。

简单变量使用例子:

location /test {

     if ($arg_name = “test”) {

         return 200 “welcome”;

    }

}

上面的配置完成的功能是,如果客户访问的服务器的/test URI并且携带的name参数值是test, 则给client返回状态码200并且输出“welcome”

上述简单的例子中,if对应的是指令,args_name对应的是变量。变量和指令也是任何编程语言必备的要素。通过使用脚本语言和变量,可以大大提高NGINX的灵活性,避免了通过大量添加新代码来支持新功能。

下面我们通过一系列的文章,来分析NGINX脚本语言是如何实现的。本篇文章,我们先介绍NGINX变量的基本特性。下一篇,我们继续分析变量的实现原理。最后我们将分析NGINX指令的实现原理以及变量是如何被使用的。

变量的定义

任何语言的变量都是用来存储和表示数据的。在定义变量时,我们首先需要给变量命名。比如在C语言中,我们通过int abc = 10;来定义一个整数类型的变量。其中int表示变量的类型,abc表示变量的名字。

关于定义变量的类型,在NGINX中所有的变量除了内置变量“${binary_remote_addr}”以外,剩余的变量都是字符串类型。变量“${binary_remote_addr}”根据IP的版本可是IPV4或者IPV6格式的二进制形式。

每种语言对于可以表示变量的字符要求不尽相同。在NGINX脚本语言中,变量的名字是使用“$”或“${}”符号来表示一个变量。可以用来定义变量的有效字符只有四种:“a-z”、“A-Z”、“0-9”、“_”。 比如 set $abc ‘abc’或者set ${abc} ‘abc’。 符号{}的意义在于,如果一个变量名需要和它相邻的字符进行区分时,需要显式地添加{}来实现。比如 变量${a}s $as${as}两个变量表示的意义是不一样的,前者表示在变量a的值后面再添加一个s字符。 $as${as}都表示变量as本身。

NGINX的变量支持变量插入,比如我们通过set $def this is a test $abc”定义变量$def。在进行赋值时,需要首先计算出变量abc的值然后再和另外的字符串连接,最后把连接后的字符串赋值给变量def。如果变量abc的值是“yeah!”,那么变量def的值就是 “this is a test yeah!”

变量的分类

NGINX变量可以分为内置变量和自定义变量两种。自定义变量是通过NGINX不同模块的进行显示定义。比如通过rewrite模块中的set指令可以如下定义: set $test “abc”;这个指令完成定义一个名为test的变量,并且在变量第一次被使用时把字符串abc赋值给test变量。还有比如geo模块也可以如下根据客户端的IP来定义一个新的变量。

geo $a  {

    default    “我是geo默认值”;

    127.0.0.1  “客户端ip127.0.0.1”;

}

除了自定义变量以外,NGINX还支持大量的内置变量。这些内置变量根据系统的层次模型,可以分为系统相关的变量比如$pid、网络三层相关的变量,比如$remote_addr、四层相关的变量,比如$remote_port、表示层(SSL,TLS)相关的变量,比如$http_ssl_vars以及七层HTTP,比如$url相关的变量。

内置变量又可以分为静态内置变量和动态内置变量。静态内置变量是在不同的模块中通过代码预先定义的。比如在NGX_HTTP_CORE_MODULAR里面就定义了大量的系统变量如$HOST,  $URI等。

所谓“动态”指的是变量的名字是不确定的,这个不确定性发生在NGINX的运行过程中。比如对于一个HTTP请求,同一个请求可以有不同的查询参数,而查询参数的不同又可以返回不同的结果,比如这个查询功能:

/query?name=jikui

/query?occurpation=coder

该查询功能有两个输入参数,一个是name,一个是occupation。当请求发生的时候,在NGINX内部肯定可以解析出所有的查询入参和对应的值,但是在配置文件中如何得到和使用呢? NGINX通过使用前缀的方式来表示HTTP模块中各种动态内置变量。分别使用arg_namearg_occurpation来表示其对应的变量。而arg_就是查询参数中某个入参的变量前缀。如此一来NGINX只需要在内部内置一个以arg_开头的规则就可以方便的表示这类参数相关数据了。

目前在NGINXhttp模块中有如下几种内置动态变量,分别是“http_”、“sent_http_”、“sent_trailer_”、“upstream_http_”、 “upstream_cookie_”、“cookie_”,“arg_”, upstream_trailer_”

以“http_”开头的动态内置变量可以表示http请求过程中的任意请求头,使用的过程中不区分大小写,并且请求头中如果有“-”字符需要用“_”字符替代。另外别的种类的动态内置变量也有相应的对应HTTP处理阶段。

变量的作用域

NGINX在启动时,会对变量进行静态检查,如果有指令使用未经定义的变量,NGINX启动会出错。并且打印如下的出错信息:“NGINX: [emerg] unknown "**" variable”

NGINX变量在配置文件中是全局可见的,基于此,在如下的配置中,虽然我们是在location /test2中定义的变量name,locaiton /test1中也进行了使用,但是这样在NGINX启动时是也不会出错。

        location /test1 {

               return 200 “I am $name”;

        }

        location /test2 {

             set $name“jikui”;

             return 200 “I am $name”;

         }

在实际的访问中,如果访问location test1返回的结果是 “I am”而访问location test2返回的结果是 “I am jikui”。这是因为,虽然静态的变量定义是全局可见的,但是对应每一个请求,都会有自己的一份变量的定义和数值。虽然对于不同的请求,都有各自不同的变量定义,但是在父子请求模型中,所有的子请求自动继承父请求所有的变量值。     

变量的可变性

根据变量是否可以被再次赋值,NGINX中的变量分为可变变量和不可变变量。在C语言中有特定的修饰符const用来描述这个变量的可变性。但是在NGINX中,并没有显著的修饰符来区分变量的可变性。只能从变量的实现代码实现中来判断某个变量是否可变。

具体来说,在定义NGINX的变量时都会打上一个是否可以被改变的标记,然后把这个变量放到一个容器中。当后续试图再次定义同一个变量时,NGINX会首先从这个容器中查找这个变量,如果找到相同的变量,再判断这个变量的可改变的标记。如果变量的可变标志是True,则会把容器中的变量覆盖掉,反之则返回错误并终止NGINX启动。具体的实现我们下篇变量实现中继续分析。

NGINX的内置变量要先于“set”或“geo”指令存放到系统中,如果某个内置变量,比如$host,被打上了不可改变的标记,后续其它指令就无法再定义相同名字的变量了。如果试图再次定义$host变量,则会出现如下错误:

nginx: [emerg] the duplicate "host" variable in / nginx.conf:45

目前NGINX的核心http模块中几乎所有静态内置变量都是不可改变的。只有“$args”和“$limit_rate”这两个内置变量可以被改变。另外由于http模块的动态内置变量并不会把自己放入到容器中,所以它是可以被改变。

比如:

location /name {

    set $arg_name “jikui”;

    return 200 “$arg_name”;

}

在访问location /name时,输出的是通过set指令设置的数值“jikui”而不是url参数中的“test”

curl http://127.0.0.1/name?name=test

jikui

这是因为在NGINX中,一自定义或内置变量不会被赋予动态变量的特性。比如例子中的“$arg_name”,通过set指令赋值后,其实已经变成了一个自定义变量,相应的动态变量特征也就不存在了。但在这个请求中,其它以“$arg_”开头的变量仍然是动态变量。  

变量的可缓存性

变量的可缓存性是指,在获取变量值时,是否需要每次都实时计算变量的值。对于不可缓存变量,获取数值时都是实时计算的。对于可缓存的变量,不需要每次都实时计算。

具体来说,NGINX中所有的变量在定义时都会被关联上一个get_handler()方法。所有变量在第一次获取值时,都是通过这个handler方法获取。后续再次获取变量值时,是否仍然调用该handler方法则取决于该变量是否可以被缓存。

比如$arg_”开头的动态变量,每次获取值时都会从查询参数中重新解析对应的值;而可以缓存的变量并不会每次都调用这个handler方法,在它的整个生命周期中,如果这个变量没有被刷新过,那么自始至终只会调用一次。

NGINX中用set指令定义的变量都是可以缓存的,但set指令不会改变已有变量的缓存特性而所有以arg_”开头的动态变量都是不可缓存的。这两种变量结合在一起的时候会产生一种有意思的现象,来看一个简单的例子:

比如:

location /url {

    set   $name   “$arg_name”;

    set   $args   “name=jikui”;

    return 200   “$name = $arg_name”;

}

访问url输出结果是:

curl http://127.0.0.1/url?name=test

test = jikui

这时候我们可以看到,$name”$arg_name”这两个变量虽然都是在表示入参name的值,但是却输出了不同的结果。

这其实就是变量是否可缓存的特性引起的,因为变量$name”是一个可缓存的变量,当被设置后变量值就被保存下来了;而$arg_name”是一个不可被缓存的变量,每次获取该值的时候都会调用其对应的handler方法。我们看到第一次调用的时候查询参数值是name=test”,这个值被赋值给了变量$name”,在第二次获取该变量值之前,我们把查询参数改成了name=jikui”,当它再次调用对应的handler方法的时候获取到的值就变成了jikui”

动态内置变量此时仍然是一个特殊的存在,我们之前说过,动态变量被重新定义后它就不再是动态变量了,所以它也就不再保有不可缓存的特性,看个例子就知道了:

location /a {

    set     $arg_name     “$arg_name”;

    set     $name     “$arg_name”;

    set     $args     “name=jikui”;

    return   200    “$name = $arg_name”;

}

访问location

curl http://127.0.0.1/a?name=test

test = test

可以看到这两个变量的值又一样了。原因是,在用set指令重新定义$arg_name”后,它就不再是动态变量了,它原本的不可缓存特性也就不存在了,所以此时查询参数的更改对他也就不起任何作用。具体原因我们可以通过下篇代码分析来看实际的实现原理。

变量的隔离性

NGINX中变量的隔离性类似于其它编程语言中变量的作用域。比如C语言中的局部变量和全局变量。而NGINX中的变量的作用域是基于请求的。同一个变量在不同的请求中毫无关系(子请求例外,子请求继承了父请求所有的变量),即A请求不会读到(或改变)B请求中的变量值,B也不会读到(改变)A的,比如下面一个例子:

server {

    set $name “$uri”;

    location /test1 {

        return 200 “I am $name”;

    }

    location /test2 {

        return 200 “I am $name”;

    }

}

我们在server块定义了一个看似是全局变量$name”如果它有全局性,那么访问上面的两个location的时候肯定会得到相同的值,但NGINX中不是这样的。

NGINX中两个location都可以看到这个变量$name”,这体现了NGINX变量的全局可见性;但两个location看到的变量值确实是不一样的,这体现了隔离性。上述配置的运行结果是:

curl http://127.0.0.1/test1

I am test1

curl http://127.0.0./test2

I am test2

在同一个请求中NGINX的变量是有全局性的,但仅限于当前请求中。不管变量的更改发生在配置文件的哪个位置,在同一个请求中都可以被看到,看下面一个例子:

server {

    set $name “server”;

    location / {

        set $name “location”;

        if ($uri) {

           set $name “if”;

        }

        return 200 “$name”;

    }

}

从上面的例子可以看到,变量$name”被更改了三次。上面的例子一定是输出字符串if”。从上面这个例子我们看到,NGINX 变量值容器的生命期是与当前正在处理的请求绑定的,而与 location 无关。

结语

本篇简单介绍了NGINX变量的一些特性。下篇我们将继续分析变量是如何实现的。然后我们还将通过分析NGINX的脚本语言的实现,来分析NGINX的变量是如何在系统中被使用的。


已修改于2023-03-09 02:04
本作品系原创
创作不易,留下一份鼓励
皮皮鲁

暂无个人介绍

关注



写下您的评论
发表评论
全部评论(2)

按点赞数排序

按时间排序

是的。多谢指正。

赞同

0

回复举报

发表于2022-10-05 21:51



回复皮皮鲁
回复

您好 应该是$arg_name = “test”吧

赞同

0

回复举报

发表于2022-01-13 18:04



回复ffashion
回复
关于作者
皮皮鲁
这家伙很懒还未留下介绍~
85
文章
2
问答
41
粉丝
相关文章
分布式系统提升可用性时,最有效的方案就是在空间维度上,将资源复制一份作为缓存,并把缓存放在离用户更近的地方。这样,通过缩短用户的访问路径,不只可以降低请求的时延,多份资源还能提升系统的健壮性。比如WEB服务中的CDN就是这样一个缓存系统。 Nginx由于具有下面3个特性,因此是最合适的缓存系统:l 首先,高并发、低延迟赋予了Nginx优秀的性能;l 其次,多进程架构让Nginx具备了很高的稳定性;l 最后,模块化的开源生态,以及从开放中诞生的Openresty、Kong等其他体系,这都让Nginx的功能丰富而强大。 所以,Nginx往往部署在企业最核心的边缘位置,在最外层的Nginx上部署共享缓存,能够给服务带来更大的收益,更短的访问路径带来了更佳的用户体验。然而,当整个系统的可用性极度依赖Nginx的缓存功能时,我们必须仔细地配置Nginx,还得使用到缓存的许多进阶功能。 比如,在超大流量下如果热点资源的缓存失效,那么在巨大的流量穿透Nginx缓存后,非常有可能把脆弱的上游服务打挂。此时合并回源请求功能,就是你的最佳应
点赞 12
浏览 2.5k
原文作者:AmirRawdatofF5原文链接:比较裸机和虚拟环境中的NGINX性能-NGINX转载来源:NGINX官方网站由于新冠疫情爆发,公有云的采用呈爆炸式增长的同时,企业也正积极拥抱混合云,即在公有云和本地(比如私有数据中心)同时运行工作负载。  这一混合方案使企业能够在最能满足其需求的环境中部署工作负载。例如,企业可以在本地环境中部署具有高度敏感数据的任务关键型工作负载,同时利用公有云运行仅需对核心网络基础架构进行有限访问的Web托管和边缘服务(如物联网)等工作负载。当在本地运行工作负载时,您可以进一步选择是在裸机环境还是在虚拟(管理程序)环境中运行。为了帮助您确定最出色、最经济的本地解决方案来满足您对性能和扩展性的需求,我们提供了一份选型指南,比较了NGINX在这两种环境中的性能。本文介绍了如何测试NGINX以得出选型指南中发布的数值。由于许多客户也将应用部署至Kubernetes,因此我们还在RancherKubernetesEngine(RKE)平台上分步测试了NGINXIngressController
点赞 1
浏览 1.3k
原文作者:NGINX中文社区官方团队 原文链接:完整会议议程:NGINXSprintChina2022年度线上大会 转载来源:NGINX官方网站 带上您的潜水服、调节器、潜水电脑表和水下摄像机,跟随我们在NGINXSprintChina2022年度线上会议期间,一起深潜到NGINX的斑斓世界吧! 12月1日,加入到一年一度的NGINXSprintChina2022线上大会中,了解最热的行业趋势以及NGINX的最新动态,并探索NGINX及其周边生态的使用案例、技术解析和运维实践。您还将有机会与NGINX官方团队、行业大咖以及社区中的开发者和技术爱好者共同探讨交流。 在此次深潜之旅期间,多位社区大咖和资深用户将与您分享他们的经验和见解,与您一同探索NGINX的无限可能。同时NGINX官方团队也将借此机会与社区进行深入交流,期待听到您的声音和反馈。 扫描上方海报二维码,立即免费注册NGINXSprintChina2022。您将通过邮件接收此次会议的相关信息,包括参会福利、会前提醒、参会链接、资
点赞 1
浏览 848