在设置了“取处理程序”的情况下,Nginx 变量也可以选择将其值容器用作缓存,这样在多次读取变量的时候,就只需要调用“取处理程序”计算一次。我们下面就来看一个这样的例子:

    map $args $foo {
        default     0;
        debug       1;
    }
 
    server {
        listen 8080;
 
        location /test {
            set $orig_foo $foo;
            set $args debug;
 
            echo "orginal foo: $orig_foo";
            echo "foo: $foo";
        }
    }

这里首次用到了标准 ngx_map 模块的 map 配置指令,我们有必要在此介绍一下。map 在英文中除了“地图”之外,也有“映射”的意思。比方说,中学数学里讲的“函数”就是一种“映射”。而 Nginx 的这个 map 指令就可以用于定义两个 Nginx 变量之间的映射关系,或者说是函数关系。回到上面这个例子,我们用 map 指令定义了用户变量 $foo 与 $args 内建变量之间的映射关系。特别地,用数学上的函数记法 y = f(x) 来说,我们的$args 就是“自变量” x,而 $foo 则是“因变量” y,即 $foo 的值是由 $args 的值来决定的,或者按照书写顺序可以说,我们将 $args 变量的值映射到了 $foo 变量上。

现在我们再来看 map 指令定义的映射规则:

    map $args $foo {
        default     0;
        debug       1;
    }

花括号中第一行的 default 是一个特殊的匹配条件,即当其他条件都不匹配的时候,这个条件才匹配。当这个默认条件匹配时,就把“因变量” $foo 映射到值 0. 而花括号中第二行的意思是说,如果“自变量” $args精确匹配了 debug 这个字符串,则把“因变量” $foo 映射到值 1. 将这两行合起来,我们就得到如下完整的映射规则:当 $args 的值等于 debug 的时候,$foo 变量的值就是 1,否则 $foo 的值就为 0.

明白了 map 指令的含义,再来看 location /test. 在那里,我们先把当前 $foo 变量的值保存在另一个用户变量 $orig_foo 中,然后再强行把 $args 的值改写为 debug,最后我们再用 echo 指令分别输出 $orig_foo和 $foo 的值。

从逻辑上看,似乎当我们强行改写 $args 的值为 debug 之后,根据先前的 map 映射规则,$foo 变量此时的值应当自动调整为字符串 1, 而不论 $foo 原先的值是怎样的。然而测试结果并非如此:

    $ curl 'http://localhost:8080/test'
    original foo: 0
    foo: 0

第一行输出指示 $orig_foo 的值为 0,这正是我们期望的:上面这个请求并没有提供 URL 参数串,于是 $args最初的取值就是空,再根据我们先前定义的映射规则,$foo 变量在第一次被读取时的值就应当是 0(即匹配默认的那个 default 条件)。

而第二行输出显示,在强行改写 $args 变量的值为字符串 debug 之后,$foo 的条件仍然是 0 ,这显然不符合映射规则,因为当 $args 为 debug 时,$foo 的值应当是 1. 这究竟是为什么呢?

其实原因很简单,那就是 $foo 变量在第一次读取时,根据映射规则计算出的值被缓存住了。刚才我们说过,Nginx 模块可以为其创建的变量选择使用值容器,作为其“取处理程序”计算结果的缓存。显然,ngx_map模块认为变量间的映射计算足够昂贵,需要自动将因变量的计算结果缓存下来,这样在当前请求的处理过程中如果再次读取这个因变量,Nginx 就可以直接返回缓存住的结果,而不再调用该变量的“取处理程序”再行计算了。

为了进一步验证这一点,我们不妨在请求中直接指定 URL 参数串为 debug:

    $ curl 'http://localhost:8080/test?debug'
    original foo: 1
    foo: 1

我们看到,现在 $orig_foo 的值就成了 1,因为变量 $foo 在第一次被读取时,自变量 $args 的值就是debug,于是按照映射规则,“取处理程序”计算返回的值便是 1. 而后续再读取 $foo 的值时,就总是得到被缓存住的 1 这个结果,而不论 $args 后来变成什么样了。

map 指令其实是一个比较特殊的例子,因为它可以为用户变量注册“取处理程序”,而且用户可以自己定义这个“取处理程序”的计算规则。当然,此规则在这里被限定为与另一个变量的映射关系。同时,也并非所有使用了“取处理程序”的变量都会缓存结果,例如我们前面在 (三) 中已经看到 $arg_XXX 并不会使用值容器进行缓存。

类似 ngx_map 模块,标准的 ngx_geo 等模块也一样使用了变量值的缓存机制。

在上面的例子中,我们还应当注意到 map 指令是在 server 配置块之外,也就是在最外围的 http 配置块中定义的。很多读者可能会对此感到奇怪,毕竟我们只是在 location /test 中用到了它。这倒不是因为我们不想把 map 语句直接挪到 location 配置块中,而是因为 map 指令只能在 http 块中使用!

很多 Nginx 新手都会担心如此“全局”范围的 map 设置会让访问所有虚拟主机的所有 location 接口的请求都执行一遍变量值的映射计算,然而事实并非如此。前面我们已经了解到 map 配置指令的工作原理是为用户变量注册 “取处理程序”,并且实际的映射计算是在“取处理程序”中完成的,而“取处理程序”只有在该用户变量被实际读取时才会执行(当然,因为缓存的存在,只在请求生命期中的第一次读取中才被执行),所以对于那些根本没有用到相关变量的请求来说,就根本不会执行任何的无用计算。

