Lua 5.2/5.3 熱更新小結(jié)

Lua熱更新實(shí)現(xiàn)

用途

在生產(chǎn)環(huán)境上锰镀,總有可能出現(xiàn)不可預(yù)知的Bug斟赚,而通常修改好Bug僅僅又修改幾句着降,停機(jī)維護(hù)的成本又太高,對(duì)于游戲來(lái)說(shuō)拗军,通常每個(gè)服就是單獨(dú)的進(jìn)程任洞,也做不到像分布式環(huán)境下,關(guān)掉一部分機(jī)器发侵,先升級(jí)一部分交掏,再升級(jí)另一部分的無(wú)縫升級(jí)。這時(shí)候如果有熱更就可以迅速的把Bug修復(fù)方案通過(guò)熱更新進(jìn)行修復(fù)刃鳄,不會(huì)對(duì)用戶任何的影響盅弛。例如:

  1. 業(yè)務(wù)邏輯有Bug
  2. 配置的數(shù)據(jù)有誤
  3. 需求發(fā)生變更

熱更新的原則

1、熱更新不破壞原有數(shù)據(jù)

熱更新更新的基本內(nèi)容就是更新服務(wù)的邏輯,通常只是邏輯發(fā)生變化挪鹏,但原有的值并不能被改變见秽,例如:

local a = 1
function get_a()
    return a
end

此時(shí),我們調(diào)用get_a()返回是的1讨盒,我們將熱更成

local a = 2
function get_a()
    print("get_a function")
    return a
end

此時(shí)我們改變了a的初始值解取,但我們并不知道之前服務(wù)a的值是不是被重新賦過(guò)值,假設(shè)熱更前a的值仍然為1返顺,那么我們熱更后調(diào)用get_a()返回的應(yīng)該是1禀苦,而不應(yīng)受新的初始值影響,而且同能打印出了"get_a function"遂鹊,這時(shí)候則認(rèn)為熱更正常振乏。

2、不為熱更新寫更多的代碼

熱更新可以通過(guò)很多種方法實(shí)現(xiàn)秉扑,比如說(shuō)模塊為了支持?jǐn)?shù)據(jù)不變的特性慧邮,需要在模塊里額外寫一些代碼來(lái)記錄舊值,熱更新之后再把舊值copy過(guò)來(lái)邻储,或者用一些特殊的語(yǔ)法來(lái)支撐赋咽。這種方法將會(huì)對(duì)項(xiàng)目增加很多的負(fù)擔(dān)旧噪,而且一旦發(fā)生意料之外的Bug吨娜,熱更系統(tǒng)幾乎處于半癱瘓狀態(tài)。應(yīng)該來(lái)說(shuō)淘钟,代碼原本該怎么實(shí)現(xiàn)就怎么實(shí)現(xiàn)宦赠,對(duì)于99%的lua代碼都是支持的,不需要修改來(lái)迎合熱更新米母。通常熱更新不改變?cè)凶兞恐档念愋汀?/p>

熱更新的實(shí)現(xiàn)勾扭,代碼適用于5.2以上

原理

利用_ENV環(huán)境,在加載的時(shí)候把數(shù)據(jù)加載到_ENV下铁瞒,然后再通過(guò)對(duì)比的方式修改_G底下的值妙色,從而實(shí)現(xiàn)熱更新,函數(shù)

function hotfix(chunk, check_name)

定義env的table慧耍,并為env設(shè)置_G訪問(wèn)權(quán)限身辨,然后調(diào)用load實(shí)現(xiàn)把數(shù)據(jù)重新加載進(jìn)來(lái)

local env = {}
setmetatable(env, { __index = _G })
local _ENV = env
local f, err = load(chunk, check_name,  't', env)
assert(f,err)
local ok, err = pcall(f)
assert(ok,err)

此時(shí)env我們可以得到新函數(shù)有變更的部分,我們替換的為可見變量芍碧,也就是可直接訪問(wèn)的變量

for name,value in pairs(env) do
    local g_value = _G[name]
    if type(g_value) ~= type(value) then
        _G[name] = value
    elseif type(value) == 'function' then
        update_func(value, g_value, name, 'G'..'  ')
        _G[name] = value
    elseif type(value) == 'table' then
        update_table(value, g_value, name, 'G'..'  ')
    end
end

通過(guò)env當(dāng)前的值和_G當(dāng)前的值進(jìn)行對(duì)比

  1. 如果類型不同我們直接覆蓋原值煌珊,此時(shí)value不為nil,不會(huì)出現(xiàn)原則被覆蓋成nil的情況
  2. 如果當(dāng)前值為函數(shù)泌豆,我們進(jìn)行函數(shù)的upvalue值比對(duì)
function update_func(env_f, g_f, name, deep)
    --取得原值所有的upvalue定庵,保存起來(lái)
    local old_upvalue_map = {}
    for i = 1, math.huge do
        local name, value = debug.getupvalue(g_f, i)
        if not name then break end
        old_upvalue_map[name] = value
    end
    --遍歷所有新的upvalue,根據(jù)名字和原值對(duì)比,如果原值不存在則進(jìn)行跳過(guò)蔬浙,如果為其它值則進(jìn)行遍歷env類似的步驟
    for i = 1, math.huge do
        local name, value = debug.getupvalue(env_f, i)
        if not name then break end
        local old_value = old_upvalue_map[name]
        if old_value then
            if type(old_value) ~= type(value) then
                debug.setupvalue(env_f, i, old_value)
            elseif type(old_value) == 'function' then
                update_func(value, old_value, name, deep..'  '..name..'  ')
            elseif type(old_value) == 'table' then
                update_table(value, old_value, name, deep..'  '..name..'  ')
                debug.setupvalue(env_f, i, old_value)
            else
                debug.setupvalue(env_f, i, old_value)
            end
        end
    end
