Nginx模块Lua-Nginx-Module学习笔记(二)Lua指令详解(Directives)

原文链接:https://yq.aliyun.com/articles/311759

源码地址:https://github.com/Tinywan/Lua-Nginx-Redis

Nginx与Lua编写脚本的基本构建块是指令。 指令用于指定何时运行用户Lua代码以及如何使用结果。 下面是显示指令执行顺序的图。

image.png

当一个请求发起一个“子请求”的时候,按照 Nginx 的术语,习惯把前者称为后者的“父请求”(parent request)。

location /main {  
    echo_location /foo;     # echo_location发送子请求到指定的location 
    echo_location /bar;  
}  
location /foo { 
  echo Tinywan_foo;  
}  
location /bar { 
  echo Tinywan_bar;  
}

重启Nginx,curl访问

root@iZ236j3sofdZ:/usr/local/nginx/conf # service nginx restart
* Stopping Nginx Server... [ OK ] * Starting Nginx Server... [ OK ] 
root@iZ236j3sofdZ:/usr/local/nginx/conf # curl 'http://localhost/main'
Tinywan_foo
Tinywan_bar

这里,main location就是发送2个子请求,分别到foo和bar,这就类似一种函数调用。 “子请求”方式的通信是在同一个虚拟主机内部进行的,所以 Nginx 核心在实现“子请求”的时候,就只调用了若干个 C 函数,完全不涉及任何网络或者 UNIX 套接字(socket)通信。我们由此可以看出“子请求”的执行效率是极高的。

协程(Coroutine)

**协程类似一种多线程,与多线程的区别有: **

1. 协程并非os线程,所以创建、切换开销比线程相对要小。

2. 协程与线程一样有自己的栈、局部变量等,但是协程的栈是在用户进程空间模拟的,所以创建、切换开销很小。

3. 多线程程序是多个线程并发执行,也就是说在一瞬间有多个控制流在执行。而协程强调的是一种多个协程间协作的关系,只有当一个协程主动放弃执行权,另一个协程才能获得执行权,所以在某一瞬间,多个协程间只有一个在运行。

4. 由于多个协程时只有一个在运行,所以对于临界区的访问不需要加锁,而多线程的情况则必须加锁。

5. 多线程程序由于有多个控制流,所以程序的行为不可控,而多个协程的执行是由开发者定义的所以是可控的。

Nginx的每个Worker进程都是在epoll或kqueue这样的事件模型之上,封装成协程,每个请求都有一个协程进行处理。这正好与Lua内建协程的模型是一致的,所以即使ngx_lua需要执行Lua,相对C有一定的开销,但依然能保证高并发能力。

原理介绍

原理:ngx_lua将Lua嵌入Nginx,可以让Nginx执行Lua脚本,并且高并发、非阻塞的处理各种请求。Lua内建协程,这样就可以很好的将异步回调转换成顺序调用的形式。ngx_lua在Lua中进行的IO操作都会委托给Nginx的事件模型,从而实现非阻塞调用。开发者可以采用串行的方式编写程序,ngx_lua会自动的在进行阻塞的IO操作时中断,保存上下文;然后将IO操作委托给Nginx事件处理机制,在IO操作完成后,ngx_lua会恢复上下文,程序继续执行,这些操作都是对用户程序透明的。 每个NginxWorker进程持有一个Lua解释器或者LuaJIT实例,被这个Worker处理的所有请求共享这个实例。每个请求的Context会被Lua轻量级的协程分割,从而保证各个请求是独立的。 ngx_lua采用“one-coroutine-per-request”的处理模型,对于每个用户请求,ngx_lua会唤醒一个协程用于执行用户代码处理请求,当请求处理完成这个协程会被销毁。每个协程都有一个独立的全局环境(变量空间),继承于全局共享的、只读的“comman data”。所以,被用户代码注入全局空间的任何变量都不会影响其他请求的处理,并且这些变量在请求处理完成后会被释放,这样就保证所有的用户代码都运行在一个“sandbox”(沙箱),这个沙箱与请求具有相同的生命周期。 得益于Lua协程的支持,ngx_lua在处理10000个并发请求时只需要很少的内存。根据测试,ngx_lua处理每个请求只需要2KB的内存,如果使用LuaJIT则会更少。所以ngx_lua非常适合用于实现可扩展的、高并发的服务。

Nginx Lua模块指令


lua_code_cache

语法: lua_code_cache on | off

默认值: lua_code_cache on

