Skip to Content

ngx ctx bug

Posted on 3 mins read

复现

在 “init_worker_by_lua” 阶段定义方法 init_vanilla,在 content_by_lua 阶段来调用,使用 local registry = debug.getregistry(); print_r(registry.ngx_lua_ctx_tables) 查看当前存储的所有 key,发现前后两个请求的数据会同时保存在 ngx.ctx 中,按理说 ngx.ctx 表就是用来共享请求内变量的,应该是每个请求有自己的一份,为什么在一个请求中能获取到其他请求的 ngx.ctx ?这个需要细节了解下 ngx.ctx 的实现机制,再回来解析这个问题。

init_vanilla 函数的实现

	init_vanilla = function (ngx)
    Registry.namespace = ngx_var.APP_NAME

    local REQ_Registry = require('registry'):new()

    REQ_Registry['REQ_URI'] = ngx_var.uri
    REQ_Registry['REQ_ARGS'] = ngx_var.args
    REQ_Registry['REQ_ARGS_ARR'] = ngx_req.get_uri_args()
    REQ_Registry['REQ_HEADERS'] = ngx_req.get_headers()
    REQ_Registry['APP_CACHE_PURGE'] = REQ_Registry['REQ_ARGS_ARR']['vapurge']
    ngx.ctx.REQ_Registry = REQ_Registry


    if Registry['VANILLA_INIT'] then return end
    Registry['VA_ENV'] = ngx_var.VA_ENV
    Registry['APP_NAME'] = Registry.namespace
    Registry['APP_ROOT'] = ngx_var.document_root
    Registry['APP_HOST'] = ngx_var.host
    Registry['APP_PORT'] = ngx_var.server_port
    Registry['VANILLA_ROOT'] = ngx_var.VANILLA_ROOT
    Registry['VANILLA_VERSION'] = ngx_var.VANILLA_VERSION

    Registry['VANILLA_APPLICATION'] = LoadV 'vanilla.v.application'
    Registry['VANILLA_UTILS'] = LoadV 'vanilla.v.libs.utils'
    Registry['VANILLA_CACHE_LIB'] = LoadV 'vanilla.v.cache'
    Registry['VANILLA_COOKIE_LIB'] = LoadV 'vanilla.v.libs.cookie'

    Registry['APP_CONF'] = LoadApp 'config.application'
    Registry['APP_BOOTS'] = LoadApp 'application.bootstrap'
    Registry['APP_PAGE_CACHE_CONF'] = Registry['APP_CONF']['page_cache']
    LoadSysConf()
    Registry['VANILLA_INIT'] = true
end

相关数据的打印

    local REQ_Registry = ngx.ctx.REQ_Registry
    local redis_instance = redis()

    local debug = require "debug"
    local registry = debug.getregistry()
    print_r('------x1x-------')
    print_r(REQ_Registry['REQ_ARGS_ARR'])
    ngx.sleep(5)
    print_r(REQ_Registry['REQ_ARGS_ARR'])
    print_r(REQ_Registry['APP_PAGE_CACHE_KEY'])
    print_r(registry.ngx_lua_ctx_tables)
    print_r('------x2x-------')

对应的结果打印

curl -m 20 --no-keepalive -i 'http://10.211.55.15:9110/?uid=1'

"------x1x-------"
{
  uid = "1"
}
{
  uid = "1"
}
"/uid=1"
{
  {
    REQ_Registry = {
      APP_PAGE_CACHE_KEY = "/uid=1",
      _data = {
      },
      REQ_URI = "/",
      USE_PAGE_CACHE = true,
      COOKIES = false,
      set = "function: 0x4100e118",
      has = "function: 0x4100e0f8",
      REQ_HEADERS = {
        host = "10.211.55.15:9110",
        accept = "*/*",
        ["user-agent"] = "curl/7.29.0"
      },
      REQ_ARGS_ARR = {
        uid = "1"
      },
      REQ_ARGS = "uid=1",
      dump = "function: 0x4100e138",
      namespace = "vanilla_app",
      del = "function: 0x4100e0b8",
      get = "function: 0x4100e0d8"
    },
    xxxxxxxxx = "xxxxxxxxxxx",
    zhou = "xxxx--->3<-----xxx0000-----19902",
    xxxxxdddddxxxx = "xxxxxxxxxxx"
  },
  {
    REQ_Registry = {
      APP_PAGE_CACHE_KEY = "/uid=2",
      _data = {
      },
      REQ_URI = "/",
      USE_PAGE_CACHE = true,
      COOKIES = false,
      set = "function: 0x4100e118",
      has = "function: 0x4100e0f8",
      REQ_HEADERS = {
        host = "10.211.55.15:9110",
        accept = "*/*",
        ["user-agent"] = "curl/7.29.0"
      },
      REQ_ARGS_ARR = {
        uid = "2"
      },
      REQ_ARGS = "uid=2",
      dump = "function: 0x4100e138",
      namespace = "vanilla_app",
      del = "function: 0x4100e0b8",
      get = "function: 0x4100e0d8"
    },
    xxxxxxxxx = "xxxxxxxxxxx",
    zhou = "xxxx--->3<-----xxx0000-----19902",
    xxxxxdddddxxxx = "xxxxxxxxxxx"
  }
}
"------x2x-------"

