Nginx 配置指令的执行顺序(四)
2014-12-18 14:48
357 查看
ngx_lua 模块提供了配置指令 access_by_lua,用于在
我们来看一个简单的例子,利用 access_by_lua 来实现 ngx_access 模块的 IP 地址过滤功能:
这里在 Lua 代码中通过引用 Nginx 标准的内建变量 $remote_addr 来获取字符串形式的客户端 IP 地址,然后用 Lua 的
这个例子在功能上完全等价于先前在 (三) 中介绍过的那个使用 ngx_access 模块的例子:
虽然这两个例子在功能上完全相同,但在性能上还是有区别的,毕竟 ngx_access 是用纯 C 实现的专门化的 Nginx 模块。
下面我们不妨来实际测量一下这两个例子的性能差别。因为我们使用 Nginx 就是为了追求性能,而量化的性能比较,在工程上具有很大的现实意义,所以我们顺便介绍一下重要的测量技术。由于无论是 ngx_access 还是 ngx_lua 在进行 IP 地址验证方面的性能都非常之高,所以为了减少测量误差,我们希望能对
幸运的是,在新一点的 Solaris, Mac OS X, 以及 FreeBSD 等系统上存在一个叫做
首先,在 Mac OS X 系统中打开一个命令行终端,在某一个文件目录下面创建一个名为
保存好此文件后,再赋予它可执行权限:
这个
现在来演示一下这个
重启 Nginx 服务器之后,可以利用
在我机器上的一次典型输出是
其中第一列的数值便是我的 nginx worker 进程的进程号,
接下来使用刚刚得到的 worker 进程号以及 root 身份来运行
如果一切正常,则会看到这样一行输出:
这行输出是说,我们的
然后我们再打开一个新终端,在那里使用
最后我们回到原先那个一直在运行
最后一行输出
通过上面介绍的步骤,可以通过
这样我们的
在我的苹果系统上,一次典型的测试结果如下:
把前两组的结果分别减去“空白对照组”的结果可以得到
可以看到,ngx_access 组比 access_by_lua 组快了大约一个数量级,这正是我们所预期的。不过其绝对时间差是极小的,对于我的
当然,上面使用 access_by_lua 的例子还可以通过换用 $binary_remote_addr 内建变量进行优化,因为$binary_remote_addr 读出的是二进制形式的 IP 地址,而 $remote_addr 则返回更长一些的字符串形式的地址。更短的地址意味着用 Lua 进行字符串比较时通常可以更快。
值得注意的是,如果按 (一) 中介绍的方法为 Nginx 开启了“调试日志”的话,上面统计出来的时间会显著增加,因为“调试日志”自身的开销是很大的。
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 配置指令的执行顺序(五)
- Nginx 配置指令的执行顺序(九)
- Nginx 配置指令的执行顺序(六)
- Nginx 配置指令的执行顺序(七)
- Nginx配置指令的执行顺序
- Nginx 配置指令的执行顺序(十一)
- Nginx 配置指令的执行顺序(二)
- Nginx 配置指令的执行顺序(八)
- Nginx 配置指令的执行顺序(八)
- Nginx 配置指令的执行顺序
- Nginx 配置指令的执行顺序(十)
- Nginx 配置指令的执行顺序(三)
- Nginx 配置指令的执行顺序(一)
- nginx配置指令的执行顺序(1)转载子章亦春
- Nginx-Lua模块的执行顺序及指令
- 转载:nginx配置文件的location标签执行顺序和反向代理配置
- Nginx 源码分析-- 模块module 解析执行 nginx.conf 配置文件流程分析 二
- bash中profile等配置文件执行顺序
- Linux周期性任务的执行指令配置
- Nginx 关于 Rewrite 执行顺序详解