上下文:http, server, location, location if

启用或禁用指令中Lua代码的Lua代码缓存*_by_lua_file(如set_by_lua_filecontent_by_lua_file)和Lua模块,

关闭时,ngx_lua提供的每个请求都将在一个单独的Lua VM实例中运行,从该0.9.3版本开始。因此,set_by_lua_filecontent_by_lua_fileaccess_by_lua_file引用的Lua文件将不被缓存,所有使用的Lua模块都将从头开始加载。有了这个,开发人员可以采用编辑和刷新方式。

但是请注意,编辑内联中的Lua代码时,在nginx.conf中编写的Lua代码,如set_by_luacontent_by_luaaccess_by_luarewrite_by_lua指定的Lua代码将不会被更新,nginx.conf因为只有Nginx配置文件解析器可以正确解析该nginx.conf 文件和唯一的方式是通过发送HUP信号或仅重新启动Nginx 来重新加载配置文件。

启用代码缓存即使,这是由装载Lua的文件dofile或者loadfile 在* _by_lua_file不能被缓存(除非你缓存结果你自己)。通常,您可以使用init_by_luainit_by_lua_file指令加载所有这些文件,也可以使这些Lua文件成为真正的Lua模块并通过它们加载require

ngx_lua模块不支持statApache mod_lua模块可用的模式(尚未)。

禁止使用Lua代码缓存,对于生产使用是非常不鼓励的,只能在开发过程中使用,因为它对整体性能有显着的负面影响。例如,在禁用Lua代码缓存后,“hello world”Lua示例的性能可能会下降一个数量级。

lua_regex_cache_max_entries

语法:lua_regex_cache_max_entries <num>

默认值:lua_regex_cache_max_entries 1024

上下文:http

指定在工作进程级编译的正则表达式高速缓存中允许的最大条目数。

如果指定了正则表达式选项o(即编译一次的标志),则ngx.re.match,ngx.re.gmatch,ngx.re.sub和ngx.re.gsub中使用的正则表达式将缓存在此缓存中。

允许的默认条目数为1024,当达到此限制时,新的正则表达式将不被缓存(就好像未指定o选项),并且在error.log文件中将只有一个,只有一个警告:

2011/08/27 23:18:26 [warn] 31997#0:* 1 lua超过正则表达式缓存最大条目(1024),...
如果通过加载resty.core.regex模块(或resty.core模块)来使用lua-resty-core的ngx.re. *实现,则在此使用的正则表达式缓存使用LRU缓存。

不要为正在生成的正则表达式(和/或替换ngx.re.sub和ngx.re.gsub的字符串参数)激活o选项,并产生无限变化以避免达到指定的限制。

init_by_lua

语法:init_by_lua <lua-script-str>

上下文:http

phase:loading-config

警告自从v0.9.17发行版以来,不鼓励使用此指令; 请改用新的init_by_lua_block指令。

当Nginx主进程(如果有的话)加载Nginx配置文件时,运行全局Lua VM级别上的参数<lua-script-str>指定的Lua代码。

当Nginx收到HUP信号并开始重新加载配置文件时,Lua VM也将被重新创建,并且init_by_lua将在新的Lua VM上再次运行。 如果lua_code_cache指令关闭(默认为on),则init_by_lua处理程序将在每个请求上运行,因为在此特殊模式下,始终为每个请求创建独立的Lua VM。

通常可以通过这个钩子注册(true)Lua全局变量或在服务器启动时预加载Lua模块。 以下是预先加载Lua模块的示例:

init_by_lua 'cjson = require "cjson"';
server {
    listen 80;
    server_name 127.0.0.1;
    charset utf8;
    default_type text/html; location = /api {
        content_by_lua_block {
         ngx.say(cjson.encode({name = 'tinywan', age = 24}))
        }
    }
}

访问输出结果:

image

您也可以在此阶段初始化lua_shared_dict shm存储。 这是一个例子:

# 定义一个字典
lua_shared_dict fruit 1m;
init_by_lua_block{
     local fruit = ngx.shared.fruit;
     fruit:set("apple", 88)
}
server {
    listen 80;
    server_name 127.0.0.1;
    charset utf8;
    default_type text/html;

    location = /api2 {
        content_by_lua_block {
             local fruit = ngx.shared.fruit;
             ngx.say(fruit:get("apple"))
        }
    }
}

访问输出结果:

image

