<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          基于nodejs線上代碼熱部署原理與實(shí)現(xiàn)

          共 4225字,需瀏覽 9分鐘

           ·

          2020-11-17 04:55


          背景

          大家都知道,nodejs啟的后端服務(wù),如果有代碼變動(dòng),要重啟進(jìn)程,代碼才能生效。

          nodejs的進(jìn)程在重啟的時(shí)候,用戶去訪問(wèn)服務(wù),就會(huì)出現(xiàn)短暫的 502 bad gateway

          如果你的服務(wù)器加上了watch機(jī)制

          當(dāng)服務(wù)器上的代碼頻繁發(fā)生變動(dòng),或者短時(shí)間內(nèi)發(fā)生高頻變動(dòng),那就會(huì)一直 502 bad gateway

          近段時(shí)間在做線上服務(wù)編譯相關(guān)需求的時(shí)候,就出現(xiàn)了短時(shí)間內(nèi)線上服務(wù)代碼高頻變動(dòng),代碼功能模塊高頻更新,在不能重啟服務(wù)的情況下,讓更新的代碼生效的場(chǎng)景。

          這就涉及到一個(gè)熱部署的概念,在不重啟服務(wù)的情況下,讓新部署的代碼生效。

          接下來(lái)我來(lái)給大家講解熱部署的原理和實(shí)現(xiàn)方案

          代碼沒(méi)法實(shí)時(shí)生效的原因

          當(dāng)我們通過(guò)require('xx/xx.js')去加載一個(gè)功能模塊的時(shí)候,node會(huì)把require('xx/xx.js')得到的結(jié)果緩存在require.cache('xx/xx.js')

          當(dāng)我們多次調(diào)用require('xx/xx.js'),node就不再重新加載,而是直接從require.cache('xx/xx.js')讀取緩存

          所以當(dāng)小伙伴在服務(wù)器上修改xx/xx.js這個(gè)路徑下的文件時(shí),node只會(huì)去讀取緩存,不會(huì)去加載小伙伴的最新代碼

          源碼地址和使用

          為了實(shí)現(xiàn)這個(gè)熱部署機(jī)制,在網(wǎng)上到處查資料,踩了好多坑才弄好

          以下代碼是提煉出來(lái)、完整可運(yùn)行的熱部署基礎(chǔ)原理代碼,大家可以基于這個(gè)代碼去自行拓展:smart-node-reload(https://github.com/airuikun/smart-node-reload)

          注意最新版本12版本的node運(yùn)行會(huì)報(bào)錯(cuò),官方對(duì)require.cache做了調(diào)整,已經(jīng)上報(bào)問(wèn)題給官方,建議使用nodejs版本:v10.5.0

          git clone下來(lái)以后,無(wú)需安裝,直接運(yùn)行

          ????npm?start

          這時(shí)候就開(kāi)啟了熱部署變動(dòng)監(jiān)聽(tīng)

          如何看到效果呢

          小伙伴請(qǐng)看/hots/hot.js文件

              const hot = 1????module.exports?=?hot

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

              const hot = 111????module.exports?=?hot

          這時(shí)候就能看到終端里監(jiān)聽(tīng)到代碼變動(dòng),然后動(dòng)態(tài)加載你的最新代碼并得到執(zhí)行結(jié)果,輸出為:

          ????熱部署文件:hot.js ,執(zhí)行結(jié)果:{?'hot.js':?111?}

          熱部署服務(wù)監(jiān)聽(tīng)到代碼變動(dòng),并重新加載了代碼,小伙伴就可以實(shí)時(shí)拿到最新代碼的執(zhí)行結(jié)果了,整個(gè)過(guò)程都在線上環(huán)境運(yùn)行,node進(jìn)程也沒(méi)有重啟

          源碼解析

          loadHandlers主函數(shù)

          const handlerMap = {};// 緩存const hotsPath = path.join(__dirname, "hots");
          // 加載文件代碼 并 監(jiān)聽(tīng)指定文件夾目錄文件內(nèi)容變動(dòng)const loadHandlers = async () => { // 遍歷出指定文件夾下的所有文件 const files = await new Promise((resolve, reject) => { fs.readdir(hotsPath, (err, files) => { if (err) { reject(err); } else { resolve(files); } }); }); // 初始化加載所有文件 把每個(gè)文件結(jié)果緩存到handlerMap變量當(dāng)中 for (let f in files) { handlerMap[files[f]] = await loadHandler(path.join(hotsPath, files[f])); }
          // 監(jiān)聽(tīng)指定文件夾的文件內(nèi)容變動(dòng) await watchHandlers();};

          loadHandlers是整個(gè)熱部署服務(wù)的主函數(shù),我們指定了服務(wù)器根目錄下的hots文件夾是用來(lái)監(jiān)聽(tīng)變動(dòng)和熱部署的文件夾

          fs.readdir掃描hots文件夾下的所有文件,通過(guò)loadHandler方法去加載和運(yùn)行每一個(gè)掃描到的文件,將結(jié)果緩存到handlerMap

          然后用watchHandlers方法開(kāi)啟文件變動(dòng)監(jiān)聽(tīng)

          watchHandlers監(jiān)聽(tīng)文件變動(dòng)

          // 監(jiān)視指定文件夾下的文件變動(dòng)const watchHandlers = async () => {  // 這里建議用chokidar的npm包代替文件夾監(jiān)聽(tīng)  fs.watch(hotsPath, { recursive: true }, async (eventType, filename) => {    // 獲取到每個(gè)文件的絕對(duì)路徑    // 包一層require.resolve的原因,拼接好路徑以后,它會(huì)主動(dòng)去幫你判斷這個(gè)路徑下的文件是否存在    const targetFile = require.resolve(path.join(hotsPath, filename));    // 當(dāng)你適應(yīng)require加載一個(gè)模塊后,模塊的數(shù)據(jù)就會(huì)緩存到require.cache中,下次再加載相同模塊,就會(huì)直接走require.cache    // 所以我們熱加載部署,首要做的就是清除require.cache中對(duì)應(yīng)文件的緩存    const cacheModule = require.cache[targetFile];    // 去除掉在require.cache緩存中parent對(duì)當(dāng)前模塊的引用,否則會(huì)引起內(nèi)存泄露,具體解釋可以看下面的文章	//《記錄一次由一行代碼引發(fā)的“血案”》https://cnodejs.org/topic/5aaba2dc19b2e3db18959e63	//《一行 delete require.cache 引發(fā)的內(nèi)存泄漏血案》https://zhuanlan.zhihu.com/p/34702356    if (cacheModule.parent) {        cacheModule.parent.children.splice(cacheModule.parent.children.indexOf(cacheModule), 1);    }    // 清除指定路徑對(duì)應(yīng)模塊的require.cache緩存    require.cache[targetFile] = null;
          // 重新加載發(fā)生變動(dòng)后的模塊文件,實(shí)現(xiàn)熱加載部署效果,并將重新加載后的結(jié)果,更新到handlerMap變量當(dāng)中 const code = await loadHandler(targetFile) handlerMap[filename] = code; console.log("熱部署文件:", filename, ",執(zhí)行結(jié)果:", handlerMap); });};

          watchHandlers函數(shù)是用來(lái)監(jiān)聽(tīng)指定文件夾下的文件變動(dòng)、清理緩存更新緩存用的。

          fs.watch原生函數(shù)監(jiān)聽(tīng)hots文件夾下文件變動(dòng),當(dāng)文件發(fā)生變動(dòng),就算出文件的絕對(duì)路徑targetFile

          require.cache[targetFile]就是require對(duì)targetFile原文件的緩存,清除緩存用require.cache[targetFile] = null;

          坑爹的地方來(lái)了,僅僅只是將緩存置為null,會(huì)發(fā)生內(nèi)存泄露,我們還需要清除緩存父級(jí)的引用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方法來(lái)預(yù)編譯發(fā)生變化后的文件代碼,檢查語(yǔ)法錯(cuò)誤,提前發(fā)現(xiàn)是否存在語(yǔ)法錯(cuò)誤等報(bào)錯(cuò)          new vm.Script(data);        } catch (e) {          // 語(yǔ)法錯(cuò)誤,編譯失敗          reject(e);          return;        }        // 編譯通過(guò)后,重新require加載最新的代碼        resolve(require(filename));      }    });  });};

          loadHandler函數(shù)的作用是加載指定文件,并校驗(yàn)新文件代碼語(yǔ)法等。

          通過(guò)fs.readFile讀取文件內(nèi)容

          用node原生vm模塊vm.Script方法去預(yù)編譯發(fā)生變化后的文件代碼,檢查語(yǔ)法錯(cuò)誤,提前發(fā)現(xiàn)是否存在語(yǔ)法錯(cuò)誤等報(bào)錯(cuò)

          檢驗(yàn)通過(guò)后,通過(guò)resolve(require(filename))方法重新將文件require加載,并自動(dòng)加入到require.cache緩存中

          結(jié)尾:

          以上就是熱部署的所有內(nèi)容了,代碼地址是:smart-node-reload(https://github.com/airuikun/smart-node-reload)

          這個(gè)代碼是我經(jīng)過(guò)極簡(jiǎn)后的代碼,方便大家閱讀和理解,感興趣的小伙伴可以通過(guò)這個(gè)代碼去進(jìn)行深一步拓展



          瀏覽 53
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  91AV影院 | 久久久久久久久久久久久久久久久久久久 | 国产乱伦三级片导航 | 樱桃视频香蕉 | 影音先锋成人无码在线观看 |