这种只在实际使用对象时才计算对象值的技术,在计算领域被称为“惰性求值”(lazy evaluation)。提供“惰性求值” 语义的编程语言并不多见,最经典的例子便是 Haskell. 与之相对的便是“主动求值” (eager evaluation)。我们有幸在 Nginx 中也看到了“惰性求值”的例子,但“主动求值”语义其实在 Nginx 里面更为常见,例如下面这行再普通不过的 set 语句:

    set $b "$a,$a";

这里会在执行 set 规定的赋值操作时,“主动”地计算出变量 $b 的值,而不会将该求值计算延缓到变量 $b实际被读取的时候。

(未完待续)

Nginx 变量漫谈(四)的更多相关文章

  1. Nginx 变量漫谈(八)

    与 $arg_XXX 类似,我们在 (二) 中提到过的内建变量 $cookie_XXX 变量也会在名为 XXX 的 cookie 不存在时返回特殊值“没找到”:     location /test  ...

  2. Nginx 变量漫谈(七)

    在 (一) 中我们提到过,Nginx 变量的值只有一种类型,那就是字符串,但是变量也有可能压根就不存在有意义的值.没有值的变量也有两种特殊的值:一种是“不合法”(invalid),另一种是“没找到”( ...

  3. Nginx 变量漫谈(五)

    前面在 (二) 中我们已经了解到变量值容器的生命期是与请求绑定的,但是我当时有意避开了“请求”的正式定义.大家应当一直默认这里的“请求”都是指客户端发起的 HTTP 请求.其实在 Nginx 世界里有 ...

  4. Nginx 变量漫谈(三)

    也有一些内建变量是支持改写的,其中一个例子是 $args. 这个变量在读取时返回当前请求的 URL 参数串(即请求 URL 中问号后面的部分,如果有的话 ),而在赋值时可以直接修改参数串.我们来看一个 ...

  5. Nginx 变量漫谈(二)

    关于 Nginx 变量的另一个常见误区是认为变量容器的生命期,是与 location 配置块绑定的.其实不然.我们来看一个涉及“内部跳转”的例子:     server {        listen ...

  6. Nginx 变量漫谈(一)

    Nginx 的配置文件使用的就是一门微型的编程语言,许多真实世界里的 Nginx 配置文件其实就是一个一个的小程序.当然,是不是“图灵完全的”暂且不论,至少据我观察,它在设计上受 Perl 和 Bou ...

  7. Nginx 变量漫谈

    转自:http://blog.sina.com.cn/openrestyNginx 的配置文件使用的就是一门微型的编程语言,许多真实世界里的 Nginx 配置文件其实就是一个一个的小程序.当然,是不是 ...

  8. Nginx 变量漫谈(六)

    Nginx 内建变量用在“子请求”的上下文中时,其行为也会变得有些微妙. 前面在 (三) 中我们已经知道,许多内建变量都不是简单的“存放值的容器”,它们一般会通过注册“存取处理程序”来表现得与众不同, ...

  9. Alink漫谈(四) : 模型的来龙去脉

    Alink漫谈(四) : 模型的来龙去脉 目录 Alink漫谈(四) : 模型的来龙去脉 0x00 摘要 0x01 模型 1.1 模型包含内容 1.2 Alink的模型文件 0x02 流程图 0x03 ...

随机推荐

  1. Java中操作时间比较好用的类

    项目中经常用到日期的操作,包括日期的格式化.下面是几个比较常用的工具类. import java.text.SimpleDateFormat; import java.util.Date; impor ...

  2. 编程修养-C语言篇(二)

    1.版权和版本——————— 好的程序员会给自己的每个函数,每个文件,都注上版权和版本.  对于C/C++的文件,文件头应该有类似这样的注释: /*************************** ...

  3. HTML骨架-深入理解

    HTML是WEB开发最基本的语言之一,也是最重要的语言之一,我们在浏览网页时做看到的内容是最直接的呈现形式就是HTML代码.<!DOCTYPE html PUBLIC "-//W3C/ ...

  4. #1042 - Can't get hostname for your address

    my.ini 或 my.cnf 末尾添加 skip-name-resolve 并重启MySQL服务器 ok!

  5. C++静态成员函数不能调用非静态成员变量

    其实我们从直观上可以很好的理解静态成员函数不能调用非静态成员变量这句话因为无论是静态成员函数还是静态成员变量,它们 都是在类的范畴之类的,及在类的整个生存周期里始终只能存在一份.然而非静态成员变量和非 ...

  6. HDU1232 畅通工程 (并查集模板题)

    畅通工程 Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)Total Submis ...

  7. yum安装配置mongoDB客户端和服务器端

    1,Centos6.X yum安装mongoDB客户端和服务器端; yum -y install mongodb mongodb-server; 基于epel repo.当前的mongoDB的版本为2 ...

  8. android 分享到新浪微博

    分享到新浪微博,折腾了大半个月,现在终于弄出来了,心里的那个爽呀,太痛快了,哈哈!! 废话少说,首先是认证, 1.进入新浪微博提供的开放平台注册新浪账号. 2.点击’我是开发者‘,创建一个应用,得到C ...

  9. java Socket 使用注意

    Socket s = new Socket(ia, port); BufferedOutputStream bufOut = new BufferedOutputStream(s.getOutputS ...

  10. 树的直径 poj 2631

    树的直径:从随意一点出发,BFS找到最远的距离,然后在从该点出发BFS找到最远的距离 #include <iostream> #include <algorithm> #inc ...