但请注意,lua_shared_dict的shm存储将不会通过配置重新加载(例如通过HUP信号)来清除。所以如果在这种情况下不想在init_by_lua代码中重新初始化shm存储,那么您只需要在shm存储中设置一个自定义标志,并始终检查init_by_lua代码中的标志。

因为在这个上下文中的Lua代码运行在Nginx为其 worker 进程(如果有的话)分配之前,这里加载的数据或代码将享受许多操作系统在所有 worker 进程之间提供的复制(COW)功能,从而节省了很多记忆

在这种情况下不要初始化您自己的Lua全局变量,因为使用Lua全局变量具有性能损失,并可能导致全局命名空间污染(有关更多详细信息,请参阅Lua Variable Scope部分)。推荐的方法是使用适当的Lua模块文件(但是不要使用标准的Lua函数模块()来定义Lua模块,因为它也会污染全局命名空间),并调用require()将您自己的模块文件加载到init_by_lua或其他上下文(require())在Lua注册表中的全局package.loaded表中缓存加载的Lua模块,因此您的模块将仅为整个Lua VM实例加载一次)。

在这种情况下,仅支持一小部分用于Lua的Nginx API

日志API:ngx.log 和print,
共享字典API:ngx.shared.DICT。
在未来的用户请求的情况下,可以支持更多用于Lua的Nginx API。

基本上,您可以安全地使用在这种情况下阻止I / O的Lua库,因为在服务器启动期间阻止主进程完全正常。即使Nginx内核在配置加载阶段也阻止I / O(至少在解析上游的主机名称)。

您应该非常小心您在此上下文注册的Lua代码中的潜在安全漏洞,因为Nginx主进程通常在root帐户下运行。

该指令首先在v0.5.5版本中引入。

/dev/shm/是linux下一个非常有用的目录,因为这个目录不在硬盘上,而是在内存里。因此在linux下,就不需要大费周折去建ramdisk,直接使用/dev/shm/就可达到很好的优化效果。

在linux下,它默认最大为内存的一半大小,使用df -h命令可以看到

image

参考:Linux目录下/dev/shm的理解和使用

init_by_lua_block

 init_by_lua_block {
     print("I need no extra escaping here, for example: \r\nblah")
 }

init_by_lua_file

init_by_lua_file "/Lua/lua_project_v0.01/application/demo/cjson.lua";

init_worker_by_lua

语法:init_worker_by_lua <lua-script-str>

上下文:http

阶段:starting-worker

警告自从v0.9.17发行版以来,不鼓励使用此指令; 请改用新的init_worker_by_lua_block指令。

在启动主进程时,在每个Nginx工作进程的启动时运行指定的Lua代码。 当主进程被禁用时,该钩子将在init_by_lua *之后运行。

这个钩子通常用于创建每个工作者重复的定时器(通过ngx.timer.at Lua API),用于后端健康检查或其他定时日常工作。 以下是一个例子,

init_worker_by_lua '
     local delay = 3  -- in seconds
     local new_timer = ngx.timer.at
     local log = ngx.log
     local ERR = ngx.ERR
     local check

     check = function(premature) if not premature then
             -- do the health check or other routine work
             local ok, err = new_timer(delay, check) if not ok then log(ERR, "failed to create timer: ", err)
                 return
             end
         end
     end

     local ok, err = new_timer(delay, check) if not ok then log(ERR, "failed to create timer: ", err)
         return
     end ';

init_worker_by_lua_block

语法:init_worker_by_lua_block {lua-script}

上下文:http

阶段:起始人

与init_worker_by_lua指令类似,除了该伪指令直接在一对花括号({})中内联Lua源,而不是在NGINX字符串文字中(需要特殊字符转义)。例如:

lua_shared_dict healthcheck 1m;
    lua_socket_log_errors off;
    init_worker_by_lua_block {
        local hc = require "resty.upstream.healthcheck" local ok, err = hc.spawn_checker{
                shm = "healthcheck",
                upstream = "websocket_proxy",
                type = "http",

                http_req = "GET /health.txt HTTP/1.0\r\nHost: websocket_proxy\r\n\r\n",

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

                http_req = "GET /health.txt HTTP/1.0\r\nHost: workerman_proxy\r\n\r\n",

                interval = 2000,
                timeout = 1000,
                fall = 3,
                rise = 2,
                valid_statuses = {200, 302},
                concurrency = 10,
         }
    }

以上为一个后台健康状态的检查,详细配置https://github.com/Tinywan/Lua-Nginx-Redis/blob/master/Openresty/lua-resty-upstream-healthcheck.md

