ngx_lua 模块提供了配置指令 access_by_lua,用于在 access 请求处理阶段插入用户 Lua 代码。这条指令运行于 access 阶段的末尾,因此总是在 allow 和 deny 这样的指令之后运行,虽然它们同属 access 阶段。一般我们通过 access_by_lua 在 ngx_access 这样的模块检查过客户端 IP 地址之后,再通过 Lua 代码执行一系列更为复杂的请求验证操作,比如实时查询数据库或者其他后端服务,以验证当前用户的身份或权限。

我们来看一个简单的例子,利用 access_by_lua 来实现 ngx_access 模块的 IP 地址过滤功能:

    location /hello {
        access_by_lua '
            if ngx.var.remote_addr == "127.0.0.1" then
                return
            end
 
            ngx.exit(403)
        ';
 
        echo "hello world";
    }

这里在 Lua 代码中通过引用 Nginx 标准的内建变量 $remote_addr 来获取字符串形式的客户端 IP 地址,然后用 Lua 的 if 语句判断是否为本机地址,即是否等于 127.0.0.1. 如果是本机地址,则直接利用 Lua 的return 语句返回,让 Nginx 继续执行后续的请求处理阶段(包括 echo 指令所处的 content 阶段);而如果不是本机地址,则通过 ngx_lua 模块提供的 Lua 函数 ngx.exit 中断当前的整个请求处理流程,直接返回 403错误页给客户端。

这个例子在功能上完全等价于先前在 (三) 中介绍过的那个使用 ngx_access 模块的例子:

    location /hello {
        allow 127.0.0.1;
        deny all;
 
        echo "hello world";
    }

虽然这两个例子在功能上完全相同,但在性能上还是有区别的,毕竟 ngx_access 是用纯 C 实现的专门化的 Nginx 模块。

下面我们不妨来实际测量一下这两个例子的性能差别。因为我们使用 Nginx 就是为了追求性能,而量化的性能比较,在工程上具有很大的现实意义,所以我们顺便介绍一下重要的测量技术。由于无论是 ngx_access 还是 ngx_lua 在进行 IP 地址验证方面的性能都非常之高,所以为了减少测量误差,我们希望能对 access 阶段的用时进行直接测量。为了做到这一点,传统的做法一般会涉及到修改 Nginx 源码,自己插入专门的计时代码和统计输出代码,抑或是重新编译 Nginx 以启用像 GNU gprof 这样专门的性能监测工具。

幸运的是,在新一点的 Solaris, Mac OS X, 以及 FreeBSD 等系统上存在一个叫做 dtrace 的工具,可以对任意的用户程序进行微观性能分析(以及行为分析),而无须对用户程序的源码进行修改或者对用户程序进行重新编译。因为 Mac OS X 10.5 以后就自带了 dtrace,所以为方便起见,下面在我的 MacBook Air 笔记本上演示一下这里的测量过程。

首先,在 Mac OS X 系统中打开一个命令行终端,在某一个文件目录下面创建一个名为 nginx-access-time.d 的文件,并编辑内容如下:

    #!/usr/bin/env dtrace -s
 
    pid$1::ngx_http_handler:entry
    {
        elapsed = 0;
    }
 
    pid$1::ngx_http_core_access_phase:entry
    {
        begin = timestamp;
    }
 
    pid$1::ngx_http_core_access_phase:return
    /begin > 0/
    {
        elapsed += timestamp - begin;
        begin = 0;
    }
 
    pid$1::ngx_http_finalize_request:return
    /elapsed > 0/
    {
        @elapsed = avg(elapsed);
        elapsed = 0;
    }

保存好此文件后,再赋予它可执行权限:

    $ chmod +x ./nginx-access-time.d

这个 .d 文件中的代码是用 dtrace 工具自己提供的 D 语言来编写的(注意,这里的 D 语言并不同于 Walter Bright 作为另一种“更好的 C++”而设计的 D 语言)。由于本系列教程并不打算介绍如何编写 dtrace 的 D脚本,同时理解这个脚本需要不少有关 Nginx 内部源码实现的细节,所以这里我们不展开介绍。大家只需要知道这个脚本的功能是:统计指定的 Nginx worker 进程在处理每个请求时,平均花费在 access 阶段上的时间。