curl -m 20 --no-keepalive -i 'http://10.211.55.15:9110/?uid=2'

"------x1x-------"
{
  uid = "2"
}
{
  uid = "2"
}
"/uid=2"
{
  [0] = 1,
  [2] = {
    REQ_Registry = {
      APP_PAGE_CACHE_KEY = "/uid=2",
      _data = {
      },
      REQ_URI = "/",
      USE_PAGE_CACHE = true,
      COOKIES = false,
      set = "function: 0x4100e118",
      has = "function: 0x4100e0f8",
      REQ_HEADERS = {
        host = "10.211.55.15:9110",
        accept = "*/*",
        ["user-agent"] = "curl/7.29.0"
      },
      REQ_ARGS_ARR = {
        uid = "2"
      },
      REQ_ARGS = "uid=2",
      dump = "function: 0x4100e138",
      namespace = "vanilla_app",
      del = "function: 0x4100e0b8",
      get = "function: 0x4100e0d8"
    },
    xxxxxxxxx = "xxxxxxxxxxx",
    zhou = "xxxx--->3<-----xxx0000-----19902",
    xxxxxdddddxxxx = "xxxxxxxxxxx"
  }
}
"------x2x-------"

关于 ngx.ctx

ngx.ctx 表主要用来存放每个请求共享的 Lua 上下文变量,它跟 Nginx 变量一样有与当前请求一致的生命周期 ngx.ctx 表在请求各个阶段(rewrite、access、content 等)是共享的,每个请求,包括子请求都有自己的一份 ngx.ctx 拷贝,对子请求 ngx.ctx 表的修改不会影响“父请求” 中的数据,他们有相互独立的 ngx.ctx 内部重定(ngx.exec)向会销毁 ngx.ctx,新的请求将会有一个全新的 ngx.ctx 表,例如:

 location /new {
     content_by_lua_block {
         ngx.say(ngx.ctx.foo)
     }
 }

 location /orig {
     content_by_lua_block {
         ngx.ctx.foo = "hello"
         ngx.exec("/new")
     }
 }

上面 GET 请求 /orig 返回结果 nil,而不是 hello 任意的值,包括 Lua 闭包、嵌套表等都可以放入 ngx.ctx,同样支持注册自定义的元方法,也可以使用 Lua table 来覆盖 ngx.ctx,例如:ngx.ctx = { foo = 32, bar = 54 } 当在 init_worker_by_lua* 中使用 ngx.ctx 时,ngx.ctx 的生命周期与当前 Lua 句柄一致,在其他阶段将无法引用到这些值 ngx.ctx 的检索需要相对昂贵的元方法调用,这比通过用户自己的函数参数直接传递基于请求的数据要慢得多。所以不要为了节约用户函数参数而滥用此 API,因为它可能对性能有明显影响。 因为 metamethod magic,永远都不要在 Lua 模块的 Lua 方法范围外来试图 “local” “ngx.ctx”,因为 Lua 模块是 “worker-level data sharing” 的,例如下面的做法是不可取的(无形中错误的扩展到了 “per-nginx-worker” 共享):

 -- mymodule.lua
 local _M = {}

 -- the following line is bad since ngx.ctx is a per-request
 -- data while this <code>ctx</code> variable is on the Lua module level
 -- and thus is per-nginx-worker.
 local ctx = ngx.ctx

 function _M.main()
     ctx.foo = "bar"
 end

 return _M

下面是推荐的使用方式(调用方显示的传递 ctx 表)

 -- mymodule.lua
 local _M = {}

 function _M.main(ctx)
     ctx.foo = "bar"
 end

 return _M
comments powered by Disqus