set_by_lua

语法:set_by_lua res <lua-script-str> [ arg1 $ arg2 ...]

上下文:服务器,服务器if,位置,位置if

阶段:重写

警告自从v0.9.17发行版以来,不鼓励使用此指令;请改用新的set_by_lua_block指令。

使用可选的输入参数arg1 arg2 ...执行<lua-script-str>中指定的代码,并将字符串输出返回给$ res。 <lua-script-str>中的代码可以进行API调用,并可以从ngx.arg表中检索输入参数(索引从1开始,依次增加)。

该指令旨在执行短,快速运行的代码块,因为在代码执行期间Nginx事件循环被阻止。因此应避免耗时的代码序列。

该指令通过将自定义命令注入到标准ngx_http_rewrite_module的命令列表中来实现。因为ngx_http_rewrite_module在其命令中不支持非阻塞I / O,因此需要产生当前Lua“light thread”的Lua API在此指令中无法工作。

至少以下API功能目前在set_by_lua的上下文中被禁用:

输出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。
另外,请注意,这个指令一次只能写出一个Nginx变量的值。但是,可以使用ngx.var.VARIABLE接口进行解决。

location /set_by_lua_test {
    set $diff ''; # we have to predefine the $diff variable here
    set_by_lua $sum '
        local a = 32 local b = 56 ngx.var.diff = a - b;  -- write to $diff directly
        return a + b;          -- return the $sum value normally ';
    echo "sum = $sum, diff = $diff";
}

测试结果:

image

set_by_lua_file

语法:set_by_lua_file $res <path-to-lua-script-file> [$arg1 $arg2 ...]

上下文: server, server if, location, location if