现在来演示一下这个 D 脚本的运行方法。这个脚本接受一个命令行参数用于指定监视的 Nginx worker 进程的进程号(pid)。由于 Nginx 支持多 worker 进程,所以我们测试时发起的 HTTP 请求可能由其中任意一个 worker 进程服务。为了确保所有测试请求都为固定的 worker 进程处理,不妨在 nginx.conf 配置文件中指定只启用一个 worker 进程:

    worker_processes 1;

重启 Nginx 服务器之后,可以利用 ps 命令得到当前 worker 进程的进程号:

    $ ps ax|grep nginx|grep worker|grep -v grep

在我机器上的一次典型输出是

    10975   ??  S      0:34.28 nginx: worker process

其中第一列的数值便是我的 nginx worker 进程的进程号,10975。如果你得到的输出不止一行,则通常意味着你的系统中同时运行着多个 Nginx 服务器实例,或者当前 Nginx 实例启用了多个 worker 进程。

接下来使用刚刚得到的 worker 进程号以及 root 身份来运行 nginx-access-time.d 脚本:

    $ sudo ./nginx-access-time.d 10975

如果一切正常,则会看到这样一行输出:

    dtrace: script './nginx-access-time.d' matched 4 probes

这行输出是说,我们的 D 脚本已成功向目标进程动态植入了 4 个 dtrace “探针”(probe)。紧接着这个脚本就挂起了,表明 dtrace 工具正在对进程 10975 进行持续监视。

然后我们再打开一个新终端,在那里使用 curl 这样的工具多次请求我们正在监视的接口

    $ curl 'http://localhost:8080/hello'
    hello world
 
    $ curl 'http://localhost:8080/hello'
    hello world

最后我们回到原先那个一直在运行 D 脚本的终端,按下 Ctrl-C 组合键中止 dtrace 的运行。而该脚本在退出时会向终端打印出最终统计结果。例如我的终端此时是这个样子的:

    $ sudo ./nginx-access-time.d 10975
    dtrace: script './nginx-access-time.d' matched 4 probes
    ^C
           19219

最后一行输出 19219 便是那几次 curl 请求在 access 阶段的平均用时(以纳秒,即 10 的负 9 次方秒为单位)。

通过上面介绍的步骤,可以通过 nginx-access-time.d 脚本分别统计出各种不同的 Nginx 配置下 access阶段的平均用时。针对我们感兴趣的三种情况可以进行三组平行试验,即使用 ngx_access 过滤 IP 地址的情况,使用 access_by_lua 过滤 IP 地址的情况,以及不在 access 阶段使用任何配置指令的情况。最后一种情况属于“空白对照组”,用于校正测试过程中因 dtrace 探针等其他因素而引入的“系统误差”。另外,为了最小化各种不可控的“随机误差”,可以用 ab 这样的批量测试工具来取代 curl 发起连续十万次以上的请求,例如

    $ ab -k -c1 -n100000 'http://127.0.0.1:8080/hello'

这样我们的 D 脚本统计出来的平均值将更加接近“真实值”。

在我的苹果系统上,一次典型的测试结果如下:

    ngx_access 组               18146
    access_by_lua 组            35011
    空白对照组                   15887

把前两组的结果分别减去“空白对照组”的结果可以得到

    ngx_access 组               2259
    access_by_lua 组           19124

可以看到,ngx_access 组比 access_by_lua 组快了大约一个数量级,这正是我们所预期的。不过其绝对时间差是极小的,对于我的 Intel Core2Duo 1.86 GHz 的 CPU 而言,也只有区区十几微秒,或者说是在十万分之一秒的量级。

当然,上面使用 access_by_lua 的例子还可以通过换用 $binary_remote_addr 内建变量进行优化,因为$binary_remote_addr 读出的是二进制形式的 IP 地址,而 $remote_addr 则返回更长一些的字符串形式的地址。更短的地址意味着用 Lua 进行字符串比较时通常可以更快。

值得注意的是,如果按 (一) 中介绍的方法为 Nginx 开启了“调试日志”的话,上面统计出来的时间会显著增加,因为“调试日志”自身的开销是很大的。

