对 ngx.ctx 的一次 Hack

栏目: Lua · 发布时间: 6年前

内容简介:对 ngx.ctx 的一次 Hack
ngx.ctxlua-nginx-module 提供的一个充满魔力的 Lua table,它可以存放任何我们想要存放的内容,生命周期贯穿整个 location ,也正因为生命周期局限在单个 location 里,所以当发生内部跳转(例如通过 ngx.exec )之后,之前的 ngx.ctx

将被销毁。所以很多时候,我们不得不转而使用 ngx.var.VARIABLE 来替代 ngx.ctx ,例如我们需要在 log 阶段的时候收集之前准备好的字段,然后发送到日志服务器或者 nsq 等组件。

然而,事物总是具有两面性, ngx.var.VARIABLE 生命周期虽然贯穿于一个请求,但是其代价却更加昂贵,它具有计算 hash 值,查找 hash 表,分配内存等等操作,这相比于 ngx.ctx 实在是繁重得多了。通过观察火焰图,大量的使用 ngx.var.VARIABLE 已经成为了一个瓶颈。于是才有了对 ngx.ctx ,或者说 ngx.exec 的一次 hack 过程。

ngx.ctx

既然要对 ngx.ctx 进行 hack,首先需要了解 ngx.ctx 的机制,事实上, ngx.ctx 就是一个普通的 Lua table, lua-nginx-module 创建一个 table 之后,将其存放在 Lua 的注册表里,利用 luaL_ref 来索引每个 ngx.ctx ,利用 luaL_unref 来解除索引。这个索引,是被存放在 lua-nginx-module 的模块上下文里的,也就是 ngx_http_lua_ctx_s::ctx_ref 这个成员变量。

为什么经过内部跳转,ngx.ctx 会被销毁

Nginx 核心在进行内部跳转的时候,会把对应请求所有的模块上下文全部清除,可以参考函数 ngx_http_internal_redirect ,所以 lua-nginx-modulectx_ref 也会被销毁。在 lua-nginx-module 关于 ngx.exec 的源码里也可以看到对 ngx.ctx 的解索引过程。

Hack it

了解了它的机制之后,我们可以试着来绕过这种限制,既然 lua-nginx-module 利用一个数字来索引 ngx.ctx ,我们也可以主动创建一个索引,将它存在一个介质里,只要这个介质不随着内部跳转而消失即可(例如 Nginx 变量就是一个非常好的选择),等到内部跳转完成之后,第一时间将 ngx.ctx 恢复出来即可,下面来介绍下这个过程。

首先我们需要一个变量

set ctx_ref "";

设计一个函数,创建一个新的索引

function _M.stash_ngx_ctx()
	local ctxs = registry.ngx_lua_ctx_tables
 	local ctx_ref = base.ref_in_table(ctxs, ngx.ctx)
    ngx.var.ctx_ref = tostring(ctx_ref)
end

registry 就是 Lua 的注册表,通过下面的方法获得。

local debug = require "debug"
local registry = debug.getregistry()

所有请求的 ngx.ctx 放置在一张表里,这张表存放在注册表里,key 就是 "ngx_http_lua_ctx_tables" ,所以上述代码里的 ctxs 就是存放所有请求的 ngx.ctx 的那张表了。

local ctx_ref = base.ref_in_table(ctxs, ngx.ctx)

这行代码给 ngx.ctx 创建了一个新的索引,关于具体的细节,大家有兴趣可以查看 lua-resty-corebase.ref_in_table ,这个函数的原理和 luaL_ref 一致。

拿到索引之后,将它存放到我们的变量即可。至此,当前请求的 ngx.ctx 就存在 2 个索引了(一个索引由 lua-nginx-module 管理,另外一个则由我们自己管理)。

执行完内部跳转后,恢复跳转前的 ngx.ctx

function _M.apply_ngx_ctx()
	local ctx_ref = tonumber(ngx.var.ctx_ref)
 	if not ctx_ref then
		return
	end
 
 	local ctxs = registry.ngx_lua_ctx_tables
 	local origin_ngx_ctx = ctxs[ctx_ref]
 	ngx.ctx = origin_ngx_ctx

 	local FREE_LIST_REF = 0
 	ctxs[ctx_ref] = ctxs[FREE_LIST_REF]
 	ctxs[FREE_LIST_REF] = ctx_ref
 	ngx.var.ctx_ref = ""
 end

我们通过存放在变量的 ctx_ref 来得到执行内部跳转前的 ngx.ctx 表,接着需要把我们自己管理的这个索引解除,否则会造成严重的内存泄漏!

local FREE_LIST_REF = 0
	ctxs[ctx_ref] = ctxs[FREE_LIST_REF]
	ctxs[FREE_LIST_REF] = ctx_ref

这三行代码即完成了解索引(和 LuaL_unref 一直),这里简单解释下, LuaL_unref 管理索引的时候,用 0 这个 index 记录上一次解索引的 index(为 nil 则表示目前还没有过解索引的操作),所以上述两行代码,实际上就是在当前需要解索引的 index 处记录了上一次解索引的 index,然后在 0 下标处记录当前最新的 index,有点像链表。这样操作有什么好处呢?当下次需要产生索引的时候,可以首先检查 0 下标,看看是否有解过索引的位置,如果有,复用即可,否则需要返回 #table + 1 ,所以利用这个 “链表”,可以避免很多 Lua table 扩大,导致内存拷贝,影响到性能。

后续

  • 这两个函数的代码已经经过充分测试,目前已经运行在我们的一个项目当中。
  • 另外,这类基础的 Hack 操作,不适合存放在业务态,由调用者自己控制,因为这两个函数必须成对调用,否则就会造成内存泄漏。
  • 使用之后,强烈建议进行压测,确认没有内存泄漏的隐患。
  • 如果你有更多的 idea,可以给我发送邮件(zchao1995@gmail.com)。

以上所述就是小编给大家介绍的《对 ngx.ctx 的一次 Hack》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

蚂蚁金服

蚂蚁金服

廉薇、边慧、苏向辉、曹鹏程 / 中国人民大学出版社 / 2017-7 / 59.00

打开支付宝,我们不但可以用手机即时付款,给好友转账,为信用卡还款,购买水、电、天然气,还可以办理出国购物退税;因为余额宝,我们可以开始打理手中的零用钱,随时随地进行理财;因为芝麻信用,我们感受到信用为我们带来的信任、尊严与方便——免押金租车、租房、骑行。从支付宝到蚂蚁金服,可以毫不夸张地说,一家企业改变了我们的生活。 蚂蚁金服无疑是目前中国最具代表性的金融科技巨头,同时也是全球估值最高的金融......一起来看看 《蚂蚁金服》 这本书的介绍吧!

MD5 加密
MD5 加密

MD5 加密工具

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器