作用时期: 重写(rewrite

在lua代码中可以实现所有复杂的逻辑,但是要执行速度很快,不要阻塞.

等同于set_by_lua,除了指定的文件<path-to-lua-script-file>包含Lua代码,或者从v0.5.0rc32发行版开始,要执行的Lua / LuaJIT字节码。在该伪指令的<path-to-lua-script-file>参数字符串中支持Nginx可变插值。但是必须特别注意注射攻击。

foo/bar.lua给定一个相对路径时,在启动Nginx服务器时,它们将被转换为相对于server prefix-p PATH命令行选项确定的路径的绝对路径。当Lua代码缓存打开时(默认情况下),用户代码在第一次请求时被加载一次并被缓存,并且每次修改Lua源文件时必须重新加载Nginx配置。Lua代码缓存可以在开发期间通过切换lua_code_cache 暂时禁用offnginx.conf以避免重新加载Nginx。此指令需要ngx_devel_kit模块。

location =/lua_set_args {
    default_type 'text/html';
    set_by_lua_file $num /usr/local/nginx/conf/lua_set_1.lua; echo $num;
}

lua_set_1.lua 添加以下内容:

local uri_args = ngx.req.get_uri_args()
local i = uri_args["i"] or 0 local j = uri_args["j"] or 0
return i + j

测试结果:

curl 'http://localhost/lua_set_args?i=2&j=10'
12

content_by_lua

语法: content_by_lua <lua-script-str>

上下文: location, location if

作用时期: 上下文内容

注:这个指令的使用气馁以下v0.9.17版本。请改用content_by_lua_block指令。

充当“内容处理程序”并执行<lua-script-str>每个请求中指定的Lua代码字符串。Lua代码可以进行API调用,并且作为独立全局环境(即沙箱)中的新生成的协同程序来执行。不要在同一位置使用此指令和其他内容处理程序指令。例如,此伪指令和proxy_pass伪指令不应在同一位置使用。

nginx.conf配置:

lua_package_path "/usr/local/nginx/lua/?.lua;;";  #lua 模块 
#include lua.conf; #单独lua配置     
server {
    listen 80;
    server_name  localhost;
    location =/lua {
       content_by_lua '
            ngx.say("Hello Lua!") ';
 }
}

说明:#lua模块路径,多个之间”;”分隔,其中”;;”表示默认搜索路径,默认到/usr/local/nginx下找

输出结果:

root@iZ236j3sofdZ:/usr/local/nginx/conf # curl 'http://localhost/lua'
Hello Lua!

rewrite_by_lua_file

语法: rewrite_by_lua_file <path-to-lua-script-file>

上下文:http, server, location, location if

作用时期: 上下文内容

作用:执行内部URL重写或者外部重定向,典型的如伪静态化的URL重写。其默认执行在rewrite处理阶段的最后。

概述:

相当于rewrite_by_lua,除了指定的文件<path-to-lua-script-file>包含Lua代码,或者从v0.5.0rc32发行版开始,要执行的Lua / LuaJIT字节码

Nginx变量可以在<path-to-lua-script-file>字符串中使用以提供灵活性。但这有一些风险,通常不推荐。

foo/bar.lua给定一个相对路径时,在启动Nginx服务器时,它们将被转换为相对于server prefix-p PATH命令行选项确定的路径的绝对路径。

当Lua代码缓存打开时(默认情况下),用户代码在第一次请求时被加载一次并被缓存,并且每次修改Lua源文件时必须重新加载Nginx配置。Lua代码缓存可以在开发期间通过切换lua_code_cache 暂时禁用offnginx.conf以避免重新加载Nginx。

rewrite_by_lua_file代码将总是在结束时运行rewrite,除非请求处理相rewrite_by_lua_no_postpone被接通。

动态分派的文件路径支持Nginx变量,就像content_by_lua_file中一样

Example # 1

location /rewrite_by_lua_file {
    default_type "text/html";
    rewrite_by_lua_file /usr/local/nginx/conf/lua/test_rewrite_1.lua; echo "no rewrite";
}

test_rewrite_1.lua 添加一下内容:

if ngx.req.get_uri_args()["jump"] == "1" 
  then return ngx.redirect("http://www.jd.com?jump=1", 302) 
end 

当我们请求http://192.168.1.2/lua_rewrite_1时发现没有跳转,

image

而请求http://192.168.1.2/lua_rewrite_1?jump=1时发现跳转到京东首页了。 此处需要301/302跳转根据自己需求定义。

image

Example # 2

location /lua_rewrite_3 {
    default_type "text/html";
    rewrite_by_lua_file /usr/local/nginx/conf/lua/test_rewrite_3.lua; echo "rewrite3 uri : $uri";
}

test_rewrite_3.lua 添加一下内容:

if ngx.req.get_uri_args()["jump"] == "1" then  
   ngx.req.set_uri("/lua_rewrite_4", true);  
   ngx.log(ngx.ERR, "=========")  
   ngx.req.set_uri_args({a = 1, b = 2}); 
end

ngx.req.set_uri(uri, true):可以内部重写uri,即会发起新的匹配location请求,等价于 rewrite ^ /lua_rewrite_4 last;此处看error log是看不到我们记录的log。

所以请求如http://localhost/lua_rewrite_3?jump=1会到新的location中得到响应,此处没有/lua_rewrite_4,所以匹配到/lua请求,得到类似如下的响应

root@iZ236j3sofdZ:/usr/local/nginx/conf/lua # curl 'http://localhost/lua_rewrite_3?jump=1'
Hello Lua!
root@iZ236j3sofdZ:/usr/local/nginx/conf/lua # curl 'http://localhost/lua_rewrite_3?jump=2'
rewrite3 uri : /lua_rewrite_3

即这样:

rewrite ^ /lua_rewrite_3;             等价于  ngx.req.set_uri("/lua_rewrite_3", false);
rewrite ^ /lua_rewrite_3 break;       等价于  ngx.req.set_uri("/lua_rewrite_3", false); 加 if/else判断/break/return rewrite ^ /lua_rewrite_4 last;        等价于  ngx.req.set_uri("/lua_rewrite_4", true);

注意,在使用rewrite_by_lua时,开启rewrite_log on;后也看不到相应的rewrite log。

access_by_lua_file

语法: access_by_lua_file <path-to-lua-script-file>

上下文:http, server, location, location if

作用时期: access tail

作用:用于访问控制,比如我们只允许内网ip访问,可以使用如下形式

location /lua_access_1 {
    default_type "text/html";
    access_by_lua_file /usr/local/nginx/conf/lua/lua_access_1.lua; echo "access_ ";
}

lua_access_1.lua 添加以下内容:

if ngx.req.get_uri_args()["token"] ~= "123" then
 return ngx.exit(403) 
end 

测试输出:

root@iZ236j3sofdZ:/usr/local/nginx/conf/lua # curl 'http://localhost/lua_access_1?token=123'
access_ 

root@iZ236j3sofdZ:/usr/local/nginx/conf/lua # curl 'http://localhost/lua_access_1?token=1234'
<html>
  <head><title>403 Forbidden</title></head>
  <body bgcolor="white">
    <center><h1>403 Forbidden</h1></center>
    <hr><center>nginx/1.7.9</center>
  </body>
</html>

即如果访问如http://localhost/lua_access?token=234将得到403 Forbidden的响应。这样我们可以根据如cookie/用户token来决定是否有访问权限。

在执行Redis写入数据的时候,出现一下错误:

root@iZ236j3sofdZ:/usr/local/nginx/conf/lua # curl 'http://localhost/lua_redis_basic'
set msg error : ERR wrong number of arguments for 'set' command
location /lua_redis_basic {
    default_type 'text/html';
    lua_code_cache on; //在这里的缓存是打开的,修改为 lua_code_cache off; 就可以了 
    content_by_lua_file /usr/local/nginx/conf/lua/test_redis_basic.lua; 
}

header_filter_by_lua

语法:header_filter_by_lua <lua-script-str>

上下文:http,服务器,位置,位置如果

phase:output-header-filter

警告自从v0.9.17发行版以来,不鼓励使用此指令; 请改用新的header_filter_by_lua_block指令。

使用<lua-script-str>中指定的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)。
以下是我们的Lua头过滤器中覆盖一个响应头(或者如果不存在的话)的例子:

