Nginx Lua的执行阶段

对刚接触Ngx_lua的读者来说,可能会存在下面两个困惑。

1、Lua在Nginx的哪些阶段可以执行代码?
Lua在Nginx的每个阶段可以执行哪些操作?

2、只有理解了这两个问题,才能在业务中巧妙地利用Ngx_Lua来完成各项需求。

Nginx的11个执行阶段,每个阶段都有自己能够执行的指令,并可以实现不同的功能。Ngx_Lua的功能大部分是基于Nginx这11个执行阶段开发和配置的,Lua代码在这些指令块中执行,并依赖于它们的执行顺序。本章将对Ngx_Lua的执行阶段进行一一讲解。

一、 init_by_lua_block

init_by_lua_block是init_by_lua的替代版本,在OpenResty 1.9.3.1或Lua-Nginx-Modulev 0.9.17之前使用的都是init_by_lua。init_by_lua_block比init_by_lua更灵活,所以建议优先选用init_by_lua_block。
本章中的执行阶段都采用*_block格式的指令,后续不再说明。

1.1 阶段说明
语法:init_by_lua_block {lua-script-str}
配置环境:http
阶段:loading-config
含义:当Nginx的master进程加载Nginx配置文件(加载或重启Nginx进程)时,会在全局的Lua VM(Virtual Machine,虚拟机)层上运行<lua-script-str> 指定的代码,每次当Nginx获得HUP(即Hangup)重载信号加载进程时,代码都会被重新执行。

1.2 初始化配置
在loading-config阶段一般会执行如下操作。
1.初始化Lua全局变量,特别适合用来处理在启动master进程时就要求存在的数据,对CPU消耗较多的功能也可以在此处处理。
2.预加载模块。
3.初始化lua_shared_dict共享内存的数据(关于共享内存详见第10章)。
示例如下:
user webuser webuser;
worker_processes 1;
worker_rlimit_nofile 10240;

events {
use epoll;
worker_connections 10240;
}

