lua熱更新學(xué)習(xí)

什么是熱更新,對(duì)于它的理解煎饼,正如云風(fēng)所說的那樣,熱更新更多的用途是做不停機(jī)的 bug 修復(fù)校赤,不應(yīng)用于常規(guī)的版本更新吆玖。對(duì)于熱更新的博客筒溃,網(wǎng)上看了不少,包括云風(fēng)寫的一篇 熱更文章沾乘。也仔細(xì)看了 snax 的熱更部分實(shí)現(xiàn)細(xì)節(jié)怜奖。發(fā)現(xiàn)有不少可以吸取之處。并把核心部分抽取出來翅阵,做個(gè)簡單分享歪玲。
至于怎么個(gè)熱更新法,更新的是哪些內(nèi)容掷匠,我的理解是滥崩,熱更新最好只更新模塊中的一小部分,比如其中的某個(gè)函數(shù)讹语,而不是將這個(gè)模塊都一起更新替換钙皮。盡量做到小改動(dòng),以達(dá)到最終目的顽决。至于更新的思路短条,我歸納為兩點(diǎn):

  1. 將模塊中舊的函數(shù)替換成新的函數(shù),這個(gè)新的函數(shù)可以放到一個(gè)lua文件中擎值,或者以字符串的形式給出慌烧。
  2. 將模塊中舊的函數(shù),當(dāng)前用到的所有上值鸠儿,(什么是上值屹蚊,后面有講到)保存到起來,用于新函數(shù)引用进每,保證新函數(shù)作為模塊中的一部分能夠正確運(yùn)行汹粤。

下面以一個(gè)demo為例,這也是我抽取 snax 模塊中熱更新部分田晚,以及和他人一起探討寫的嘱兼。
目錄結(jié)構(gòu):

./main.lua                調(diào)用 test.lua,做為運(yùn)行文件贤徒,顯示最終運(yùn)行效果
./test.lua                一個(gè)簡單模塊文件芹壕,用于提供熱更新的來源
./test_hot.lua            用于更新替換 test 模塊中的某些函數(shù),更新文件
./hotfix.lua              實(shí)現(xiàn)熱更新機(jī)制
關(guān)系結(jié)構(gòu)圖

通過這幅關(guān)系圖接奈,可以了解到踢涌,test 模塊和 test_hot 之間的關(guān)系,test_hot 負(fù)責(zé)更新 test 模塊中的某些函數(shù)序宦,但更新后的這些函數(shù)依然屬于 test 模塊中的一部分睁壁,并沒有脫離 test 模塊的掌控,而獨(dú)立出來。

test.lua 模塊包含的內(nèi)容

現(xiàn)在我們看看 test.lua 包含了哪些內(nèi)容潘明,分別有 一個(gè)局部變量 index行剂,兩個(gè)函數(shù) print_index,show 钳降,函數(shù)體分別是圓圈1和2厚宰,兩個(gè)函數(shù)都引用到了這個(gè)局部變量 index。
假設(shè)當(dāng)前牲阁,我們想更新替換掉 print_index 函數(shù)固阁,讓其 index 加1 操作,并打印 index 值城菊,那么我們可以在 test_hot.lua 文件中這么寫备燃,見下圖黃色框部分:

test 模塊 print_index 第一次熱更后

我們希望在 print_index 更新后, index 加 1 后凌唬,show 函數(shù)獲取到的 index 值是 1并齐,即把更新函數(shù)也看作是 test.lua 模塊中的一部分。而不應(yīng)該是 index 加 1 后客税,show 函數(shù)獲取到的還是原值 0况褪。

假設(shè)我們希望更新 print_index 后,再一次更新更耻,把 index 值直接設(shè)置為 100测垛,那么它又應(yīng)該是這樣子的,見下圖最左側(cè)黃色部分:

test 模塊 print_index 第n次熱更后

通過這幾幅圖秧均,我們可以大致猜想到食侮,熱更新后,應(yīng)該是個(gè)什么效果目胡。

再談及熱更之前锯七,先要介紹幾過 lua 概念。一個(gè)是 _ENV 環(huán)境變量誉己,一個(gè)是上值 upvalue眉尸。

_ENV

在 lua 程序設(shè)計(jì)一書中有過這樣的解釋,lua 語言并沒有全局變量巨双,所謂的全局變量都是通過某種手段模擬出來的噪猾。

Lua 語言是在一個(gè)名為 _ENV 的預(yù)定義上值(一個(gè)外部的局部變量,upvalue)存在的情況下編譯所有的代碼段的筑累。因此畏妖,所有的變量要么綁定到一個(gè)名稱的局部變量,要么是 _ENV 中的一個(gè)字段疼阔,而 _ENV 本身是一個(gè)局部變量。
例如:
local z = 10
x = 0
y = 1
x = y + z
等價(jià)于
local z = 10
_ENV.x = 0
_ENV.y = 1
_ENV.x = _ENV.y + z