location =/header_filter_by_lua {
     proxy_pass http://www.tinywan.com;
     header_filter_by_lua 'ngx.header.Names = "Tinywan"';
}

执行结果:

image

header_filter_by_lua_block

语法: header_filter_by_lua_block {lua-script}

上下文: http,服务器,位置,位置如果

phase: output-header-filter

类似于header_filter_by_lua指令,除了该指令直接在一对花括号({})中内联Lua源代码,而不是以NGINX字符串文字(需要特殊字符转义)内。

例如:

header_filter_by_lua_block {
     ngx.header [“content-length”] = nil 
}

body_filter_by_lua

语法: body_filter_by_lua <lua-script-str>

上下文:http, server, location, location if

阶段: 输出体过滤器

注释在发布之后不鼓励使用此指令v0.9.17。改用body_filter_by_lua_block指令。

使用<lua-script-str>指定的Lua代码定义输出体过滤器。

输入数据块通过ngx.arg [1](作为Lua字符串值)传递,表示响应正文数据流结束的“eof”标志通过ngx.arg [2](作为Lua布尔值)。

在幕后,“eof”标志只是Nginx链链接缓冲区的last_buf(用于主要请求)或last_in_chain(用于子请求)标志。(在v0.7.14发布之前,“eof”标志在子请求中完全不起作用。)

可以通过运行以下Lua语句立即中止输出数据流

return ngx.ERROR

这将截断响应体,通常会导致不完整和无效的响应。

Lua代码可以通过用Lua字符串或Lua表的字符串覆盖ngx.arg [1],将自己的输入数据块的修改版本传递给下游的Nginx输出体过滤器。例如,要转换响应正文中的所有小写字母,我们可以写:

location / {
     proxy_pass http://mybackend;
     body_filter_by_lua 'ngx.arg[1] = string.upper(ngx.arg[1])';
 }

当设置nil或空Lua字符串值时ngx.arg[1],根本不会将数据块传递到下游的Nginx输出过滤器。

同样,也可以通过将布尔值设置为ngx.arg [2] 来指定新的“eof”标志。例如

location /t {
     echo hello world;
     echo hiya globe;

     body_filter_by_lua '
         local chunk = ngx.arg[1] if string.match(chunk, "hello") then ngx.arg[2] = true  -- new eof
             return
         end

         -- just throw away any remaining chunk data
         ngx.arg[1] = nil
     ';
 }

也就是说,当身体过滤器看到包含单词“hello”的块时,它将立即将“eof”标志设置为true,导致截断但仍然有效的响应。

当Lua代码可能改变响应体的长度时,需要总是清除Content-Length标题过滤器中的响应标题(如果有的话)来强制执行流输出,如

 location /foo { # fastcgi_pass/proxy_pass/...
     header_filter_by_lua_block { ngx.header.content_length = nil }
     body_filter_by_lua 'ngx.arg[1] = string.len(ngx.arg[1]) .. "\\n"';
 }

请注意,由于NGINX输出过滤器当前实现的限制,以下API功能目前在此上下文中被禁用:

可以为单个请求调用Nginx输出过滤器多次,因为响应主体可能以块形式传送。因此,在此指令中指定的Lua代码也可能在单个HTTP请求的生存期内多次运行。

该指令在v0.5.0rc32发行版中首次引入。