http {
include mime.types;
default_type application/octet-stream;
log_format main ‘$remote_addr-$remote_user[$time_local] "$request" ‘
‘$status $body_bytes_sent "$http_referer" ‘
‘"$http_user_agent" "$http_x_forwarded_for" "$request_time" "$upstream_addr $upstream_status $upstream_response_time" "upstream_time_sum:$upstream_time_sum" "jk_uri:$jk_uri"‘;
access_log logs/access.log main;
sendfile on;
keepalive_timeout 65;

lua_package_path "/usr/local/nginx_1.12.2/conf/lua_modules/?.lua;;";
lua_package_cpath "/usr/local/nginx_1.12.2/conf/lua_modules/c_package/?.so;;";

lua_shared_dict dict_a 100k;  --声明一个Lua共享内存,dict_a为100KB

init_by_lua_block {
-- cjson.so文件需要手动存放在lua_package_cpath搜索路径下,如果是OpenResty,就不必了,因为它默认支持该操作
cjson = require "cjson";
local dict_a = ngx.shared.dict_a;
dict_a:set("abc", 9)
}

server {
    listen       80;
    server_name  testnginx.com;
    location / {
       content_by_lua_block {
           ngx.say(cjson.encode({a = 1, b = 2}))
           local dict_a = ngx.shared.dict_a;
           ngx.say("abc=",dict_a:get("abc"))
       }
    }
}

执行结果如下:

# curl -I http://testnginx.com/
{"a":1,"b":2}
abc=9

1.3 控制初始值
在init_by_lua_block阶段设置的初始值,即使在其他执行阶段被修改过,当Nginx重载配置时,这些值就又会恢复到初始状态。如果在重载Nginx配置时不希望再次改动这些初始值,可以在代码中做如下调整。

init_by_lua_block {
      local cjson = require "cjson";
      local dict_a = ngx.shared.dict_a;
      local v = dict_a:get("abc");  --判断初始值是否已经被set
      if not v then                 --如果没有,就执行初始化操作
        dict_a:set("abc", 9)
      end
}

1.4 init_by_lua_file
init_by_lua_file和init_by_lua_block的作用一样,主要用于将init_by_lua_block的内容转存到指定的文件中,这样Lua代码就不必全部写在Nginx配置里了,易读性更强。
init_by_lua_file支持配置绝对路径和相对路径。相对路径是在启动Nginx时由-p PATH 决定的,如果启动Nginx时没有配置-p PATH,就会使用编译时--prefix的值,该值一般存放在Nginx的$prefix(也可以用${prefix}来表示)变量中。init_by_lua_file和Nginx的include指令的相对路径一致。
举例如下:

init_by_lua_file conf/lua/init.lua;  --相对路径
init_by_lua_file /usr/local/nginx/conf/lua/init.lua; --绝对路径
init.lua文件的内容如下:
cjson = require "cjson"
local dict_a = ngx.shared.dict_a
local v = dict_a:get("abc")
if not v then
   dict_a:set("abc", 9)
end  

1.5 可使用的Lua API指令
init_by_lua是Nginx配置加载的阶段,很多Nginx API for Lua命令是不支持的。目前已知支持的Nginx API for Lua的命令有ngx.log、ngx.shared.DICT、print。
注意:init_by_lua
中的表示通配符,init_by_lua即所有以init_by_lua开头的API。后续的通配符亦是如此,不再另行说明。

二、init_worker_by_lua_block

2.1 阶段说明
语法:init_worker_by_lua_block {lua-script-str}
配置环境:http
阶段:starting-worker
含义:当master进程被启动后,每个worker进程都会执行Lua代码。如果Nginx禁用了master进程,init_by_lua*将会直接运行。

2.2 启动Nginx的定时任务
在init_worker_by_lua_block执行阶段最常见的功能是执行定时任务。示例如下:

user  webuser webuser;
worker_processes  3;
worker_rlimit_nofile 10240;

events {
    use epoll;
    worker_connections  10240;
}

http {
    include       mime.types;
    default_type  application/octet-stream;
    sendfile        on;
    keepalive_timeout  65;

    upstream  test_12 {
        server 127.0.0.1:81  weight=20  max_fails=300000 fail_timeout=5s;
        server 127.0.0.1:82  weight=20  max_fails=300000 fail_timeout=5s;
    }

    lua_package_path "${prefix}conf/lua_modules/?.lua;;";
    lua_package_cpath "${prefix}conf/lua_modules/c_package/?.so;;";

    init_worker_by_lua_block  {
        local delay = 3  --3秒
        local cron_a
    --定时任务的函数
        cron_a = function (premature)
            if not  premature then  --如果执行函数时没有传参,则该任务会一直被触发执行
                ngx.log(ngx.ERR, "Just do it !")
            end

        end
    --每隔delay参数值的时间,就执行一次cron_a函数
        local ok, err = ngx.timer.every(delay, cron_a)
        if not ok then
            ngx.log(ngx.ERR, "failed to create the timer: ", err)
            return
        end
    }

2.3 动态进行后端健康检查
在init_worker_by_lua_block阶段,也可以实现后端健康检查的功能,用于检查后端HTTP服务是否正常,类似于Nginx商业版中的health_check功能。
如果使用OpenResty 1.9.3.2及以上的版本,默认已支持此模块;如果使用Nginx,则首先需要安装此模块,安装方式如下:

# git clone https://github.com/openresty/lua-upstream-nginx-module.git
# cd nginx-1.12.2
# ./configure --prefix=/opt/nginx     --with-ld-opt="-Wl,-rpath,$LUAJIT_LIB"     --add-module=/path/to/lua-nginx-module     --add-module=/path/to/lua-upstream-nginx-module
# make && make install

注意:安装完成后,重新编译Nginx时,请先确认之前安装模块的数量,避免遗忘某个模块。
然后,将lua-resty-upstream-healthcheck模块中的lib文件复制到lua_package_path指定的位置,示例如下:

# git clone https://github.com/openresty/lua-resty-upstream-healthcheck.git
# cp lua-resty-upstream-healthcheck/lib/resty/upstream/healthcheck.lua   /usr/local/nginx_1.12.2/conf/lua_modules/resty/

实现动态进行后端健康检查的功能,配置如下:

user  webuser webuser;
worker_processes  3;
worker_rlimit_nofile 10240;

events {
    use epoll;
    worker_connections  10240;
}
http {
    include       mime.types;
    default_type  application/octet-stream;
    sendfile        on;
    keepalive_timeout  65;

    upstream  test_12 {
        server 127.0.0.1:81  weight=20  max_fails=10 fail_timeout=5s;
        server 127.0.0.1:82  weight=20  max_fails=10 fail_timeout=5s;
        server 127.0.0.1:8231  weight=20  max_fails=10 fail_timeout=5s;
    }
    lua_shared_dict healthcheck 1m;  # 存放upstream servers的共享内存,后端服务器组越多,配置就越大
    lua_socket_log_errors off;  # 当TCP发送失败时,会发送error日志到error.log中,该过程会增加性能开销,建议关闭,以避免在健康检查过程中出现多台服务器宕机的情况,异常情况请使用ngx.log来记录
    lua_package_path "${prefix}conf/lua_modules/?.lua;;";
    lua_package_cpath "${prefix}conf/lua_modules/c_package/?.so;;";

    init_worker_by_lua_block  {
        local hc = require "resty.upstream.healthcheck"
        local ok, err = hc.spawn_checker{
            shm = "healthcheck",  -- 使用共享内存
            upstream = "test_12", -- 进行健康检查的upstream名字
            type = "http",        -- 检查类型是http
            http_req = "GET /status HTTP/1.0\r\nHost: testnginx.com\r\n\r\n",
                    -- 用来发送HTTP请求的格式和数据,核实服务是否正常
            interval = 3000,  -- 设置检查的频率为每3s一次
            timeout = 1000,   -- 设置请求的超时时间为1s
            fall = 3,  --设置连续失败3次后就把后端服务改为down
            rise = 2,  --设置连续成功2次后就把后端服务改为up
        valid_statuses = {200, 302},  --设置请求成功的响应状态码是200和302
            concurrency = 10,  --设置发送请求的并发等级
        }
        if not ok then
            ngx.log(ngx.ERR, "failed to spawn health checker: ", err)
            return
        end
    }
    server {
        listen       80;
        server_name  testnginx.com;
        location / {
           proxy_pass http://test_12;
        }
        # /status 定义了后端健康检查结果的输出页面
        location = /status {
            default_type text/plain;
            content_by_lua_block {
                local hc = require "resty.upstream.healthcheck"
                --输出当前检查结果是哪个worker进程的
                ngx.say("Nginx Worker PID: ", ngx.worker.pid())
--status_page()输出后端服务器的详细情况
                ngx.print(hc.status_page())
            }
        }
    }

访问http://testnginx.com/status查看检查的结果,图8-1所示为健康检查数据结果。

图8-1 健康检查数据结果
如果要检查多个upstream,则配置如下(只有黑色加粗位置的配置有变化):

local ok, err = hc.spawn_checker{
    shm = "healthcheck",
    upstream = "test_12",
    type = "http",

    http_req = "GET /status HTTP/1.0\r\nHost: testnginx.com\r\n\r\n",

    interval = 3000,
    timeout = 1000,
    fall = 3,
    rise = 2,
    valid_statuses = {200, 302},
    concurrency = 10,
}
local ok, err = hc.spawn_checker{
    shm = "healthcheck",
    upstream = "test_34",
    type = "http",

    http_req = "GET /test HTTP/1.0\r\nHost: testnginx.com\r\n\r\n",
    interval = 3000,
    timeout = 1000,
    fall = 3,
    rise = 2,
    valid_statuses = {200, 302},
    concurrency = 10,  

如果把lua_socket_log_errors设置为on,那么当有异常出现时,例如出现了超时,就会往error.log里写日志,日志记录如图8-2所示。

图8-2 日志记录
经过lua-resty-upstream-healthcheck的健康检查发现异常的服务器后,Nginx会动态地将异常服务器在upstream中禁用,以实现更精准的故障转移。

三、set_by_lua_block

3.1 阶段说明
语法:set_by_lua_block $res {lua-script-str}
配置环境:server,server if,location,location if
阶段:rewrite
含义:执行<lua-script-str>代码,并将返回的字符串赋值给$res。

3.2 变量赋值
本指令一次只能返回一个值,并赋值给变量$res(即只有一个$res被赋值),示例如下:

server {
    listen       80;
    server_name  testnginx.com;
    location / {
    set $a ‘‘;
    set_by_lua_block $a {
         local t = ‘tes‘
         return t
    }
    return 200 $a;
}

执行结果如下:

#  curl http://testnginx.com/
test

那如果希望返回多个变量,该怎么办呢?可以使用ngx.var.VARIABLE,示例如下:

server {
    listen       80;
    server_name  testnginx.com;
    location / {
    #使用ngx.var.VARIABLE前需先定义变量
    set $b ‘‘;
    set_by_lua_block $a {
        local t = ‘test‘
        ngx.var.b = ‘test_b‘
        return t
    }
    return 200 $a,$b;
    }
}

执行结果如下:

#  curl http://testnginx.com/test
test,test_b

3.2 Rewrite阶段的混用模式
因为set_by_lua_block处在rewrite阶段,所以它可以和ngx_http_rewrite_module、set-misc-nginx-module,以及array-var-nginx-module一起使用,在代码执行时,按照配置文件的顺序从上到下执行,示例如下:

server {
    listen       80;
    server_name  testnginx.com;
    location / {

    set $a ‘123‘;
    set_by_lua_block $b {
        local t = ‘bbb‘
        return t
    }
    set_by_lua_block $c {
        local t = ‘ccc‘  .. ngx.var.b
        return t
    }
    set $d "456$c";
    return 200 $a,$b,$c,$d;
    }

}

从执行结果可以看出数据是从上到下执行的,如下所示:

# curl  http://testnginx.com/test
123,bbb,cccbbb,456cccbbb

3.3 阻塞事件
set_by_lua_block指令块在Nginx中执行的指令是阻塞型操作,因此应尽量在这个阶段执行轻、快、短、少的代码,以避免耗时过多。set_by_lua_block不支持非阻塞I/O,所以不支持yield当前Lua的轻线程。
3.4 被禁用的Lua API指令
在set_by_lua_block阶段的上下文中,下面的Lua API是被禁止的(只罗列部分)。
输出类型的API函数(如ngx.say和ngx.send_headers)。
控制类型的API函数(如ngx.exit)。
子请求的API函数(如ngx.location.capture和ngx.location.capture_multi)。
Cosocket API函数(如ngx.socket.tcp和ngx.req.socket)。
休眠API函数ngx.sleep。

四、rewrite_by_lua_block

4.1 阶段说明
语法:rewrite_by_lua_block {lua-script-str}
配置环境:http,server,location,location if
阶段:rewrite tail
含义:作为一个重写阶段的处理程序,对每个请求执行<lua-script-str>指定的Lua代码。
这些Lua代码可以调用所有的Lua API,并且运行在独立的全局环境(类似于沙盒)中,以新的协程来执行。因为可以调用所有的Lua API,所以此阶段可以实现很多功能,例如对请求的URL进行重定向、读取MySQL或Redis数据、发送子请求、控制请求头等。

4.2 利用rewrite_by_lua_no_postpone改变执行顺序
rewrite_by_lua_block命令默认在ngx_http_rewrite_module之后执行,示例如下:

server {
    listen       80;
    server_name  testnginx.com;
    location / {
     set $b  ‘1‘;
     rewrite_by_lua_block { ngx.var.b = ‘2‘}
     set $b  ‘3‘;
     echo $b;
    }
}

从代码来看,预计应输出的结果是3,但输出的结果却是2,如下所示:

#  curl  http://testnginx.com/test
2

上述配置的操作结果说明rewrite_by_lua_block始终都在rewrite阶段的后面执行,如果要改变这个顺序,需使用rewrite_by_lua_no_postpone指令。
语法:rewrite_by_lua_no_postpone on|off
默认:rewrite_by_lua_no_postpone off
配置环境:http
含义:在rewrite请求处理阶段,控制rewrite_by_lua*的所有指令是否被延迟执行,默认为off,即延迟到最后执行;如果设置为on,则会根据配置文件的顺序从上到下执行。
示例:

rewrite_by_lua_no_postpone on;   #只能在http阶段配置
server {
    listen       80;
    server_name  testnginx.com;
    location / {
     set $b  ‘1‘;
     rewrite_by_lua_block { ngx.var.b = ‘2‘}
     set $b  ‘3‘;
     echo $b;
    }
}

执行结果如下:

#  curl -i http://testnginx.com/test
HTTP/1.1 200 OK
Server: nginx/1.12.2
Date: Mon, 28 May 2018 12:47:37 GMT
Content-Type: application/octet-stream
Transfer-Encoding: chunked
Connection: keep-alive

3

注意:if 语句是在rewrite_by_lua_block阶段之前执行的,所以在运用if语句时要特别留意执行顺序,避免出现意想不到的结果。

4.3 阶段控制
在rewrite_by_lua_block阶段,当调用ngx.exit(ngx.OK)之后,请求会退出当前的执行阶段,继续下一阶段的内容处理(如果想了解更多关于ngx.exit的使用方式,请参考7.16.3节)。

五、access_by_lua_block

5.1 阶段说明
语法:access_by_lua_block {lua-script-str}
配置环境:http,server,location,location if
阶段:access tail
含义:在Nginx的access阶段,对每个请求执行<lua-script-str>的代码,和rewrite_bylua block一样,这些Lua代码可以调用所有的Lua API,并且运行在独立的全局环境(类似于沙盒)中,以新的协程来执行。此阶段一般用来进行权限检查和黑白名单配置。

5.2 利用access_by_lua_no_postpone改变执行顺序
access_by_lua_block默认在ngx_http_access_module之后。但把access_by_lua_no_postpone设置为on可以改变执行顺序,变成根据配置文件的顺序从上到下执行。access_by_luano postpone的用法和rewrite_by_lua_no_postpone类似。

5.3 阶段控制
access_by_lua_block和rewrite_by_lua_block一样,都可以通过ngx.exit来结束当前的执行阶段。

5.4 动态配置黑白名单
Nginx提供了allow、deny等指令来控制IP地址访问服务的权限,但如果每次新增的IP地址都需要重载配置文件就不太灵活了,而且反复重载Nginx也会导致服务不稳定。此时,可以通过access_by_lua_block来动态添加黑白名单。
下面是两种常见的动态配置黑白名单的方案。

1.将黑白名单存放在Redis中,然后使用Nginx+Lua读取Redis的数据,通过修改Redis中的数据来动态配置黑白名单。这种方案的缺点是增加了对网络I/O的操作,相比使用共享内存的方式,性能稍微差了些。

2.将黑白名单存放在Ngx_Lua提供的共享内存中,每次请求时都去读取共享内存中的黑白名单,通过修改共享内存的数据达到动态配置黑白名单的目的。本方案的缺点是在Nginx 重启的过程中数据会丢失。

8.6 content_by_lua_block

6.1 阶段说明
语法:content_by_lua_block {lua-script-str}
配置环境:location,location if
阶段:content
含义:content_by_lua_block 作为内容处理阶段,对每个请求执行<lua-script-str>的代码。和rewrite_by_lua_block一样,这些Lua代码可以调用所有的Lua API,并且运行在独立的全局环境(类似于沙盒)中,以新的协程来执行。
content_by_lua_block指令不可以和其他内容处理阶段的模块如echo、return、proxy_pass等放在一起,示例如下:

server {
    listen       80;
    server_name  testnginx.com;

    location / {
        content_by_lua_block {
             ngx.say("content_by_lua_block")
        }
        echo ‘ok‘;
    }
}

输出结果只有ok,并未执行content_by_lua_block指令。

6.2 动态调整执行文件的路径
指令content_by_lua_file可以用来动态地调整执行文件的路径,它后面跟的Lua执行文件路径可以是变量,该变量可以是不同的参数或URL等,示例如下:

location / {
    #content_by_lua_file可以直接获取URL中参数file_name的值
    content_by_lua_file conf/lua/$arg_file_name;
}

#七、balancer_by_lua_block

7.1 阶段说明
语法:balancer_by_lua_block { lua-script }
配置环境:upstream
阶段:content
含义:在upstream的配置中执行并控制后端服务器的地址,它会忽视原upstream中默认的配置。
示例:

upstream foo {
    server 127.0.0.1;  #会忽视这个配置
    balancer_by_lua_block {
        #真实的IP地址在这里配置
    }
}

7.2 被禁用的Lua API指令
在balancer_by_lua_block阶段,Lua代码的执行环境不支持yield,因此需禁用可能会产生yield的Lua API指令(如cosockets和light threads)。但可以利用ngx.ctx创建一个拥有上下文的变量,在本阶段前面的某个执行阶段(如rewrite_by_lua*阶段)将数据生成后传入upstream中。

八、header_filter_by_lua_block

8.1 阶段说明
语法:header_filter_by_lua_block { lua-script }
配置环境:http,server,location,location if
阶段:output-header-filter
含义:在header_filter_by_lua_block阶段,对每个请求执行<lua-script-str>的代码,以此对响应头进行过滤。常用于对响应头进行添加、删除等操作。
例如,添加一个响应头test,值为nginx-lua,示例如下:

location / {
    header_filter_by_lua_block {
       ngx.header.test = "nginx-lua";
    }
    echo ‘ok‘;
}

8.2 被禁用的Lua API指令
在header_filter_by_lua_block阶段中,下列Lua API是被禁止使用的。
输出类型的API函数(如ngx.say和ngx.send_headers)。
控制类型的API函数(如ngx.redirect和ngx.exec)。
子请求的API函数(如ngx.location.capture和ngx.location.capture_multi)。
cosocket API函数(如ngx.socket.tcp和ngx.req.socket)。

九、body_filter_by_lua_block

9.1 阶段说明
语法:body_filter_by_lua_block { lua-script-str }
配置环境:http,server,location,location if
阶段:output-body-filter
含义:在body_filter_by_lua_block阶段执行<lua-script-str>的代码,用于设置输出响应体的过滤器。在此阶段可以修改响应体的内容,如修改字母的大小写、替换字符串等。

9.2 控制响应体数据
通过ngx.arg[1](ngx.arg[1]是Lua的字符串类型的数据)输入数据流,结束标识eof是响应体数据的最后一位ngx.arg[2](ngx.arg[2]是Lua的布尔值类型的数据)。
由于Nginx chain缓冲区的存在,数据流不一定都是一次性完成的,可能需要发送多次。在这种情况下,结束标识eof仅仅是Nginx chain缓冲区的last_buf(主请求)或last_in_chain(子请求),因此Nginx输出过滤器在一个单独的请求中会被多次调用,此阶段的Lua指令也会被执行多次,示例如下:

server {
    listen       80;
    server_name  testnginx.com;

    location / {
        #将响应体全部转换为大写
        body_filter_by_lua_block { ngx.arg[1] = string.upper(ngx. arg[1]) }
        echo ‘oooKkk‘;
        echo ‘oooKkk‘;
        echo ‘oooKkk‘;
    }
}

执行结果如下:

#  curl -i http://testnginx.com/?file_name=1.lua
HTTP/1.1 200 OK
Server: nginx/1.12.2
Date: Tue, 29 May 2018 11:36:54 GMT
Content-Type: application/octet-stream
Transfer-Encoding: chunked
Connection: keep-alive

OOOKKK
OOOKKK
OOOKKK
使用return ngx.ERROR可以截断响应体,但会导致数据不完整,请求无效:
location / {
    body_filter_by_lua_block {
        ngx.arg[1] = string.upper(ngx.arg[1]);
        return ngx.ERROR
    }
    echo ‘oooKkk‘;
    echo ‘oooKkk‘;
    echo ‘oooKkk‘;
}

执行结果如下:

#  curl -i http://testnginx.com/
curl: (52) Empty reply from server

ngx.arg[2]是一个布尔值,如果把它设为true,可以用来截断响应体的数据,和return ngx.ERROR不一样,此时被截断的数据仍然可以输出内容,是有效的请求,示例如下:

server {
    listen       80;
    server_name  testnginx.com;
    location / {
        body_filter_by_lua_block {
           local body_chunk = ngx.arg[1]
           --如果响应体匹配到了2ooo,就让ngx.arg[2]=true
           if string.match(body_chunk, "2ooo") then
               ngx.arg[2] = true
               return
           end
--设置ngx.arg[1] = nil,表示此响应体不会出现在输出内容中
           ngx.arg[1] = nil

        }
        echo ‘1oooKkk‘;
        echo ‘2oooKkk‘;
        echo ‘3oooKkk‘;
    }
}

执行结果如下:

#  curl -i http://testnginx.com/?file_name=1.lua
HTTP/1.1 200 OK
Server: nginx/1.12.2
Date: Tue, 29 May 2018 11:48:52 GMT
Content-Type: application/octet-stream
Transfer-Encoding: chunked
Connection: keep-alive

2oooKkk

从输出结果可以得出如下结论。
1.没有输出1oooKkk,是因为它在if语句中匹配不成功,设置 ngx.arg[1] = nil将没有匹配成功的数据屏蔽了。
2.没有输出3oooKkk,是因为2oooKkk被匹配成功了,使用ngx.arg[2] = true终止了后续的操作,剩下的内容就不会再被输出了。

9.3 被禁用的Lua API指令
在body_filter_by_lua_block阶段中,下列Lua API是被禁止的。
输出类型的API函数(如ngx.say和ngx.send_headers)。
控制类型的API函数(如ngx.redirect和ngx.exec)。
子请求的API函数(如ngx.location.capture和ngx.location.capture_multi)。
cosocket API函数(如ngx.socket.tcp和ngx.req.socket)。

#十、log_by_lua_block

10.1 阶段说明
语法:log_by_lua_block { lua-script }
配置环境:http,server,location,location if
阶段:log
含义:在日志请求处理阶段执行的{ lua-script }代码。它不会替换当前access请求的日志,而会运行在access的前面。
log_by_lua_block阶段非常适合用来对日志进行定制化处理,且可以实现日志的集群化维护(详见第12章)。另外,此阶段属于log阶段,这时,请求已经返回到了客户端,对Ngx_Lua代码的异常影响要小很多。
示例:

server {
    listen       80;
    server_name  testnginx.com;

    location / {
        log_by_lua_block {
            local ngx = require "ngx";
            xxxsada  --一个明显的语法错误
            ngx.log(ngx.ERR, ‘x‘);
        }
        echo ‘ok‘;
    }
}

上述代码的error_log虽有报错,但执行结果仍然会输出“ok”。

10.2 被禁用的Lua API指令
在log_by_lua_block阶段中,下列Lua API是被禁止的。
输出类型的API函数(如ngx.say和ngx.send_headers)。
控制类型的API函数(如ngx.redirect和ngx.exec)。
子请求的API函数(如ngx.location.capture和ngx.location.capture_multi)。
cosocket API函数(如ngx.socket.tcp和ngx.req.socket)。
休眠API函数ngx.sleep。

#十一、Lua和ssl

Lua API还可以对HTTPS协议进行处理,可以使用ssl_certificate_by_lua_block、ssl_session_fetch_by_lua_block、ssl_session_store_by_lua_block这3个指令块进行配置,由于都涉及lua-resty-core模块的ngx.ssl指令(这已经超出了本章的内容),有兴趣的读者可以去查看lua-resty-core的相关资料。

#十二、Ngx_Lua执行阶段

通过前面章节的学习,读者了解了Ngx_lua模块各个执行阶段的作用,现在将这些执行阶段汇总到一起来观察一下整体的执行流程。
图8-3所示为Ngx_lua的执行顺序,它引用自Lua-Nginx-Module的官方Wiki,很清晰地展示了Ngx_lua在Nginx中的执行顺序。下面举一个例子来验证一下这个执行顺序。

图8-3 Ngx_Lua的执行顺序

1.测试代码使用 *_by_lua_file指令来配置,首先,建立如下的test.lua文件:

# vim /usr/local/nginx_1.12.2/conf/lua/test.lua
 local ngx  = require "ngx"
-- ngx.get_phase()可以获取Lua代码在运行时属于哪个执行阶段
 local phase = ngx.get_phase()
 ngx.log(ngx.ERR, phase, ‘:   Hello,world! ‘)

2.在Nginx配置中载入如下的文件:

# init_by_lua_file 和 init_worker_by_lua_file只能在http指令块执行
init_by_lua_file  /usr/local/nginx_1.12.2/conf/lua/test.lua;
init_worker_by_lua_file  /usr/local/nginx_1.12.2/conf/lua/test.lua;

server {
    listen       80;
    server_name  testnginx.com;

    location / {
       #文件的顺序是随意摆放的,观察日志,留意文件顺序是否对执行顺序有干扰
       body_filter_by_lua_file  /usr/local/nginx_1.12.2/conf/lua/test.lua;
       header_filter_by_lua_file  /usr/local/nginx_1.12.2/conf/lua/test.lua;
       rewrite_by_lua_file  /usr/local/nginx_1.12.2/conf/lua/test.lua;
       access_by_lua_file  /usr/local/nginx_1.12.2/conf/lua/test.lua;
       set_by_lua_file $test /usr/local/nginx_1.12.2/conf/lua/test.lua;
       log_by_lua_file  /usr/local/nginx_1.12.2/conf/lua/test.lua;
       # content_by_lua_file 和 balancer_by_lua_block 不能存在于同一个location,所以只用content_by_lua_file示例
       content_by_lua_file  /usr/local/nginx_1.12.2/conf/lua/test.lua;
    }
}

3.重载Nginx配置让代码生效,此时不要访问Nginx,可以观察到error.log在没有请求访问的情况下也会有内容输出。
如果输出“init”,表示此阶段属于init_by_lua_block阶段;如果输出“init_worker”,表示此阶段属于init_worker_by_lua_block阶段。
有些读者可能会发现init_worker的输出有3行,那是因为此阶段是在Nginx的worker进程上执行的,每个worker进程都会独立运行一次Lua代码,因此可以看出此时Nginx启动了3个worker进程,如下所示:

2018/06/04 19:00:23 [error] 12034#12034: [lua] test.lua:3: init:   Hello,world!
2018/06/04 19:00:23 [error] 21019#21019: *914 [lua] test.lua:3: init_worker:   Hello,world!, context: init_worker_by_lua*
2018/06/04 19:00:23 [error] 21020#21020: *915 [lua] test.lua:3: init_worker:   Hello,world!, context: init_worker_by_lua*
2018/06/04 19:00:23 [error] 21018#21018: *916 [lua] test.lua:3: init_worker:   Hello,world!, context: init_worker_by_lua*

4.发送请求:

curl -i http://testnginx.com/
HTTP/1.1 200 OK
Server: nginx/1.12.2
Date: Mon, 04 Jun 2018 11:00:29 GMT
Content-Type: application/octet-stream
Transfer-Encoding: chunked
Connection: keep-alive

5.观察access.log日志,能够看到每个执行阶段的优先级顺序:

2018/06/04 19:16:20 [error] 21019#21019: *920 [lua] test.lua:3: set:   Hello,world!, client: 10.19.64.210, server: testnginx.com, request: "GET /test?ss HTTP/1.1", host: "testnginx.com"
2018/06/04 19:16:20 [error] 21019#21019: *920 [lua] test.lua:3: rewrite:   Hello,world!, client: 10.19.64.210, server: testnginx.com, request: "GET /test?ss HTTP/1.1", host: "testnginx.com"
2018/06/04 19:16:20 [error] 21019#21019: *920 [lua] test.lua:3: access:   Hello,world!, client: 10.19.64.210, server: testnginx.com, request: "GET /test?ss HTTP/1.1", host: "testnginx.com"
2018/06/04 19:16:20 [error] 21019#21019: *920 [lua] test.lua:3: content:   Hello,world!, client: 10.19.64.210, server: testnginx.com, request: "GET /test?ss HTTP/1.1", host: "testnginx.com"
2018/06/04 19:16:20 [error] 21019#21019: *920 [lua] test.lua:3: header_filter:   Hello,world!, client: 10.19.64.210, server: testnginx.com, request: "GET /test?ss HTTP/1.1", host: "testnginx.com"
2018/06/04 19:16:20 [error] 21019#21019: *920 [lua] test.lua:3: body_filter:   Hello,world!, client: 10.19.64.210, server: testnginx.com, request: "GET /test?ss HTTP/1.1", host: "testnginx.com"
2018/06/04 19:16:20 [error] 21019#21019: *920 [lua] test.lua:3: log:   Hello,world! while logging request, client: 10.19.64.210, server: testnginx.com, request: "GET /test?ss HTTP/1.1", host: "testnginx.com"

通过access.log日志截图(如图8-4所示)可以看得更清晰。

图8-4 access.log日志截图
观察Ngx_Lua整体的执行流程,可得出如下结论。
Ngx_Lua执行顺序如下:

init_by_lua_block
init_worker_by_lua_block
set_by_lua_block
rewrite_by_lua_block
access_by_lua_block
content_by_lua_block
header_filter_by_lua_block
body_filter_by_lua_block
log_by_lua_block

指令在配置文件中的顺序不会影响执行的顺序,这一点和Nginx一样。
http阶段的init_by_lua_block和init_worker_by_lua_block只会在配置重载时执行,只进行HTTP请求时,不会被执行。
注意:init_by_lua_file 在lua_code_cache 为off 的情况下,每次执行HTTP请求都会执行重载配置阶段的Lua代码。

13、小结

本章对Ngx_Lua的执行阶段进行了讲解,掌握每个执行阶段的用途会让大家在实际开发中更加得心应手。

原文地址:http://blog.51cto.com/xikder/2331649

时间: 2024-10-28 13:38:42

Nginx Lua的执行阶段的相关文章

nginx+lua打造10K qps+的web应用

背景篇 由于项目流量越来越大,之前的nginx+php-fpm的架构已经难以承受峰值流量的冲击,春节期间集群负载一度长时间维持0%的idle,于是这段时间逐渐对旧系统进行重构. 受高人指点,发现lua这个好东西.因此在技术选型上,我们使用lua代替部分的php逻辑,比如请求的过滤.lua是一种可以嵌入nginx配置文件的动态语言,结合nginx的请求处理过程(参见另一篇博文),lua可以在这些阶段接管请求的处理. 我们的环境使用openresty搭建,openresty包括了很多nginx常用扩

使用nginx lua实现网站统计中的数据收集

使用nginx lua实现网站统计中的数据收集 导读 网站数据统计分析工具是各网站站长和运营人员经常使用的一种工具,常用的有 谷歌分析.百度统计和腾讯分析等等.所有这些统计分析工具的第一步都是网站访问数据的收集.目前主流的数据收集方式基本都是基于javascript的.在此简要分析数据收集的原理,并按照步骤,带领大家一同搭建一个实际的数据收集系统. 数据收集原理分析 简单来说,网站统计分析工具需要收集到用户浏览目标网站的行为(如打开某网页.点击某按钮.将商品加入购物车等)及行为附加数据(如某下单

深入浅出 nginx lua 为什么高性能

最近很多人问我nginx lua的优势是什么?为什么? 一.同步和异步.阻塞和非阻塞 如果要说清楚这个问题首先要了解:同步和异步.阻塞和非阻塞的关系 同步:php.java的正常代码都是同步执行的 异步:javascript的回调函数就是异步的 说白了自己写的程序里面如果没有回调函数都是同步的,常见的php.python等语言少有异步代码,当前大量使用异步代码的有javascript. 阻塞:查询数据库或者读写文件,如果没有获取结果就一直等待,说明是阻塞的.一般是阻塞的. 非阻塞:执行之后立刻返

OpenResty--------核心执行阶段篇

前言 前两篇分别介绍了OpenResty核心概念和,优势与架构等信息,进行本篇之前建议至少观看一遍. OpenResty--------企业级理论实践篇 OpenResty--------企业级入门实践篇 背景 之前篇章介绍了OpenResty是基于Nginx为基础核心的开发平台,本篇将继续介绍基础平台(Nginx)的主要特性. 特性 执行阶段 OpenResty将应用分为4个大阶段,11个小阶段,如下图所示. 初始化阶段: master进程启动预加载/生成worker进程预加载 转发/访问阶段

nginx+lua+redis(openresty)配置

nginx+lua+redis(openresty)配置 2014-07-18 11:10 2494人阅读 评论(1) 收藏 举报 方案一: 1.安装lua解释器 wget http://luajit.org/download/LuaJIT-2.0.2.tar.gz (http://luajit.org/download.html) 配置lua Lib和include/luajit-$version目录为环境变量 2.下载ngx_devel_kit和lua-nginx-module https:

使用Nginx+Lua代理Hadoop HA

一.Hadoop HA的Web页面访问 Hadoop开启HA后,会同时存在两个Master组件提供服务,其中正在使用的组件称为Active,另一个作为备份称为Standby,例如HDFS的NameNode.YARN 的ResourceManager.HDFS的web页面只有通过Active的NameNode才能正常访问,同样地,YARN的web页面也只有通过Active的ResouceManager才能正常访问. (1) HDFS HA的Web访问 正常使用Nginx的proxy_pass代理单

使用Nginx+Lua(OpenResty)开发高性能Web应用

在互联网公司,Nginx可以说是标配组件,但是主要场景还是负载均衡.反向代理.代理缓存.限流等场景:而把Nginx作为一个Web容器使用的还不是那么广泛.Nginx的高性能是大家公认的,而Nginx开发主要是以C/C++模块的形式进行,整体学习和开发成本偏高:如果有一种简单的语言来实现Web应用的开发,那么Nginx绝对是把好的瑞士军刀:目前Nginx团队也开始意识到这个问题,开发了nginxScript:可以在Nginx中使用JavaScript进行动态配置一些变量和动态脚本执行:而目前市面上

nginx lua redis 访问频率限制(转)

1. 需求分析 Nginx来处理访问控制的方法有多种,实现的效果也有多种,访问IP段,访问内容限制,访问频率限制等. 用Nginx+Lua+Redis来做访问限制主要是考虑到高并发环境下快速访问控制的需求. Nginx处理请求的过程一共划分为11个阶段,分别是: post-read.server-rewrite.find-config.rewrite.post-rewrite. preaccess.access.post-access.try-files.content.log. 在openre

nginx+lua+redis构建高并发应用(转)

nginx+lua+redis构建高并发应用 ngx_lua将lua嵌入到nginx,让nginx执行lua脚本,高并发,非阻塞的处理各种请求. url请求nginx服务器,然后lua查询redis,返回json数据. 备注:centos或者redhat系统请跳转到nginx + ngx_lua安装测试 一.安装lua 1 2 3 # apt-get install lua5.1 # apt-get install liblua5.1-dev # apt-get install liblua5.