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

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

当一个请求发起一个“子请求”的时候,按照 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_file和 content_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_lua, access_by_luarewrite_by_lua指定的Lua代码将不会被更新,nginx.conf因为只有Nginx配置文件解析器可以正确解析该nginx.conf 文件和唯一的方式是通过发送HUP信号或仅重新启动Nginx 来重新加载配置文件。

启用代码缓存即使,这是由装载Lua的文件dofile或者loadfile 在* _by_lua_file不能被缓存(除非你缓存结果你自己)。通常,您可以使用init_by_lua 或init_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 ;
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 = }))
}
}
}

访问输出结果:

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

# 定义一个字典
lua_shared_dict fruit 1m;
init_by_lua_block{
local fruit = ngx.shared.fruit;
fruit:set("apple", )
}
server {
listen ;
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"))
}
}
}

访问输出结果:

但请注意,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命令可以看到

参考: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 = -- 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 = ,
timeout = ,
fall = ,
rise = ,
valid_statuses = {, },
concurrency = ,
}
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 = ,
timeout = ,
fall = ,
rise = ,
valid_statuses = {, },
concurrency = ,
}
}

以上为一个后台健康状态的检查,详细配置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 =
local b = ngx.var.diff = a - b; -- write to $diff directly
return a + b; -- return the $sum value normally
';
echo "sum = $sum, diff = $diff";
}

测试结果:

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 ;
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时发现没有跳转,

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

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='
Hello Lua!
root@iZ236j3sofdZ:/usr/local/nginx/conf/lua # curl 'http://localhost/lua_rewrite_3?jump='
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"';
}

执行结果:

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[]
if string.match(chunk, "hello") then
ngx.arg[] = true -- new eof
return
end -- just throw away any remaining chunk data
ngx.arg[] = 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[], ngx.arg[]
}

该指令在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
sum = sum + upstream_time
log_dict:set("upstream_time-sum", sum) local newval, err = log_dict:incr("upstream_time-nb", )
if not newval and err == "not found" then
log_dict:add("upstream_time-nb", )
log_dict:incr("upstream_time-nb", )
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发行版中首次引入。

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_proxy和 ngx_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 = --毫秒
local pool_size = --连接池大小
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

Nginx模块Lua-Nginx-Module学习笔记(二)Lua指令详解(Directives)的更多相关文章

  1. CDN学习笔记二(技术详解)

    一本好的入门书是带你进入陌生领域的明灯,<CDN技术详解>绝对是带你进入CDN行业的那盏最亮的明灯.因此,虽然只是纯粹的重点抄录,我也要把<CDN技术详解>的精华放上网.公诸同 ...

  2. C#学习笔记二: C#类型详解

    前言 这次分享的主要内容有五个, 分别是值类型和引用类型, 装箱与拆箱,常量与变量,运算符重载,static字段和static构造函数. 后期的分享会针对于C#2.0 3.0 4.0 等新特性进行. ...

  3. IP2——IP地址和子网划分学习笔记之《子网掩码详解》

    2018-05-04 16:21:21   在学习掌握了前面的<进制计数><IP地址详解>这两部分知识后,要学习子网划分,首先就要必须知道子网掩码,只有掌握了子网掩码这部分内容 ...

  4. 【精选】Nginx模块Lua-Nginx-Module学习笔记(二)Lua指令详解(Directives)

    源码地址:https://github.com/Tinywan/Lua-Nginx-Redis Nginx与Lua编写脚本的基本构建块是指令. 指令用于指定何时运行用户Lua代码以及如何使用结果. 下 ...

  5. AngularJS学习笔记二:AngularJS指令

    AngularJS 指令: AngularJS 通过被称为 指令 的新属性来扩展 HTML. AngularJS 指令是扩展的 HTML 属性,带有前缀 ng-. 几个常用 指令: ng-app 指令 ...

  6. jQuery学习笔记之Ajax用法详解

    这篇文章主要介绍了jQuery学习笔记之Ajax用法,结合实例形式较为详细的分析总结了jQuery中ajax的相关使用技巧,包括ajax请求.载入.处理.传递等,需要的朋友可以参考下 本文实例讲述了j ...

  7. [读书笔记]C#学习笔记三: C#类型详解..

    前言 这次分享的主要内容有五个, 分别是值类型和引用类型, 装箱与拆箱,常量与变量,运算符重载,static字段和static构造函数. 后期的分享会针对于C#2.0 3.0 4.0 等新特性进行. ...

  8. 【Java学习笔记之三十三】详解Java中try,catch,finally的用法及分析

    这一篇我们将会介绍java中try,catch,finally的用法 以下先给出try,catch用法: try { //需要被检测的异常代码 } catch(Exception e) { //异常处 ...

  9. MyBatis学习笔记2--配置环境详解

    1.MyBatis-config.xml详解 一个完整的配置文件如下所示 <configuration> <!-- <properties resource="jdb ...

随机推荐

  1. PTA计算平均值(一波三折)

    PTA计算平均值( 一波三折) 现在为若干组整数分别计算平均值. 已知这些整数的绝对值都小于100,每组整数的数量不少于1个,不大于20个. 输入格式:首先输入K(不小于2,不大于20).接下来每一行 ...

  2. synchronized、锁、多线程同步的原理是咋样

    先综述个结论: 一般说的synchronized用来做多线程同步功能,其实synchronized只是提供多线程互斥,而对象的wait()和notify()方法才提供线程的同步功能. 一般说synch ...

  3. 学习jenv

    背景 生活不只是眼前的苟且, 还有诗和远方. 上个月工作需要启动了一个小项目, 按最初的计划会用JDK8. 但当计划报上去后, 运维部门出于后续升级维护的考虑, 不允许使用已经出来4年多的JDK8了, ...

  4. Json的JsonValueProcessor方法

    将对象转换成字符串,是非常常用的功能,尤其在WEB应用中,使用 JSON lib 能够便捷地完成这项工作. JSON lib能够将Java对象转成json格式的字符串,也可以将Java对象转换成xml ...

  5. Go 自学笔记

    1. 最近花时间简单自学了一下go语言的语法..为了保证自己不是每次从0 开始 这次简单进行一下记录 保证 学习 效果. 2. 安装 直接下载go的包 进行安装 以及 暗转goland2018.3 进 ...

  6. [Linux] 关于 history 命令的简单使用

    1. 如果不想记录当前 会话操作的命令的话 可以使用命令 set +o history 但是这个命令有一个坏处, 他自己被记住了... 2. 还有一个办法 就是 在退出登录之前执行命令 history ...

  7. (转)关于ES6的 模块功能 Module 中export import的用法和注意之处

    关于ES6的 模块功能 Module 中export import的用法和注意之处 export default 的用法 export default命令用于指定模块的默认输出.显然,一个模块只能有一 ...

  8. 3 vue-router 的默认hash 改mode:history去除#号 传参

    npm install vue-router --save      //安装 传参

  9. es6 很简单

    es6出了许多好的,优秀的特性.下面列举一些常用的 其实这些特性都很好理解,一两句话就可以表达出来看.主要是对旧的写法的一种改进. function  加了一些语言糖,传参更方便 class      ...

  10. 虚拟机VMware的安装

    什么是虚拟软件: 虚拟软件是一个可以使你在一台机器上同时运行二个或更多Windows.LINUX等系统.它可以模拟一个标准PC环境.这个环境和真实的计算机一样,都有芯片组.CPU.内存.显卡.声卡.网 ...