body_filter_by_lua_block

语法: body_filter_by_lua_block {lua-script-str}

上下文: http, server, location, location if

阶段: 输出体过滤器

类似于body_filter_by_lua指令,除了该伪指令直接在一对花括号({})中内嵌Lua源代码,而不是以NGINX字符串文字(需要特殊字符转义)内。

body_filter_by_lua_block { 
  local data, eof = ngx.arg[1], ngx.arg[2]
}

该指令在v0.9.17发行版中首次引入。

body_filter_by_lua_file

语法: body_filter_by_lua_file <path-to-lua-script-file>

上下文:http, server, location, location if

阶段: 输出体过滤器

相当于body_filter_by_lua,除了指定的文件<path-to-lua-script-file>包含Lua代码,或者从v0.5.0rc32发行版中,要执行的Lua / LuaJIT字节码

foo/bar.lua给出相似路径时,它们将在启动Nginx服务器时相对于server prefix-p PATH命令行选项确定的路径变为绝对路径。

该指令在v0.5.0rc32发行版中首次引入。

log_by_lua

语法: log_by_lua <lua-script-str>

上下文:http, server, location, location if

阶段: 日志

注释在发布之后不鼓励使用此指令v0.9.17。请改用log_by_lua_block指令。

<lua-script-str>log请求处理阶段内联Lua源代码。这不会替代当前的访问日志,而是在之前运行。

请注意,此上下文中当前禁用了以下API函数:

以下是收集$ upstream_response_time的平均数据的示例

lua_shared_dict log_dict 5M;
 server {
     location / {
         proxy_pass http://mybackend;
         log_by_lua '
             local log_dict = ngx.shared.log_dict local upstream_time = tonumber(ngx.var.upstream_response_time) local sum = log_dict:get("upstream_time-sum") or 0 sum = sum + upstream_time
             log_dict:set("upstream_time-sum", sum) local newval, err = log_dict:incr("upstream_time-nb", 1) if not newval and err == "not found" then log_dict:add("upstream_time-nb", 0)
                 log_dict:incr("upstream_time-nb", 1) end
         ';
 }
     location = /status {
         content_by_lua_block { local log_dict = ngx.shared.log_dict local sum = log_dict:get("upstream_time-sum") local nb = log_dict:get("upstream_time-nb") if nb and sum then ngx.say("average upstream response time: ", sum / nb, " (", nb, " reqs)") else ngx.say("no data yet") end }
     }
 }

该指令在v0.5.0rc31发行版中首次引入。

image

balancer_by_lua_block

语法: balancer_by_lua_block {lua-script}

上下文: upstream

阶段: content

该指令对由upstream {}配置块定义的任何上游实体运行Lua代码作为上游平衡器。

 upstream foo {
     server 127.0.0.1;
     balancer_by_lua_block { 
        -- use Lua to do something interesting here
         -- as a dynamic balancer
     }
 }

 server {
     location / {
         proxy_pass http://foo;
     }
 }

生成的Lua负载均衡器可以与任何现有的nginx上游模块(如ngx_proxyngx_fastcgi)配合使用

此外,Lua负载均衡器可以使用标准上游连接池机制,即标准保持活动指令。只需确保keepalive伪指令在单个配置块中balancer_by_lua_block伪指令之后使用upstream {}

Lua负载平衡器可以完全忽略upstream {}块中定义的服务器列表,并通过lua- resty -core库中的ngx.balancer模块从完全动态的服务器列表中选择对等体(甚至根据请求进行更改) 。

当nginx上游机制在指令所指定的条件(如proxy_next_upstream 指令)上重试请求时,由此指令注册的Lua代码处理程序可能在单个下游请求中被多次调用。

这个Lua代码执行上下文不支持屈服,因此在这种情况下禁用可能产生的Lua API(如cosockets和“light threads”)。通常可以通过在早期阶段处理程序(如access_by_lua *)中执行此类操作 并通过ngx.ctx表将结果传递到此上下文中来解决此限制

该指令在v0.10.0发行版中首次引入。

lua_shared_dict

语法: lua_shared_dict <name> <size>

默认值:

上下文: http

阶段: 取决于使用

声明一个共享内存区域,<name>作为基于shm的Lua字典的存储空间ngx.shared.<name>

共享内存区域始终由当前nginx服务器实例中的所有nginx工作进程共享。

<size>参数接受大小的单位,如km

http {
      lua_shared_dict dogs 10m ;
     ...
 }

