小蝌蚪傳記:nodejs線上模塊熱部署原理與實現

晚十二點,牀上javascript

富婆:這是給你的500元,辛苦了前端

小蝌蚪:不辛苦,主人java

富婆:我老公要回來了,你走吧node

小蝌蚪:是,主人git

說完小蝌蚪從三樓別墅跳了下去程序員

。。。github

小蝌蚪是一名程序員,也是一個技師算法

白天敲代碼,晚上捏腳npm

因爲常年敲代碼,因此指法高超後端

時常把客戶按到上天日龍

。。。

富婆年輕、有錢,可是缺愛

由於嫁給了80歲老公

小蝌蚪聽話活好不粘人

深得富婆的歡心

。。。

又一次見面

富婆:好無聊,趕忙討好我

小蝌蚪:主人,我用手指在您腿上敲一段「Dijkstra最短路徑算法」吧

富婆:這招你用過了

小蝌蚪:那我用胸毛在您背上默寫《javascript高級程序設計》

富婆:不想

小蝌蚪:那我用舌頭給您表演一段口技

富婆:舌頭?口技?

小蝌蚪:不要多想,是正規表演

因而小蝌蚪表演了一段舌頭碎大石

富婆大喜,掌聲經久不衰

。。。

表演完畢,富婆面色泛紅

富婆:我喜歡你

小蝌蚪:我也喜歡我本身

富婆:你愛我嗎

小蝌蚪:我不能對客戶動心

富婆:我要如何才能獲得你的心

小蝌蚪:殺了我,把心掏出來,嘻嘻

富婆:嘻嘻

。。。

富婆:對了,下週來參加個人閨蜜派對

小蝌蚪:不帶你老公去嗎

富婆:他80歲了,帶去丟臉

小蝌蚪:那我可要加錢噢

富婆:多少

小蝌蚪:200塊錢兩小時,物美價廉,服務一流

富婆:deal

。。。

閨蜜派對

晚八點,高級酒店,各類炫富

閨蜜A:我買了個包,才10萬,好便宜哦~

閨蜜B:我包了三個小奶狗,讓他們打了一夜鬥地主

閨蜜C:我老公養了好多小動物,好比寶馬、路虎、捷豹

富婆默默上前:我在北京二環買了套四合院

其餘閨蜜瞬間不說話了

。。。

小蝌蚪意識到

這不是一個普通的飯局

而是一場閨蜜之間的裝逼盛宴

小蝌蚪:你須要我作什麼

富婆:我須要你戰勝她們的男寵

。。。

酒後三巡

到了炫耀男寵的階段

每一個閨蜜都帶來了本身包養的男寵

有肌肉男、高級鴨、男模等等

肌肉男上臺就展現八塊腹肌和沙包同樣大的褲襠

男模直接亮出大長腿和帥到掉渣的臉

高級鴨把舌頭伸出來,在臺上發出「略略略略略...」的聲音

男寵們各有所長

贏得了場下閨蜜們的掌聲

。。。

輪到小蝌蚪上臺

一身屎綠色格子襯衫

呆若木雞

主持人問:辣雞,你要展現什麼特長

小蝌蚪:舌頭碎大石

主持人:除了這個還有嗎

小蝌蚪:胸毛碎大石

主持人暴怒:我懷疑你是來砸場的

小蝌蚪:是的,我就是來砸場的

主持人叫來20個保安

十秒鐘後保安所有倒地抽搐

身上被小蝌蚪的胸毛刻滿了神祕javascript代碼

。。。

主持人:爸爸對不起,剛纔是我冒犯了

主持人:爸爸,麻煩跟觀衆介紹一下本身

小蝌蚪從書包裏掏出一千萬美金(富婆送)

點燃後從98樓外拋出

燃燒的錢漫天飛舞

小蝌蚪介紹道:「我是顏色不同的渣男」

臺下全部女人狂轟亂叫

因過分興奮而七竅流血

男寵們看到本身的主子瘋狂跪舔小蝌蚪

深感本身被綠了

主持人:爸爸您還有什麼溫柔點的特長展現給你們嗎

小蝌蚪:我要唱一首孫豔枝的《綠光》,送給在座的各位男辣雞

《綠光》

愛就像一道綠光

灑在你頭上

都怪嫂子太漂亮

。。。

不要怪爸爸無情

是大家太辣雞

啊~綠光~呀~綠光

照亮你我他

男寵們:小蝌蚪你太過度了

小蝌蚪:各憑本事作人渣

男寵們:你到底想幹什麼

小蝌蚪:我想在大家墳頭上蹦迪

男寵們心態爆炸,瘋狂抓頭

從98樓跳了下去

小蝌蚪贏得了比賽

成爲了第一名男寵

。。。

酒店陽臺,微風拂過

富婆:今晚謝謝你

