OpenResty中Lua变量的使用

全局变量

在 OpenResty 里面,只有在 init_by_lua* 和 init_worker_by_lua* 阶段才能定义真正的全局变量。 这是因为其他阶段里面,OpenResty 会设置一个隔离的全局变量表,以免在处理过程污染了其他请求。 即使在上述两个可以定义全局变量的阶段,也尽量避免这么做。全局变量能解决的问题,用模块变量也能解决, 而且会更清晰、更干净。

模块变量

把定义在模块里面的变量称为模块变量。无论定义变量时有没有加 local,有没有通过 _M 把变量引用起来, 定义在模块里面的变量都是模块变量。
在使用Lua语言内置的require方法加载模块,之后就可以在Lua中操作模块变量,加载模块的操作只会执行一次(在同一个nginx worker中),所有的协程都会共享同一份拷贝(包括代码和数据)。基于这种协程间共享技术是高性能Lua应用的基础。

  • Lua协程执行流程:
    OpenResty中Lua变量的使用_第1张图片

注意,这种数据共享的方式是基于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

你可能感兴趣的:(随笔,lua,openresty,开发语言)