硬编码的最小大小为8KB,实际最小尺寸取决于实际的用户数据集(有些人以12KB开头)。

详见ngx.shared.DICT

该指令在v0.3.1rc22发行版中首次引入。

ngx.shared.DICT.get

语法: value,flags = ngx.shared.DICT:get(key)

上下文: *set_by_lua *,rewrite_by_lua *,access_by_lua *,content_by_lua *,header_filter_by_lua *,body_filter_by_lua *,log_by_lua ,ngx.timer。,balancer_by_lua *,ssl_certificate_by_lua *,ssl_session_fetch_by_lua *,ssl_session_store_by_lua **

检索字典中的价值ngx.shared.DICT的关键key。如果密钥不存在或已经过期,那么nil将被返回。

如果出现错误,nil将返回描述错误的字符串。

返回的值在插入字典时将具有原始数据类型,例如Lua布尔值,数字或字符串。

该方法的第一个参数必须是字典对象本身,例如,

local cats = ngx.shared.cats
 local value, flags = cats.get(cats, "Marry")

或使用Lua的语法糖进行方法调用

local cats = ngx.shared.cats
 local value, flags = cats:get("Marry")

这两种形式基本相同。

如果用户标志是0(默认),则不会返回标志值。

该功能首次在v0.3.1rc22版本中引入。

另请参见ngx.shared.DICT

lua_socket_pool_size

语法:lua_socket_pool_size <size>

默认值:lua_socket_pool_size 30

上下文:http,服务器,位置

指定与每个远程服务器相关联的每个cosocket连接池的大小限制(以连接数计)(即由主机端口对或unix域套接字文件路径标识)。

每个池默认为30个连接。

当连接池超过可用的大小限制时,已经在池中的最近最少使用(空闲)连接将关闭,以为当前连接腾出空间。

请注意,cosocket连接池是每个nginx工作进程,而不是每个nginx服务器实例,因此此处指定的大小限制也适用于每个单个nginx工作进程。

该指令首先在v0.5.0rc1发行版中引入。

lua_socket_keepalive_timeout

语法:lua_socket_keepalive_timeout <time>

默认值:lua_socket_keepalive_timeout 60s

上下文:http,服务器,位置

该指令控制在cosocket内置连接池中连接的默认最大空闲时间。当此超时达到时,空闲连接将被关闭并从池中删除。这个设置可以被cosocket对象的setkeepalive方法所覆盖。

<time>参数可以是整数,具有可选的时间单位,如s(秒),毫秒(毫秒),m(分钟)。默认时间单位为秒,即“秒”。默认设置为60秒。

该指令首先在v0.5.0rc1发行版中引入。

实际使用案例

Redis使用案例

连接池:建立TCP连接需要三次握手而释放TCP连接需要四次握手,而这些往返时延仅需要一次,以后应该复用TCP连接,此时就可以考虑使用连接池,即连接池可以复用连接。 我们只需要将之前的close_redis函数改造为如下即可:

local function close_redis(red) 
    if not red then  
        return  
    end  
    --释放连接(连接池实现) 
    local pool_max_idle_time = 10000 --毫秒 
    local pool_size = 100 --连接池大小 
    local ok, err = red:set_keepalive(pool_max_idle_time, pool_size) 
    if not ok then
     ngx.say("set keepalive error : ", err) 
    end  
end 

即设置空闲连接超时时间防止连接一直占用不释放;设置连接池大小来复用连接。

此处假设调用red:set_keepalive(),连接池大小通过nginx.conf中http部分的如下指令定义:

默认连接池大小,默认30

lua_socket_pool_size 30;

默认超时时间,默认60s

lua_socket_keepalive_timeout 60s;

注意:

1、连接池是每Worker进程的,而不是每Server的;

2、当连接超过最大连接池大小时,会按照LRU算法回收空闲连接为新连接使用;

3、连接池中的空闲连接出现异常时会自动被移除;

4、连接池是通过ip和port标识的,即相同的ip和port会使用同一个连接池(即使是不同类型的客户端如Redis、Memcached);

5、连接池第一次set_keepalive时连接池大小就确定下了,不会再变更;

5、cosocket的连接池http://wiki.nginx.org/HttpLuaModule#tcpsock:setkeepalive

Openresty-Lua动态修改upstream后端服务:https://github.com/Tinywan/Lua-Nginx-Redis/blob/master/Nginx/Nginx-Web/openresty-nginx-lua-Proxy.md

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343