小蝌蚪:爲了錢,應該的

富婆:我喜歡你

小蝌蚪:我也喜歡我本身

富婆:你愛我嗎

小蝌蚪:我不能對客戶動心

富婆:我要如何才能獲得你的心

小蝌蚪:殺了我,把心掏出來,嘻嘻

富婆:嘻嘻

。。。

黑幫大佬

屋內,捏腳

富婆:我要死了

小蝌蚪:怎麼了

富婆:我老公在外面有女人了

富婆:他是個黑幫大佬,要殺我,跟小三在一塊兒

小蝌蚪輕撫富婆的臉:傻瓜,在這以前,我會殺了他

小蝌蚪和富婆擁吻在了一塊兒

兩嘴之間拉出一條亮絲

吻畢,小蝌蚪消失在夜空中

。。。

刺殺大佬

深夜十二點

黑幫大佬酒店房間門外

塞入了一張小卡片

上面寫着「包小姐」

和一張小蝌蚪穿泳褲的照片

。。。

黑幫大佬有着詭異的癖好

喜歡胸毛濃密的小奶狗

小蝌蚪知足了他對一切小奶狗的幻想

。。。

房間門果真打開了

小蝌蚪用一千根胸毛扎入了大佬體內

大佬:你。。。你是誰

小蝌蚪:我是你爸爸

大佬:爲何要殺我

小蝌蚪:由於我是你爸爸

大佬:能不能不提爸爸兩個字

小蝌蚪:我和你老婆有一腿

黑幫大佬,猝,享年80

。。。

因爲刺殺了黑幫大佬

當晚回去的路上

小蝌蚪被1000名殺手全城追殺

用盡了最後一根胸毛

小蝌蚪衆寡不敵

倒在了血泊裏,奄奄一息

將死之際,富婆從殺手中出現

她微笑的捅了小蝌蚪一刀

把心掏了出來

小蝌蚪驚恐的望着她

富婆:我終於獲得了你的心,嘻嘻

(完)

做者:第一名的小蝌蚪

微信公衆號:前端屌絲

github: https://github.com/airuikun/blog

《nodejs線上模塊熱部署原理與實現》

背景

你們都知道,nodejs啓的後端服務,若是有代碼變更,要重啓進程,代碼才能生效。

nodejs的進程在重啓的時候,爸爸們去訪問服務,就會出現短暫的 502 bad gateway,爸爸們會不高興

若是你的服務器加上了watch機制

當服務器上的代碼頻繁發生變更,或者短期內發生高頻變更,那就會一直 502 bad gateway

所謂「重啓一時爽,一直重啓一直爽」

近段時間在作雲編譯相關需求的時候,就出現了短期內線上服務代碼高頻變更,代碼功能模塊高頻更新,在不能重啓服務的狀況下,讓更新的代碼生效的場景。

這就涉及到一個熱部署的概念,在不重啓服務的狀況下,讓新部署的代碼生效。

接下來我來給爸爸們講解熱部署的原理和實現方案

代碼無法實時生效的緣由

當咱們經過require('xx/xx.js')去加載一個功能模塊的時候,node會把require('xx/xx.js')獲得的結果緩存在require.cache('xx/xx.js')

當咱們屢次調用require('xx/xx.js'),node就再也不從新加載,而是直接從require.cache('xx/xx.js')讀取緩存

因此當爸爸在服務器上修改xx/xx.js這個路徑下的文件時,node只會去讀取緩存,不會去加載爸爸的最新代碼

源碼地址和使用

爲了實現這個熱部署機制,在網上處處查資料,處處踩坑才弄好

如下代碼是提煉出來、完整可運行的熱部署基礎原理代碼,你們能夠基於這個代碼去自行拓展:smart-node-reload

nodejs版本:v10.5.0

git clone下來之後,無需安裝,直接運行

npm start

這時候就開啓了熱部署變更監聽

如何看到效果呢

爸爸請看/hots/hot.js文件

const hot = 1
    module.exports = hot

將第一行代碼改成const hot = 111

const hot = 111
    module.exports = hot

這時候就能看到終端裏監聽到代碼變更,而後動態加載你的最新代碼並獲得執行結果,輸出爲:

熱部署文件: hot.js ,執行結果: { 'hot.js': 111 }

熱部署服務監聽到代碼變更,並從新加載了代碼,爸爸們就能夠實時拿到最新代碼的執行結果了,整個過程都在線上環境運行,node進程也沒有重啓

源碼解析

loadHandlers主函數

const handlerMap = {};// 緩存
const hotsPath = path.join(__dirname, "hots");