x,y 都是不用 local 聲明婆廊,z 是 local 聲明迅细。
所以,我們用到的全局變量其實(shí)是保存到 _ENV 變量中淘邻。lua 語言在內(nèi)部維護(hù)了一個(gè)表來作用全局環(huán)境(_G)茵典,通常,我們?cè)?load 一個(gè)代碼段宾舅,一個(gè)模塊時(shí)统阿,lua 會(huì)用這個(gè)表(_G)來初始化 _ENV。如果上面的幾行代碼是寫在一個(gè)文件中筹我,那么當(dāng) load 調(diào)用它時(shí)扶平,又會(huì)等價(jià)于:

-- xxx.lua 文件
local _ENV = the global environment (全局環(huán)境)
return function(...)
local z = 10
_ENV.x = 0
_ENV.y = 1
_ENV.x = _ENV.y +z
end

upvalue

當(dāng)一個(gè)局部變量被內(nèi)層的函數(shù)中使用的時(shí)候, 它被內(nèi)層函數(shù)稱作 上值蔬蕊,或是 外部局部變量结澄。引用 Lua 5.3 參考手冊(cè)
例如:
local x = 10
function hello(a, b)
local c = a + b + x
print(c)
end
那么在這段代碼中,hello 函數(shù)的上值有 變量 x岸夯,_ENV麻献,而我們剛剛講到,print 沒有經(jīng)過聲明猜扮,就可以直接使用勉吻,那么它肯定是保存于 _ENV 表中,print(c) 等價(jià)于 _ENV.print(c)旅赢,而變量 a齿桃、b、c 都是做為 hello 函數(shù)的局部變量鲜漩。

了解這個(gè)這個(gè)上值的概率源譬,我們才能在 hotfix 模塊中理解代碼的含義。下面就來看下具體 demo 的實(shí)現(xiàn)孕似。

-- main.lua
local hotfix = require "hotfix"
local test =  require "test"
local test_hot = require "test_hot"

print("before hotfix")
for i = 1, 5 do 
    test.print_index() -- 熱更前踩娘,調(diào)用 print_index,打印 index 的值
end 


hotfix.update(test.print_index, test_hot) -- 收集舊函數(shù)的上值喉祭,用于新函數(shù)的引用养渴,這個(gè)對(duì)應(yīng)之前說的歸納第2小點(diǎn)
test.print_index = test_hot -- 新函數(shù)替換舊的函數(shù),對(duì)應(yīng)之前說的歸納第1小點(diǎn)


print("after hotfix")
for i = 1, 5 do 
    test.print_index() -- 打印更新后的 index 值
end 

test.show() -- show 函數(shù)沒有被熱更泛烙,但它獲取到的 index 值應(yīng)該是 最新的理卑,即 index = 5。

接下來看看 test.lua 模塊內(nèi)容

-- test.lua
local test = {}
local index = 0 

function test.print_index()
    print(index)
end 

function test.show( )
    print("show:", index)
end

return test

再看看 熱更文件 test_hot.lua 內(nèi)容

-- test_hot.lua
local index -- 這個(gè) index 必須聲明蔽氨,不用賦值藐唠,才能夠引用到 test 模塊中的局部變量 index

return function ()  -- 返回一個(gè)閉包函數(shù)帆疟,這個(gè)就是要更新替換后的原型
    index = index + 1
    print(index)
end

最后,再看看 hotfix.lua

-- hotfix.lua
local hotfix = {}

local function collect_uv(f, uv)
    local i = 1
    while true do
        local name, value = debug.getupvalue(f, i)
        if name == nil then -- 當(dāng)所有上值收集完時(shí)宇立,跳出循環(huán)
            break
        end
        
        if not uv[name] then
            uv[name] = { func = f, index = i } -- 這里就會(huì)收集到舊函數(shù) print_index 所有的上值踪宠,包括變量 index
            if type(value) == "function" then
                collect_uv(value, uv)
            end
        end

        i = i + 1
    end
end

local function update_func(f, uv) 
    local i = 1
    while true do
        local name, value = debug.getupvalue(f, i)
        if name == nil then -- 當(dāng)所有上值收集完時(shí),跳出循環(huán)
            break
        end
        -- value 值為空妈嘹,并且這個(gè) name 在 舊的函數(shù)中存在
        if not value and uv[name] then 
            local desc = uv[name]
            -- 將新函數(shù) f 的第 i 個(gè)上值引用舊模塊 func 的第 index 個(gè)上值
            debug.upvaluejoin(f, i, desc.func, desc.index)
        end

         -- 只對(duì) function 類型進(jìn)行遞歸更新柳琢,對(duì)基本數(shù)據(jù)類型(number、boolean润脸、string) 不管
        if type(value) == "function" then
            update_func(value, uv)
        end

        i = i + 1
    end
