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. nginx+keepalived+tomcat之tomcat性能调优

    body{ font-family: Nyala; font-size: 10.5pt; line-height: 1.5;}html, body{ color: ; background-color ...

  2. 关于C语言中的强符号、弱符号、强引用和弱引用的一些陋见,欢迎指正

    首先我表示很悲剧,在看<程序员的自我修养--链接.装载与库>之前我竟不知道C有强符号.弱符号.强引用和弱引用.在看到3.5.5节弱符号和强符号时,我感觉有些困惑,所以写下此篇,希望能和同样 ...

  3. C++之------运算符重载

    ①  什么是运算符重载? 何为C++的运算符重载呢? 其实就是运算符给它重新赋予新的含义或者多重含义.让它有另外一种新的功能. 为什么需要运算符重载? 面向对象中为了实现类的多态性,我们就引用了运算符 ...

  4. 运行时数据区即内存分配管理——JVM之六

    内存分配结构,请参考: http://iamzhongyong.iteye.com/blog/1333100

  5. Ext.getCmp()的简单使用

    Ext.getCmp(Ext组件ID),根据Ext组件的ID选择EXT组件. 例如:点击Panel->toolbar上的button改变Panel的标题 Ext.onReady(function ...

  6. MongoDB基本命令随便敲敲

    1,mongoDB状态,版本,当前连接的数据库名称

  7. 【转】__attribute__机制介绍

    1. __attribute__ GNU C的一大特色(却不被初学者所知)就是__attribute__机制. __attribute__可以设置函数属性(Function Attribute).变量 ...

  8. 高效CSS書寫規範及CSS兼容性

    一.選擇器針對性說明 某一元素的多个规则集中,选择器的针对性越高,该规则集的权重也就越高.针对性相同的,后出现的规则集的权重更高. * {} /* a=0 b=0 c=0 d=0 -> spec ...

  9. 浏览器缓存相关http头

    近期看雅虎黄金34条,学习下优化站点性能的方法. 当中有一条:"为文件头指定Expires或Cache-Control",详细来说指对于静态内容:设置文件头过期时间Expires的 ...

  10. Android 属性动画(Property Animation) 全然解析 (下)

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/38092093 上一篇Android 属性动画(Property Animatio ...