// 加載文件代碼 並 監聽指定文件夾目錄文件內容變更
const loadHandlers = async () => {
  // 遍歷出指定文件夾下的全部文件
  const files = await new Promise((resolve, reject) => {
    fs.readdir(hotsPath, (err, files) => {
      if (err) {
        reject(err);
      } else {
        resolve(files);
      }
    });
  });
  // 初始化加載全部文件 把每一個文件結果緩存到handlerMap變量當中
  for (let f in files) {
    handlerMap[files[f]] = await loadHandler(path.join(hotsPath, files[f]));
  }

  // 監聽指定文件夾的文件內容變更
  await watchHandlers();
};

loadHandlers是整個熱部署服務的主函數,咱們指定了服務器根目錄下的hots文件夾是用來監聽變更和熱部署的文件夾

fs.readdir掃描hots文件夾下的全部文件,經過loadHandler方法去加載和運行每個掃描到的文件,將結果緩存到handlerMap

而後用watchHandlers方法開啓文件變更監聽

watchHandlers監聽文件變更

// 監視指定文件夾下的文件變更
const watchHandlers = async () => {
  // 這裏建議用chokidar的npm包代替文件夾監聽
  fs.watch(hotsPath, { recursive: true }, async (eventType, filename) => {
    // 獲取到每一個文件的絕對路徑 
    // 包一層require.resolve的緣由,拼接好路徑之後,它會主動去幫你判斷這個路徑下的文件是否存在
    const targetFile = require.resolve(path.join(hotsPath, filename));
    // 當你適應require加載一個模塊後,模塊的數據就會緩存到require.cache中,下次再加載相同模塊,就會直接走require.cache
    // 因此咱們熱加載部署,首要作的就是清除require.cache中對應文件的緩存
    const cacheModule = require.cache[targetFile];
    // 去除掉在require.cache緩存中parent對當前模塊的引用,不然會引發內存泄露,具體解釋能夠看下面的文章
    //《記錄一次由一行代碼引起的「血案」》https://cnodejs.org/topic/5aaba2dc19b2e3db18959e63
    //《一行 delete require.cache 引起的內存泄漏血案》https://zhuanlan.zhihu.com/p/34702356
    if (cacheModule.parent) {    
        cacheModule.parent.children.splice(cacheModule.parent.children.indexOf(cacheModule), 1);
    }
    // 清除指定路徑對應模塊的require.cache緩存
    require.cache[targetFile] = null;

    // 從新加載發生變更後的模塊文件,實現熱加載部署效果,並將從新加載後的結果,更新到handlerMap變量當中
    const code = await loadHandler(targetFile)
    handlerMap[filename] = code;
    console.log("熱部署文件:", filename, ",執行結果:", handlerMap);
  });
};

watchHandlers函數是用來監聽指定文件夾下的文件變更、清理緩存更新緩存用的。

fs.watch原生函數監聽hots文件夾下文件變更,當文件發生變更,就算出文件的絕對路徑targetFile

require.cache[targetFile]就是requiretargetFile原文件的緩存,清除緩存用require.cache[targetFile] = null;

坑爹的地方來了,僅僅只是將緩存置爲null,會發生內存泄露,咱們還須要清除緩存父級的引用require.cache[targetFile].parent,就是下面這段代碼

if (cacheModule.parent) {    
        cacheModule.parent.children.splice(cacheModule.parent.children.indexOf(cacheModule), 1);
    }

loadHandler加載文件

// 加載指定文件的代碼
const loadHandler = filename => {
  return new Promise((resolve, reject) => {
    fs.readFile(filename, (err, data) => {
      if (err) {
        resolve(null);
      } else {
        try {
          // 使用vm模塊的Script方法來預編譯發生變化後的文件代碼,檢查語法錯誤,提早發現是否存在語法錯誤等報錯
          new vm.Script(data);
        } catch (e) {
          // 語法錯誤,編譯失敗
          reject(e);
          return;
        }
        // 編譯經過後,從新require加載最新的代碼
        resolve(require(filename));
      }
    });
  });
};

loadHandler函數的做用是加載指定文件,並校驗新文件代碼語法等。

經過fs.readFile讀取文件內容

用node原生vm模塊vm.Script方法去預編譯發生變化後的文件代碼,檢查語法錯誤,提早發現是否存在語法錯誤等報錯

檢驗經過後,經過resolve(require(filename))方法從新將文件require加載,並自動加入到require.cache緩存中

結尾:

以上就是熱部署的全部內容了,代碼地址是:smart-node-reload

這個代碼是我通過極簡後的代碼,方便你們閱讀和理解,感興趣的小夥伴能夠經過這個代碼去進行深一步拓展

哎。。。。寫完這篇文章,已是凌晨兩點了,寫文章真不容易,麻煩各位爸爸star、follow、點贊和關注,辛苦爸爸了

做者:第一名的小蝌蚪

微信公衆號:前端屌絲

github: https://github.com/airuikun/blog

相關文章
相關標籤/搜索