Nginx 配置指令的执行顺序(四)的更多相关文章

  1. Nginx 配置指令的执行顺序(八)

    前面我们详细讨论了 rewrite.access 和 content 这三个最为常见的 Nginx 请求处理阶段,在此过程中,也顺便介绍了运行在这三个阶段的众多 Nginx 模块及其配置指令.同时可以 ...

  2. Nginx 配置指令的执行顺序(五)

    Nginx 的 content 阶段是所有请求处理阶段中最为重要的一个,因为运行在这个阶段的配置指令一般都肩负着生成“内容”(content)并输出 HTTP 响应的使命.正因为其重要性,这个阶段的配 ...

  3. Nginx 配置指令的执行顺序(一)

    大多数 Nginx 新手都会频繁遇到这样一个困惑,那就是当同一个 location 配置块使用了多个 Nginx 模块的配置指令时,这些指令的执行顺序很可能会跟它们的书写顺序大相径庭.于是许多人选择了 ...

  4. Nginx配置指令的执行顺序

    rewrite阶段 rewrite阶段是一个比较早的请求处理阶段,这个阶段的配置指令一般用来对当前请求进行各种修改(比如对URI和URL参数进行改写),或者创建并初始化一系列后续处理阶段可能需要的Ng ...

  5. Nginx 配置指令的执行顺序(十)

    运行在 post-rewrite 阶段之后的是所谓的 preaccess 阶段.该阶段在 access 阶段之前执行,故名preaccess. 标准模块 ngx_limit_req 和 ngx_lim ...

  6. Nginx 配置指令的执行顺序(六)

    前面我们在 (五) 中提到,在一个 location 中使用 content 阶段指令时,通常情况下就是对应的 Nginx 模块注册该 location 中的“内容处理程序”.那么当一个 locati ...

  7. Nginx 配置指令的执行顺序(三)

    如前文所述,除非像 ngx_set_misc 模块那样使用特殊技术,其他模块的配置指令即使是在 rewrite 阶段运行,也不能和 ngx_rewrite 模块的指令混合使用.不妨来看几个这样的例子. ...

  8. Nginx 配置指令的执行顺序(二)

    我们前面已经知道,当 set 指令用在 location 配置块中时,都是在当前请求的 rewrite 阶段运行的.事实上,在此上下文中,ngx_rewrite 模块中的几乎全部指令,都运行在 rew ...

  9. Nginx 配置指令的执行顺序

    在一个 location 中使用 content 阶段指令时,通常情况下就是对应的 Nginx 模块注册该 location 中的“内容处理程序”.那么当一个 location 中未使用任何 cont ...

随机推荐

  1. LFS,编译自己的Linux系统 - 准备

    概述 现在用的操作系统是Win8.1,用VMware创建一个虚拟机,3G内存(物理内存是6G,分一半),23G硬盘,其中3G用于swap分区,10G用于host system,10G用于建立LFS系统 ...

  2. django最简单表单入门

    两个html页面,存放于某个应用下的templates文件夹下. index.html 提交 点击“提交”按钮后,会调入第二个页面hello.html显示文本框的内容 原理是通过form的action ...

  3. 1006 Do the Untwist

    考察编程基础知识,用到字符和数字相互转化等.形式是描述清楚明文和暗文的转化规则. #include <stdio.h> #include <string.h> #define ...

  4. Mysql基本类型(五种年日期时间类型)——mysql之二

    转自:<MySQL技术内幕:时间和日期数据类型> http://tech.it168.com/a2012/0904/1393/000001393605_all.shtml

  5. N-Queens II 解答

    Question Follow up for N-Queens problem. Now, instead outputting board configurations, return the to ...

  6. Best Time to Buy and Sell Stock III 解答

    Question Say you have an array for which the ith element is the price of a given stock on day i. Des ...

  7. 安装配置MongoDB数据库

    一.关闭SElinux.配置防火墙 1.vi /etc/selinux/config #SELINUX=enforcing #注释掉 #SELINUXTYPE=targeted #注释掉 SELINU ...

  8. Android系统匿名共享内存(Anonymous Shared Memory)C++调用接口分析

    文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6939890 在Android系统中,针对移动设 ...

  9. java学习笔记day04

    1.static关键字  特点:1)随着类的加载而加载        2)优先于对象存在        3)被所有对象所共享        4)可以直接被类名调用(类名.静态成员) 注意:静态方法只能 ...

  10. Kali下使用libheap

    Kali下使用libheap 在github上,可以libheap用来帮助调试堆溢出.链接见:https://github.com/cloudburst/libheap 但是最后一次更新在一年前了,我 ...