前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >也谈 ngx.ctx 继承问题

也谈 ngx.ctx 继承问题

作者头像
poslua
发布2019-08-19 14:53:17
1.2K0
发布2019-08-19 14:53:17
举报
文章被收录于专栏:posluaposlua

在前一阵子的 OpenResty Con 2018 上,来自又拍云的 @tokers 分享了他们对 ngx.ctx 的 hack,以确保在发生内部跳转后 ngx.ctx 的信息依旧不会丢失。其实这个 hack 早在去年就被 @tokers 分享到了社区:ngx.ctx inheirt,并且写了一篇文章来详细阐述其思路:对 ngx.ctx 的一次 hack

这回呢,@tokers 重新封装并开源了其实现:lua-resty-ctxdump。关于这个问题,其实 Kong 也遇到过,在 Kong 0.14 版本之前,当发生内部错误时(HTTP status 500),Kong 的 log 阶段插件就无法正常的被执行。无法运行的原因就是,Kong 会将状态码为 4XX5XX 的请求内部跳转到另外一个 location 专门来处理这些异常状态:

error_page 400 404 408 411 412 413 414 417 /kong_error_handler;
error_page 500 502 503 504 /kong_error_handler;

location = /kong_error_handler {
    internal;
    uninitialized_variable_warn off;

    content_by_lua_block {
        kong.handle_error()
    }

    header_filter_by_lua_block {
        kong.header_filter()
    }

    body_filter_by_lua_block {
        kong.body_filter()
    }

    log_by_lua_block {
        kong.log()
    }
}

而 Kong 是将每个 API 启用的插件信息全部保存在 ngx.ctx 中的,所以一旦发生跳转,那么 Kong 自然就无法执行后续的插件了。Kong 针对这个问题也给了两种解决方案(ISSUE-3193):

  1. 去掉 kong_error_handler 禁止内部跳转
  2. 就是利用 @tokers 恢复 ngx.ctx 的方案

最终 Kong 选择了第二种,不过 Kong 的实现和 @tokers 这回开源出来的 lua-resty-ctxdump 有些区别。就是 lua-resty-ctxdump 是将 ngx.ctx 引用在了自身的 memo table 中(Lua Land),也正因为如此,所以其提供的 stash_ngx_ctxapply_ngx_ctx 方法必须成对调用,否则就会产生严重的内存泄漏。

local function ref_in_table(tb, key)
    if key == nil then
        return -1
    end

    local ref = tb[FREE_LIST_REF]
    if ref and ref ~= FREE_LIST_REF then
        tb[FREE_LIST_REF] = tb[ref]
    else
        ref = #tb + 1
    end

    tb[ref] = key
    return ref
end

function _M.stash_ngx_ctx()
    local ctx_ref = ref_in_table(memo, ngx.ctx)
    return ctx_ref
end

function _M.apply_ngx_ctx(ref)
    ref = tonumber(ref)
    if not ref or ref <= FREE_LIST_REF then
        return nil, "bad ref value"
    end

    local old_ngx_ctx = memo[ref]
    -- dereference
    memo[ref] = memo[FREE_LIST_REF]
    memo[FREE_LIST_REF] = ref
    return old_ngx_ctx
end

而 Kong 仅仅是将 ngx.ctx 的引用索引存放在了 ngx.var 中,随后根据这个索引把它从 Lua 的注册表中恢复出来。因为没有额外的索引创建动作,所以也就无需考虑引用释放问题。

function _M.stash_ref()
  local r = getfenv(0).__ngx_req
  if not r then
    ngx.log(ngx.WARN, "could not stash ngx.ctx ref: no request found")
    return
  end

  do
    local ctx_ref = ngx.var.ctx_ref
    if not ctx_ref or ctx_ref ~= "" then
      return
    end
    local _ = ngx.ctx -- load context if not previously loaded
  end

  local ctx_ref = C.ngx_http_lua_ffi_get_ctx_ref(r)
  if ctx_ref == FFI_NO_REQ_CTX then
    ngx.log(ngx.WARN, "could not stash ngx.ctx ref: no ctx found")
    return
  end

  ngx.var.ctx_ref = ctx_ref
end

function _M.apply_ref()
  local r = getfenv(0).__ngx_req
  if not r then
    ngx.log(ngx.WARN, "could not apply ngx.ctx: no request found")
    return
  end

  local ctx_ref = ngx.var.ctx_ref
  if not ctx_ref or ctx_ref == "" then
    return
  end

  ctx_ref = tonumber(ctx_ref)
  if not ctx_ref then
    return
  end

  local orig_ctx = registry.ngx_lua_ctx_tables[ctx_ref]
  if not orig_ctx then
    ngx.log(ngx.WARN, "could not apply ngx.ctx: no ctx found")
    return
  end

  ngx.ctx = orig_ctx
  ngx.var.ctx_ref = ""
end

如果从性能角度来考虑,我觉得 lua-resty-ctxdump 会更有优势,毕竟少了一些获取索引的动作。如果你的业务有大量的内部跳转,建议使用这个方案。同时需要注意的是 stash_ngx_ctxapply_ngx_ctx 方法必须成对调用;如果你的业务和 Kong 类似,只是发生异常才会需要少量跳转,那么建议使用 Kong 的方案, stash_refapply_ref 方法也无需成对调用,也可以省点儿事儿。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-11-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 poslua 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档