end

function hotfix.update(old, new)
    local uv = {}
    collect_uv(old, uv)
    update_func(new, uv)
end

return hotfix

這個(gè)用到了 lua 的兩個(gè) api 函數(shù)柬脸,在 Lua 5.3 參考手冊(cè) 中有介紹。

debug.getupvalue (f, up)
此函數(shù)返回函數(shù) f 的第 up 個(gè)上值的名字和值毙驯。 如果該函數(shù)沒有那個(gè)上值倒堕,返回 nil

debug.upvaluejoin (f1, n1, f2, n2)
讓 Lua 閉包 f1 的第 n1 個(gè)上值 引用 Lua 閉包 f2 的第 n2 個(gè)上值尔苦。

我們可以看到涩馆, hotfix.lua 做的事也是比較簡單的,主要是收集 舊函數(shù)的所有上值允坚,更新到新函數(shù)中魂那。最后一步替換舊函數(shù)是在 main.lua 中完成。
最后看看運(yùn)行結(jié)果:

[root@instance test]# lua main.lua
before hotfix
0
0
0
0
0
after hotfix
1
2
3
4
5
-------------
show:   5
在了解了熱更新機(jī)制后稠项,最后來思考幾個(gè)問題
  1. 在熱更文件 test_hot.lua 中涯雅,如果要更新的函數(shù)有很多,那么要聲明的變量就會(huì)有很多展运,這個(gè)繁瑣的事情活逆,應(yīng)該如何解決。
  2. 如果要更新的是 test.lua 中的局部函數(shù)拗胜,而這個(gè)局部函數(shù)又同時(shí)被多個(gè)其他函數(shù)引用到蔗候,改怎么熱更,才能解決其他函數(shù)引用問題埂软。
  3. hotfix.lua 中的 collect_uv 函數(shù)锈遥,目前只對(duì)上值是 function 類型,才繼續(xù)遞歸收集上值勘畔。就有可能會(huì)有一些上值沒辦法繼續(xù)收集到所灸,比如表,在 test.lua 中加入如下內(nèi)容炫七,那么 cmd 中的 show 方法爬立,就沒辦法收集到。
...
local cmd = {}
function cmd.show() 

end

function test.getcmd(name)
     local c = cmd[name]
      if c then c() and
end
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末万哪,一起剝皮案震驚了整個(gè)濱河市侠驯,隨后出現(xiàn)的幾起案子抡秆,更是在濱河造成了極大的恐慌,老刑警劉巖吟策,帶你破解...
    沈念sama閱讀 212,080評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件琅轧,死亡現(xiàn)場離奇詭異,居然都是意外死亡踊挠,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,422評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門冲杀,熙熙樓的掌柜王于貴愁眉苦臉地迎上來效床,“玉大人,你說我怎么就攤上這事权谁∈L矗” “怎么了?”我有些...
    開封第一講書人閱讀 157,630評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵旺芽,是天一觀的道長沪猴。 經(jīng)常有香客問我,道長采章,這世上最難降的妖魔是什么运嗜? 我笑而不...
    開封第一講書人閱讀 56,554評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮悯舟,結(jié)果婚禮上担租,老公的妹妹穿的比我還像新娘。我一直安慰自己抵怎,他們只是感情好奋救,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,662評(píng)論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著反惕,像睡著了一般尝艘。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上姿染,一...
    開封第一講書人閱讀 49,856評(píng)論 1 290
  • 那天背亥,我揣著相機(jī)與錄音,去河邊找鬼盔粹。 笑死隘梨,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的舷嗡。 我是一名探鬼主播轴猎,決...
    沈念sama閱讀 39,014評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼进萄!你這毒婦竟也來了捻脖?” 一聲冷哼從身側(cè)響起锐峭,我...
    開封第一講書人閱讀 37,752評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎可婶,沒想到半個(gè)月后沿癞,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,212評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡矛渴,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,541評(píng)論 2 327
  • 正文 我和宋清朗相戀三年椎扬,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片具温。...
    茶點(diǎn)故事閱讀 38,687評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蚕涤,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出铣猩,到底是詐尸還是另有隱情揖铜,我是刑警寧澤,帶...
    沈念sama閱讀 34,347評(píng)論 4 331
  • 正文 年R本政府宣布达皿,位于F島的核電站天吓,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏峦椰。R本人自食惡果不足惜龄寞,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,973評(píng)論 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望们何。 院中可真熱鬧萄焦,春花似錦、人聲如沸冤竹。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,777評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽鹦蠕。三九已至冒签,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間钟病,已是汗流浹背萧恕。 一陣腳步聲響...
    開封第一講書人閱讀 32,006評(píng)論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留肠阱,地道東北人票唆。 一個(gè)月前我還...
    沈念sama閱讀 46,406評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像屹徘,于是被迫代替她去往敵國和親走趋。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,576評(píng)論 2 349