在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
源码地址:https://github.com/Tinywan/Lua-Nginx-RedisNginx与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代码缓存 关闭时,ngx_lua提供的每个请求都将在一个单独的Lua VM实例中运行,从该 但是请注意,编辑内联中的Lua代码时,在nginx.conf中编写的Lua代码,如set_by_lua,content_by_lua, access_by_lua和rewrite_by_lua指定的Lua代码将不会被更新, 启用代码缓存即使,这是由装载Lua的文件 ngx_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),... 不要为正在生成的正则表达式(和/或替换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}))
}
}
}
访问输出结果:
您也可以在此阶段初始化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"))
}
}
}
访问输出结果:
但请注意,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, 基本上,您可以安全地使用在这种情况下阻止I / O的Lua库,因为在服务器启动期间阻止主进程完全正常。即使Nginx内核在配置加载阶段也阻止I / O(至少在解析上游的主机名称)。 您应该非常小心您在此上下文注册的Lua代码中的潜在安全漏洞,因为Nginx主进程通常在root帐户下运行。 该指令首先在v0.5.5版本中引入。 /dev/shm/是linux下一个非常有用的目录,因为这个目录不在硬盘上,而是在内存里。因此在linux下,就不需要大费周折去建ramdisk,直接使用/dev/shm/就可达到很好的优化效果。 在linux下,它默认最大为内存的一半大小,使用df -h命令可以看到
init_by_lua_block init_by_lua_block {
print("I need no extra escaping here, for example: \r\nblah")
}
init_by_lua_fileinit_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) 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";
}
测试结果:
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,除了指定的文件 当 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 作用时期: 上下文内容 注:这个指令的使用气馁以下 充当“内容处理程序”并执行 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,除了指定的文件 Nginx变量可以在 当 当Lua代码缓存打开时(默认情况下),用户代码在第一次请求时被加载一次并被缓存,并且每次修改Lua源文件时必须重新加载Nginx配置。Lua代码缓存可以在开发期间通过切换lua_code_cache 暂时禁用 该 动态分派的文件路径支持Nginx变量,就像content_by_lua_file中一样。 Example # 1location /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 # 2location /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_
即如果访问如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) 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指令,除了该指令直接在一对花括号( 例如: 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 阶段: 输出体过滤器 注释在发布之后不鼓励使用此指令 使用 输入数据块通过ngx.arg [1](作为Lua字符串值)传递,表示响应正文数据流结束的“eof”标志通过ngx.arg [2](作为Lua布尔值)。 在幕后,“eof”标志只是Nginx链链接缓冲区的 可以通过运行以下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])';
}
当设置 同样,也可以通过将布尔值设置为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代码可能改变响应体的长度时,需要总是清除 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请求的生存期内多次运行。 该指令在 body_filter_by_lua_block语法: body_filter_by_lua_block {lua-script-str} 上下文: http, server, location, location if 阶段: 输出体过滤器 类似于body_filter_by_lua指令,除了该伪指令直接在一对花括号( body_filter_by_lua_block {
local data, eof = ngx.arg[1], ngx.arg[2]
}
该指令在 body_filter_by_lua_file语法: body_filter_by_lua_file <path-to-lua-script-file> 上下文:http, server, location, location if 阶段: 输出体过滤器 相当于body_filter_by_lua,除了指定的文件 当 该指令在 log_by_lua语法: log_by_lua <lua-script-str> 上下文:http, server, location, location if 阶段: 日志 注释在发布之后不鼓励使用此指令
请注意,此上下文中当前禁用了以下API函数:
|
请发表评论