全局变量
在 OpenResty 里面,只有在 init_by_lua* 和 init_worker_by_lua* 阶段才能定义真正的全局变量。 这是因为其他阶段里面,OpenResty 会设置一个隔离的全局变量表,以免在处理过程污染了其他请求。 即使在上述两个可以定义全局变量的阶段,也尽量避免这么做。全局变量能解决的问题,用模块变量也能解决, 而且会更清晰、更干净。
模块变量
把定义在模块里面的变量称为模块变量。无论定义变量时有没有加 local,有没有通过 _M 把变量引用起来, 定义在模块里面的变量都是模块变量。
在使用Lua语言内置的require方法加载模块,之后就可以在Lua中操作模块变量,加载模块的操作只会执行一次(在同一个nginx worker中),所有的协程都会共享同一份拷贝(包括代码和数据)。基于这种协程间共享技术是高性能Lua应用的基础。
- Lua协程执行流程:
注意,这种数据共享的方式是基于Nginx Worker内运行的Lua协程,不能跨Worker之间的进程边界。
在使用这种方式共享时,仅推荐共享只读数据,当计算过程中没有非阻塞性 I/O 操作时(包括 ngx.sleep), 也可以在 nginx worker 进程内所有并发请求中共享可改变的数据。如果出现阻塞操作,会导致协程切换,共享可变数据则会造成数据错乱。
- 共享只读数据
-- mydata.lua
local _M = {}
local data = {
dog = 3,
cat = 4,
pig = 5,
}
function _M.get_age(name)
return data[name]
end
return _M
nginx.conf配置中加载:
location /lua {
content_by_lua_block {
local mydata = require "mydata"
ngx.say(mydata.get_age("dog"))
}
}
- 共享可变数据
-- var.lua
local count = 1
local _M = {}
local function add()
count = count + 1
end
local function sub()
count = count - 1
end
function _M.calc()
add()
-- 模拟协程调度
ngx.sleep(ngx.time()%0.003)
sub()
return count
end
return _M
-- index.lua
local var = require "var"
if var.calc() == 1 then
ngx.say("ok")
else
ngx.status = ngx.HTTP_INTERNAL_SERVER_ERROR
ngx.say("error")
end
nginx.conf中引用:
location = /index {
content_by_lua_file conf/lua/index.lua;
}
以上的示例在单个客户端和两个客户端请求下的测试情况如下:
[root@localhost wrk]# ./wrk -t 1 -c 1 -d 1s http://localhost:6666/index
Running 1s test @ http://192.168.170.150:6666/index
1 threads and 1 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 552.73us 661.80us 2.54ms 78.76%
Req/Sec 4.41k 8.22k 20.27k 80.00%
4370 requests in 1.00s, 0.85MB read
Requests/sec: 4348.51
Transfer/sec: 866.19KB
[root@localhost wrk]# ./wrk -t 2 -c 2 -d 1s http://localhost:6666/index
Running 1s test @ http://192.168.170.150:6666/index
2 threads and 2 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 440.60us 610.98us 2.70ms 80.39%
Req/Sec 9.60k 11.81k 27.40k 70.00%
19113 requests in 1.00s, 3.78MB read
Non-2xx or 3xx responses: 2729
Requests/sec: 19043.00
Transfer/sec: 3.76MB
在两个客户端访问时出现部分请求错误,那些出现异常的请求Lua协程调度执行过程大概如下:
coroutine A | coroutine B | count |
---|---|---|
add | 2 | |
sleep | 2 | |
add | 3 | |
sleep | 3 | |
sub | 2 |
(2 != 1) => HTTP_INTERNAL_SERVER_ERROR!
本地变量
跟全局变量、模块变量相对,把 *_by_lua* 里面定义的变量称之为本地变量。 本地变量仅在当前阶段有效,如果要跨阶段使用,需要借助 ngx.ctx 或者附加在模块变量里。
注意的是 ngx.timer.*。虽然 timer 代码占的是别的上下文的位置,但是每个 timer 都是运行在自己的协程里面, 里面定义的变量都是协程内部的。
举个例子,让我们在 init_worker_by_lua_block 里面定义一个 timer。
init_worker_by_lua_block {
local delay = 5
local handler
handler = function()
counter = counter or 0
counter = counter + 1
ngx.log(ngx.ERR, counter)
local ok, err = ngx.timer.at(delay, handler)
if not ok then
ngx.log(ngx.ERR, "failed to create the timer: ", err)
return
end
end
local ok, err = ngx.timer.at(delay, handler)
if not ok then
ngx.log(ngx.ERR, "failed to create the timer: ", err)
return
end
}
以上的counter虽然没有local修饰,并且定义在init_worker_by_lua*中,但是运行后会发现counter输出的都是1,主要是因为counter定义在handler这个函数内部,每个timer都运行在一个独立的协程里,timer的每次触发,都会重新把counter定义一遍。
参考资料
https://moonbingbing.gitbooks.io/openresty-best-practices/content/ngx_lua/lua-variable-scope.html
https://github.com/openresty/lua-nginx-module#data-sharing-within-an-nginx-worker