Nginx模块Lua-Nginx-Module学习笔记(⼆)Lua指令详解
(Directives)
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 [ OK ]
* Starting [ OK ]
root@iZ236j3sofdZ:/usr/local/nginx/conf # curl 'localhost/main'
nginx 配置文件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(如和)和Lua模块,
关闭时,ngx_lua提供的每个请求都将在⼀个单独的Lua VM实例中运⾏,从该0.9.3版本开始。因此,,,等的Lua⽂件将不被缓存,所有使⽤的Lua模块都将从头开始加载。有了这个,开发⼈员可以采⽤编辑和刷新⽅式。
但是请注意,编辑内联中的Lua代码时,在f中编写的Lua代码,如,,和的Lua代码将不会被更新,f因为只有Nginx配置⽂件解析器可以正确解析该f ⽂件和唯⼀的⽅式是通过发送HUP信号或仅重新启动Nginx 来重新加载配置⽂件。
启⽤代码缓存即使,这是由装载Lua的⽂件dofile或者loadfile 在* _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(即编译⼀次的标志),则atch,atch,sub和sub中使⽤的正则表达式将缓存在此缓存中。
允许的默认条⽬数为1024,当达到此限制时,新的正则表达式将不被缓存(就好像未指定o选项),并且在error.log⽂件中将只有⼀个,只有⼀个警告:
2011/08/27 23:18:26 [warn] 31997#0:* 1 lua超过正则表达式缓存最⼤条⽬(1024),...
如果通过加载模块(或模块)来使⽤lua-resty-core的 *实现,则在此使⽤的正则表达式缓存使⽤LRU缓存。
不要为正在⽣成的正则表达式(和/或替换sub和sub的字符串参数)激活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.de({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,
共享字典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命令可以看到
参考:
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 / 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 / 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,
}
}
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函数(例如,it )
⼦请求API函数(例如,ngx.location.capture和ngx.location.capture_multi)
Cosocket API函数(例如,p和q.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";
}
测试结果:
set_by_lua_file
语法:set_by_lua_file res<path−to−lua−script−file>[arg1 $arg2 ...]
上下⽂: server, server if, location, location if
作⽤时期:重写(rewrite)
在lua代码中可以实现所有复杂的逻辑,但是要执⾏速度很快,不要阻塞.
等同于,除了指定的⽂件<path-to-lua-script-file>包含Lua代码,或者从v0.5.0rc32发⾏版开始,要执⾏的。在该伪指令的<path-to-lua-script-file>参数字符串中⽀持Nginx可变插值。但是必须特别注意注射攻击。
当foo/bar.lua给定⼀个相对路径时,在启动Nginx服务器时,它们将被转换为相对于server prefix由-p PATH命令⾏选项确定的路径的绝对路径。当Lua代码缓存打开时(默认情况下),⽤户代码在第⼀次请求时被加载⼀次并被缓存,并且每次修改Lua源⽂件时必须重新加载Nginx 配置。Lua代码缓存可以在开发期间通过切换暂时禁⽤off,f以避免重新加载Nginx。此指令需要模块。
location =/lua_set_args {
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论