end
  1. 如果當(dāng)前值為table猪落,我們遍歷table值進(jìn)行對(duì)比
local protection = {
    setmetatable = true,
    pairs = true,
    ipairs = true,
    next = true,
    require = true,
    _ENV = true,
}
--防止重復(fù)的table替換,造成死循環(huán)
local visited_sig = {}
function update_table(env_t, g_t, name, deep)
    --對(duì)某些關(guān)鍵函數(shù)不進(jìn)行比對(duì)
    if protection[env_t] or protection[g_t] then return end
    --如果原值與當(dāng)前值內(nèi)存一致畴博,值一樣不進(jìn)行對(duì)比
    if env_t == g_t then return end
    local signature = tostring(g_t)..tostring(env_t)
    if visited_sig[signature] then return end
    visited_sig[signature] = true
    --遍歷對(duì)比值许布,如進(jìn)行遍歷env類似的步驟
    for name, value in pairs(env_t) do
        local old_value = g_t[name]
        if type(value) == type(old_value) then
            if type(value) == 'function' then
                update_func(value, old_value, name, deep..'  '..name..'  ')
                g_t[name] = value
            elseif type(value) == 'table' then
                update_table(value, old_value, name, deep..'  '..name..'  ')
            end
        else
            g_t[name] = value
        end
    end
    --遍歷table的元表,進(jìn)行對(duì)比
    local old_meta = debug.getmetatable(g_t)
    local new_meta = debug.getmetatable(env_t)
    if type(old_meta) == 'table' and type(new_meta) == 'table' then
        update_table(new_meta, old_meta, name..'s Meta', deep..'  '..name..'s Meta'..'  ' )
    end
end

更新

1绎晃、可以調(diào)用hotfix_file對(duì)整個(gè)文件進(jìn)行熱更

function hotfix_file(name)
    local file_str
    local fp = io.open(name)
    if fp then
        io.input(name)
        file_str = io.read('*all')
        io.close(fp)
    end

    if not file_str then
        return -1
    end
    return hotfix(file_str, name)
end

2蜜唾、可以通過(guò)hotfix進(jìn)行代碼的更新

function hotfix(chunk, check_name)

關(guān)于坑

這里有一個(gè)注意事項(xiàng),lua的module模塊庶艾,如:

module("AA", package.seeall)

當(dāng)我們加載lua模塊的時(shí)候袁余,這時(shí)候這個(gè)模塊信息并不像初始化全局代碼一樣,就算提前設(shè)置了package.loaded["AA"] = nil, 也不會(huì)出現(xiàn)在env中同時(shí)也不會(huì)調(diào)用_G的__newindex函數(shù)咱揍,也就是說(shuō)env["AA"]為空颖榜,故這種寫法無(wú)法進(jìn)行熱更新,所以通常模塊的寫法改成如下

--定義模塊AA
AA = {}
--相當(dāng)于package.seeall
setmetatable(AA, {__index = _G})
--環(huán)境隔離
local _ENV = AA

參考

源碼td_rlua

項(xiàng)目使用TDEngine

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末煤裙,一起剝皮案震驚了整個(gè)濱河市掩完,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌硼砰,老刑警劉巖且蓬,帶你破解...
    沈念sama閱讀 206,839評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異题翰,居然都是意外死亡恶阴,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門豹障,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)冯事,“玉大人,你說(shuō)我怎么就攤上這事血公£墙觯” “怎么了?”我有些...
    開封第一講書人閱讀 153,116評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵累魔,是天一觀的道長(zhǎng)摔笤。 經(jīng)常有香客問(wèn)我,道長(zhǎng)薛夜,這世上最難降的妖魔是什么籍茧? 我笑而不...
    開封第一講書人閱讀 55,371評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮梯澜,結(jié)果婚禮上寞冯,老公的妹妹穿的比我還像新娘渴析。我一直安慰自己,他們只是感情好吮龄,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評(píng)論 5 374
  • 文/花漫 我一把揭開白布俭茧。 她就那樣靜靜地躺著,像睡著了一般漓帚。 火紅的嫁衣襯著肌膚如雪母债。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,111評(píng)論 1 285
  • 那天尝抖,我揣著相機(jī)與錄音毡们,去河邊找鬼。 笑死昧辽,一個(gè)胖子當(dāng)著我的面吹牛衙熔,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播搅荞,決...
    沈念sama閱讀 38,416評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼红氯,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了咕痛?” 一聲冷哼從身側(cè)響起痢甘,我...
    開封第一講書人閱讀 37,053評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎茉贡,沒想到半個(gè)月后塞栅,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,558評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡块仆,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評(píng)論 2 325
  • 正文 我和宋清朗相戀三年构蹬,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了王暗。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片悔据。...
    茶點(diǎn)故事閱讀 38,117評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖俗壹,靈堂內(nèi)的尸體忽然破棺而出科汗,到底是詐尸還是另有隱情,我是刑警寧澤绷雏,帶...
    沈念sama閱讀 33,756評(píng)論 4 324
  • 正文 年R本政府宣布头滔,位于F島的核電站,受9級(jí)特大地震影響涎显,放射性物質(zhì)發(fā)生泄漏坤检。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評(píng)論 3 307
  • 文/蒙蒙 一期吓、第九天 我趴在偏房一處隱蔽的房頂上張望早歇。 院中可真熱鬧,春花似錦、人聲如沸箭跳。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)谱姓。三九已至借尿,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間屉来,已是汗流浹背路翻。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工桑腮, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留莺琳,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,578評(píng)論 2 355
  • 正文 我出身青樓吧雹,卻偏偏與公主長(zhǎng)得像嘹黔,于是被迫代替她去往敵國(guó)和親账嚎。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容