摘要:于是才有了對(duì),或者說(shuō)的一次過(guò)程。另外,這類(lèi)基礎(chǔ)的操作,不適合存放在業(yè)務(wù)態(tài),由調(diào)用者自己控制,因?yàn)檫@兩個(gè)函數(shù)必須成對(duì)調(diào)用,否則就會(huì)造成內(nèi)存泄漏。使用之后,強(qiáng)烈建議進(jìn)行壓測(cè),確認(rèn)沒(méi)有內(nèi)存泄漏的隱患。
緣起
ngx.ctx 是 lua-nginx-module 提供的一個(gè)充滿(mǎn)魔力的 Lua table,它可以存放任何我們想要存放的內(nèi)容,生命周期貫穿整個(gè) location,也正因?yàn)樯芷诰窒拊趩蝹€(gè) location 里,所以當(dāng)發(fā)生內(nèi)部跳轉(zhuǎn)(例如通過(guò) ngx.exec)之后,之前的 ngx.ctx
將被銷(xiāo)毀。所以很多時(shí)候,我們不得不轉(zhuǎn)而使用 ngx.var.VARIABLE 來(lái)替代 ngx.ctx,例如我們需要在 log 階段的時(shí)候收集之前準(zhǔn)備好的字段,然后發(fā)送到日志服務(wù)器或者 nsq 等組件。
然而,事物總是具有兩面性,`ngx.var.VARIABLE` 生命周期雖然貫穿于一個(gè)請(qǐng)求,但是其代價(jià)卻更加昂貴,它具有計(jì)算 `hash` 值,查找 `hash` 表,分配內(nèi)存等等操作,這相比于 `ngx.ctx` 實(shí)在是繁重得多了。通過(guò)觀察火焰圖,大量的使用 `ngx.var.VARIABLE` 已經(jīng)成為了一個(gè)瓶頸。于是才有了對(duì) `ngx.ctx`,或者說(shuō) `ngx.exec` 的一次 hack 過(guò)程。ngx.ctx
既然要對(duì) ngx.ctx 進(jìn)行 hack,首先需要了解 ngx.ctx 的機(jī)制,事實(shí)上,ngx.ctx 就是一個(gè)普通的 Lua table,lua-nginx-module 創(chuàng)建一個(gè) table 之后,將其存放在 Lua 的注冊(cè)表里,利用 luaL_ref 來(lái)索引每個(gè) ngx.ctx,利用 luaL_unref 來(lái)解除索引。這個(gè)索引,是被存放在 lua-nginx-module 的模塊上下文里的,也就是 ngx_http_lua_ctx_s::ctx_ref 這個(gè)成員變量。
為什么經(jīng)過(guò)內(nèi)部跳轉(zhuǎn),ngx.ctx 會(huì)被銷(xiāo)毀
Nginx 核心在進(jìn)行內(nèi)部跳轉(zhuǎn)的時(shí)候,會(huì)把對(duì)應(yīng)請(qǐng)求所有的模塊上下文全部清除,可以參考函數(shù) ngx_http_internal_redirect,所以 lua-nginx-module 的 ctx_ref 也會(huì)被銷(xiāo)毀。在 lua-nginx-module 關(guān)于 ngx.exec 的源碼里也可以看到對(duì) ngx.ctx 的解索引過(guò)程。
Hack it了解了它的機(jī)制之后,我們可以試著來(lái)繞過(guò)這種限制,既然 lua-nginx-module 利用一個(gè)數(shù)字來(lái)索引 ngx.ctx,我們也可以主動(dòng)創(chuàng)建一個(gè)索引,將它存在一個(gè)介質(zhì)里,只要這個(gè)介質(zhì)不隨著內(nèi)部跳轉(zhuǎn)而消失即可(例如 Nginx 變量就是一個(gè)非常好的選擇),等到內(nèi)部跳轉(zhuǎn)完成之后,第一時(shí)間將 ngx.ctx 恢復(fù)出來(lái)即可,下面來(lái)介紹下這個(gè)過(guò)程。
首先我們需要一個(gè)變量
set ctx_ref "";
設(shè)計(jì)一個(gè)函數(shù),創(chuàng)建一個(gè)新的索引
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 的注冊(cè)表,通過(guò)下面的方法獲得。
local debug = require "debug" local registry = debug.getregistry()
所有請(qǐng)求的 ngx.ctx 放置在一張表里,這張表存放在注冊(cè)表里,key 就是 "ngx_http_lua_ctx_tables",所以上述代碼里的 ctxs 就是存放所有請(qǐng)求的 ngx.ctx 的那張表了。
local ctx_ref = base.ref_in_table(ctxs, ngx.ctx)
這行代碼給 ngx.ctx 創(chuàng)建了一個(gè)新的索引,關(guān)于具體的細(xì)節(jié),大家有興趣可以查看 lua-resty-core 的 base.ref_in_table,這個(gè)函數(shù)的原理和 luaL_ref 一致。
拿到索引之后,將它存放到我們的變量即可。至此,當(dāng)前請(qǐng)求的 ngx.ctx 就存在 2 個(gè)索引了(一個(gè)索引由 lua-nginx-module 管理,另外一個(gè)則由我們自己管理)。
執(zhí)行完內(nèi)部跳轉(zhuǎn)后,恢復(fù)跳轉(zhuǎn)前的 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
我們通過(guò)存放在變量的 ctx_ref 來(lái)得到執(zhí)行內(nèi)部跳轉(zhuǎn)前的 ngx.ctx 表,接著需要把我們自己管理的這個(gè)索引解除,否則會(huì)造成嚴(yán)重的內(nèi)存泄漏!
local FREE_LIST_REF = 0 ctxs[ctx_ref] = ctxs[FREE_LIST_REF] ctxs[FREE_LIST_REF] = ctx_ref
這三行代碼即完成了解索引(和 LuaL_unref 一直),這里簡(jiǎn)單解釋下, LuaL_unref 管理索引的時(shí)候,用 0 這個(gè) index 記錄上一次解索引的 index(為 nil 則表示目前還沒(méi)有過(guò)解索引的操作),所以上述兩行代碼,實(shí)際上就是在當(dāng)前需要解索引的 index 處記錄了上一次解索引的 index,然后在 0 下標(biāo)處記錄當(dāng)前最新的 index,有點(diǎn)像鏈表。這樣操作有什么好處呢?當(dāng)下次需要產(chǎn)生索引的時(shí)候,可以首先檢查 0 下標(biāo),看看是否有解過(guò)索引的位置,如果有,復(fù)用即可,否則需要返回 #table + 1,所以利用這個(gè) “鏈表”,可以避免很多 Lua table 擴(kuò)大,導(dǎo)致內(nèi)存拷貝,影響到性能。
后續(xù)這兩個(gè)函數(shù)的代碼已經(jīng)經(jīng)過(guò)充分測(cè)試,目前已經(jīng)運(yùn)行在我們的一個(gè)項(xiàng)目當(dāng)中。
另外,這類(lèi)基礎(chǔ)的 Hack 操作,不適合存放在業(yè)務(wù)態(tài),由調(diào)用者自己控制,因?yàn)檫@兩個(gè)函數(shù)必須成對(duì)調(diào)用,否則就會(huì)造成內(nèi)存泄漏。
使用之后,強(qiáng)烈建議進(jìn)行壓測(cè),確認(rèn)沒(méi)有內(nèi)存泄漏的隱患。
如果你有更多的 idea,可以給我發(fā)送郵件([email protected])。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/40543.html
摘要:內(nèi)核代表進(jìn)程來(lái)執(zhí)行信號(hào)處理器函數(shù),當(dāng)處理器返回時(shí),主程序會(huì)在處理器被中斷的位置恢復(fù)執(zhí)行。進(jìn)程信號(hào)掩碼內(nèi)核會(huì)為每個(gè)進(jìn)程維護(hù)一個(gè)信號(hào)掩碼。這個(gè)競(jìng)態(tài)條件發(fā)生在主程序和信號(hào)處理器對(duì)同一個(gè)被解除信號(hào)的競(jìng)爭(zhēng)關(guān)系。 運(yùn)營(yíng)研發(fā)團(tuán)隊(duì) 季偉濱 一、前言 眾所周如,Nginx是多進(jìn)程架構(gòu)。有1個(gè)master進(jìn)程和N個(gè)worker進(jìn)程,一般N等于cpu的核數(shù)。另外, 和文件緩存相關(guān),還有cache mana...
摘要:結(jié)構(gòu)體數(shù)組,用來(lái)表示該模塊可以在配置文件中配置的項(xiàng)目,及其操作指令。 源文件路徑 srccore gx_conf_file.h srccore gx_conf_file.c 主要內(nèi)容 本篇的主要目的在于分析Nginx的配置功能。由于Nginx的配置基本就是對(duì)模塊的配置,因此,在討論配置功能之前,需要先分析Nginx的模塊功能。 對(duì)于模塊功能,這里的重點(diǎn)不在于某個(gè)模塊的細(xì)節(jié),而...
摘要:限流算法最簡(jiǎn)單粗暴的限流算法就是計(jì)數(shù)器法了,而比較常用的有漏桶算法和令牌桶算法計(jì)數(shù)器計(jì)數(shù)器法是限流算法里最簡(jiǎn)單也是最容易實(shí)現(xiàn)的一種算法。 運(yùn)營(yíng)研發(fā)團(tuán)隊(duì) 李樂(lè) 高并發(fā)系統(tǒng)有三把利器:緩存、降級(jí)和限流; 限流的目的是通過(guò)對(duì)并發(fā)訪(fǎng)問(wèn)/請(qǐng)求進(jìn)行限速來(lái)保護(hù)系統(tǒng),一旦達(dá)到限制速率則可以拒絕服務(wù)(定向到錯(cuò)誤頁(yè))、排隊(duì)等待(秒殺)、降級(jí)(返回兜底數(shù)據(jù)或默認(rèn)數(shù)據(jù)); 高并發(fā)系統(tǒng)常見(jiàn)的限流有:限制總并發(fā)...
摘要:如果當(dāng)前需要延遲處理,又會(huì)把請(qǐng)求放到定時(shí)器中,等到定時(shí)器過(guò)期以后,執(zhí)行寫(xiě)事件回調(diào),這個(gè)函數(shù)里會(huì)執(zhí)行,重新進(jìn)行的個(gè)階段。 ngx_http_limit_req_module 是 Nginx 官方提供的一個(gè) http 模塊,它工作在 NGX_HTTP_PREACCESS_PHASE 階段,通過(guò)在 nginx.conf 中進(jìn)行簡(jiǎn)單地配置,我們可以輕易地對(duì)請(qǐng)求速率進(jìn)行限制。 配置指令 官方文檔...
摘要:找到這個(gè)模塊的指令后,則會(huì)調(diào)用這個(gè)指令的解析回調(diào)函數(shù)即結(jié)構(gòu)體的第三個(gè)參數(shù)來(lái)進(jìn)行處理。調(diào)用他們上面提到的中的回調(diào)函數(shù)來(lái)申請(qǐng)和初始化對(duì)應(yīng)模塊的配置結(jié)構(gòu)體。需要注意的是,即時(shí)當(dāng)前是直接在塊級(jí)別,這三個(gè)回調(diào)函數(shù)都會(huì)被調(diào)用。拒絕暴力枚舉式編寫(xiě)配置文件 原博:https://blog.coordinate35.cn/... 熱身 首先來(lái)看下這幾個(gè)小例子: 第一個(gè)例子: server { l...
閱讀 2185·2021-11-24 09:39
閱讀 2806·2021-07-29 13:49
閱讀 2331·2019-08-29 14:15
閱讀 2247·2019-08-29 12:40
閱讀 3326·2019-08-26 13:42
閱讀 646·2019-08-26 12:13
閱讀 2079·2019-08-26 11:41
閱讀 3356·2019-08-23 18:32