亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Node.js巧妙實(shí)現(xiàn)Web應(yīng)用代碼熱更新

 更新時(shí)間:2015年10月22日 09:00:06   投稿:hebedich  
本文給大家講解的是Node.js的代碼熱更新的問題,其主要實(shí)現(xiàn)原理 是怎么對(duì) module 對(duì)象做處理,也就是手工監(jiān)聽文件修改, 然后清楚模塊緩存, 重新掛載模塊,思路清晰考慮細(xì)致, 雖然有點(diǎn)冗余代碼,但還是推薦給大家

背景

相信使用 Node.js 開發(fā)過 Web 應(yīng)用的同學(xué)一定苦惱過新修改的代碼必須要重啟 Node.js 進(jìn)程后才能更新的問題。習(xí)慣使用 PHP 開發(fā)的同學(xué)更會(huì)非常的不適用,大呼果然還是我大PHP才是世界上最好的編程語言。手動(dòng)重啟進(jìn)程不僅僅是非常惱人的重復(fù)勞動(dòng),當(dāng)應(yīng)用規(guī)模稍大以后,啟動(dòng)時(shí)間也逐漸開始不容忽視。

當(dāng)然作為程序猿,無論使用哪種語言,都不會(huì)讓這樣的事情折磨自己。解決這類問題最直接和普適的手段就是監(jiān)聽文件修改并重啟進(jìn)程。這個(gè)方法也已經(jīng)有很多成熟的解決方案提供了,比如已經(jīng)被棄坑的 node-supervisor,以及現(xiàn)在比較火的 PM2 ,或者比較輕量級(jí)的 node-dev 等等均是這樣的思路。

本文則提供了另外一種思路,只需要很小的改造,就可以實(shí)現(xiàn)真正的0重啟熱更新代碼,解決 Node.js 開發(fā) Web 應(yīng)用時(shí)惱人的代碼更新問題。

總體思路

說起代碼熱更新,當(dāng)下最有名的當(dāng)屬 Erlang 語言的熱更新功能,這門語言的特色在于高并發(fā)和分布式編程,主要的應(yīng)用場(chǎng)景則是類似證券交易、游戲服務(wù)端等領(lǐng)域。這些場(chǎng)景都或多或少要求服務(wù)擁有在運(yùn)行中運(yùn)維的手段,而代碼熱更新就是其中非常重要的一環(huán),因此我們可以先簡(jiǎn)單的了解一下 Erlang 的做法。

由于我也沒有使用過 Erlang ,以下內(nèi)容均為道聽途說,如果希望深入和準(zhǔn)確的了解 Erlang 的代碼熱更新實(shí)現(xiàn),最好還是查閱官方文檔。

Erlang 的代碼加載由一個(gè)名為code_server的模塊管理,除了啟動(dòng)時(shí)的一些必要代碼外,大部分的代碼均是由code_server加載。
當(dāng)code_server發(fā)現(xiàn)模塊代碼被更新后,會(huì)重新加載模塊,此后的新請(qǐng)求會(huì)使用新模塊執(zhí)行,而原有還在執(zhí)行的請(qǐng)求則繼續(xù)使用老模塊執(zhí)行。
老模塊會(huì)在新模塊加載后,被打上old標(biāo)簽,新模塊則是current標(biāo)簽。當(dāng)下一次熱更新的時(shí)候,Erlang 會(huì)掃描還在執(zhí)行老模塊的進(jìn)行并殺掉,再繼續(xù)按照這個(gè)邏輯更新模塊。
Erlang 中并非所有代碼均允許熱更新,如 kernel, stdlib, compiler 等基礎(chǔ)模塊默認(rèn)是不允許更新的
我們可以發(fā)現(xiàn) Node.js 中也有與code_server類似的模塊,即 require 體系,因此 Erlang 的做法應(yīng)該也可以在 Node.js 上做一些嘗試。通過了解 Erlang 的做法,我們可以大概的總結(jié)出在 Node.js 中解決代碼熱更新的關(guān)鍵問題點(diǎn)

如何更新模塊代碼
如何使用新模塊處理請(qǐng)求
如何釋放老模塊的資源

那么接下來我們就逐個(gè)的解析這些問題點(diǎn)。

如何更新模塊代碼

要解決模塊代碼更新的問題,我們就需要去閱讀 Node.js 的模塊管理器實(shí)現(xiàn),直接上鏈接 module.js。通過簡(jiǎn)單的閱讀,我們可以發(fā)現(xiàn)核心的代碼就在于 Module._load ,稍微精簡(jiǎn)一下代碼貼出來。

// Check the cache for the requested file.
// 1. If a module already exists in the cache: return its exports object.
// 2. If the module is native: call `NativeModule.require()` with the
// filename and return the result.
// 3. Otherwise, create a new module for the file and save it to the cache.
// Then have it load the file contents before returning its exports
// object.
Module._load = function(request, parent, isMain) {
 var filename = Module._resolveFilename(request, parent);

 var cachedModule = Module._cache[filename];
 if (cachedModule) {
 return cachedModule.exports;
 }

 var module = new Module(filename, parent);
 Module._cache[filename] = module;
 module.load(filename);

 return module.exports;
};

require.cache = Module._cache;

可以發(fā)現(xiàn)其中的核心就是 Module._cache ,只要清除了這個(gè)模塊緩存,下一次 require 的時(shí)候,模塊管理器就會(huì)重新加載最新的代碼了。

寫一個(gè)小程序驗(yàn)證一下

// main.js
function cleanCache (module) {
 var path = require.resolve(module);
 require.cache[path] = null;
}

setInterval(function () {
 cleanCache('./code.js');
 var code = require('./code.js');
 console.log(code);
}, 5000);
// code.js
module.exports = 'hello world';

我們執(zhí)行一下 main.js ,同時(shí)取修改 code.js 的內(nèi)容,就可以發(fā)現(xiàn)控制臺(tái)中,我們代碼成功的更新為了最新的代碼。

圖片

那么模塊管理器更新代碼的問題已經(jīng)解決了,接下來再看看在 Web 應(yīng)用中,我們?nèi)绾巫屝碌哪K可以被實(shí)際執(zhí)行。

如何使用新模塊處理請(qǐng)求

為了更符合大家的使用習(xí)慣,我們就直接以 Express 為例來展開這個(gè)問題,實(shí)際上使用類似的思路,絕大部分 Web應(yīng)用 均可適用。

首先,如果我們的服務(wù)是像 Express 的 DEMO 一樣所有的代碼均在同一模塊內(nèi)的話,我們是無法針對(duì)模塊進(jìn)行熱加載的

var express = require('express');
var app = express();

app.get('/', function(req, res){
 res.send('hello world');
});

app.listen(3000);

要實(shí)現(xiàn)熱加載,和 Erlang 中不允許的基礎(chǔ)庫一樣,我們需要一些無法進(jìn)行熱更新的基礎(chǔ)代碼控制更新流程。而且類似 app.listen 這類操作如果重新執(zhí)行了,那么和重啟 Node.js 進(jìn)程也沒太大的區(qū)別了。因此我們需要一些巧妙的代碼將頻繁更新的業(yè)務(wù)代碼與不頻繁更新的基礎(chǔ)代碼隔離開。

// app.js 基礎(chǔ)代碼
var express = require('express');
var app = express();
var router = require('./router.js');

app.use(router);

app.listen(3000);
// router.js 業(yè)務(wù)代碼
var express = require('express');
var router = express .Router();

// 此處加載的中間件也可以自動(dòng)更新
router.use(express.static('public'));

router.get('/', function(req, res){
 res.send('hello world');
});

module.exports = router;

然而很遺憾,經(jīng)過這樣處理之后,雖然成功的分離了核心代碼, router.js 依然無法進(jìn)行熱更新。首先,由于缺乏對(duì)更新的觸發(fā)機(jī)制,服務(wù)無法知道應(yīng)該何時(shí)去更新模塊。其次, app.use 操作會(huì)一直保存老的 router.js 模塊,因此即使模塊被更新了,請(qǐng)求依然會(huì)使用老模塊處理而非新模塊。

那么繼續(xù)改進(jìn)一下,我們需要對(duì) app.js 稍作調(diào)整,啟動(dòng)文件監(jiān)聽作為觸發(fā)機(jī)制,并且通過閉包來解決 app.use 的緩存問題

// app.js
var express = require('express');
var fs = require('fs');
var app = express();

var router = require('./router.js');

app.use(function (req, res, next) {
 // 利用閉包的特性獲取最新的router對(duì)象,避免app.use緩存router對(duì)象
 router(req, res, next);
});

app.listen(3000);

// 監(jiān)聽文件修改重新加載代碼
fs.watch(require.resolve('./router.js'), function () {
 cleanCache(require.resolve('./router.js'));
 try {
  router = require('./router.js');
 } catch (ex) {
  console.error('module update failed');
 }
});

function cleanCache(modulePath) {
 require.cache[modulePath] = null;
}

再試著修改一下 router.js 就會(huì)發(fā)現(xiàn)我們的代碼熱更新已經(jīng)初具雛形了,新的請(qǐng)求會(huì)使用最新的 router.js 代碼。除了修改 router.js 的返回內(nèi)容外,還可以試試看修改路由功能,也會(huì)如預(yù)期一樣進(jìn)行更新。

當(dāng)然,要實(shí)現(xiàn)一個(gè)完善的熱更新方案需要更多結(jié)合自身方案做一些改進(jìn)。首先,在中間件的使用上,我們可以在 app.use 處聲明一些不需要熱更新或者說每次更新不希望重復(fù)執(zhí)行的中間件,而在 router.use 處則可以聲明一些希望可以靈活修改的中間件。其次,文件監(jiān)聽不能僅監(jiān)聽路由文件,而是要監(jiān)聽所有需要熱更新的文件。除了文件監(jiān)聽這種手段外,還可以結(jié)合編輯器的擴(kuò)展功能,在保存時(shí)向 Node.js 進(jìn)程發(fā)送信號(hào)或者訪問一個(gè)特定的 URL 等方式來觸發(fā)更新。

如何釋放老模塊的資源

要解釋清楚老模塊的資源如何釋放的問題,實(shí)際上需要先了解 Node.js 的內(nèi)存回收機(jī)制,本文中并不準(zhǔn)備詳加描述,解釋 Node.js 的內(nèi)存回收機(jī)制的文章和書籍很多,感興趣的同學(xué)可以自行擴(kuò)展閱讀。簡(jiǎn)單的總結(jié)一下就是當(dāng)一個(gè)對(duì)象沒有被任何對(duì)象引用的時(shí)候,這個(gè)對(duì)象就會(huì)被標(biāo)記為可回收,并會(huì)在下一次GC處理的時(shí)候釋放內(nèi)存。

那么我們的課題就是,如何讓老模塊的代碼更新后,確保沒有對(duì)象保持了模塊的引用。首先我們以 如何更新模塊代碼 一節(jié)中的代碼為例,看看老模塊資源不回收會(huì)出現(xiàn)什么問題。為了讓結(jié)果更顯著,我們修改一下 code.js

// code.js
var array = [];

for (var i = 0; i < 10000; i++) {
 array.push('mem_leak_when_require_cache_clean_test_item_' + i);
}

module.exports = array;
// app.js
function cleanCache (module) {
 var path = require.resolve(module);
 require.cache[path] = null;
}

setInterval(function () {
 var code = require('./code.js');
 cleanCache('./code.js');
}, 10);

好~我們用了一個(gè)非常笨拙但是有效的方法,提高了 router.js 模塊的內(nèi)存占用,那么再次啟動(dòng) main.js 后,就會(huì)發(fā)現(xiàn)內(nèi)存出現(xiàn)顯著的飆升,不到一會(huì) Node.js 就提示 process out of memory。然而實(shí)際上從 app.js 與 router.js 的代碼中觀察的話,我們并沒發(fā)現(xiàn)哪里保存了舊模塊的引用。

我們借助一些 profile 工具如 node-heapdump 就可以很快的定位到問題所在,在 module.js 中我們發(fā)現(xiàn) Node.js 會(huì)自動(dòng)為所有模塊添加一個(gè)引用

function Module(id, parent) {
 this.id = id;
 this.exports = {};
 this.parent = parent;
 if (parent && parent.children) {
 parent.children.push(this);
 }

 this.filename = null;
 this.loaded = false;
 this.children = [];
}

因此相應(yīng)的,我們可以調(diào)整一下cleanCache函數(shù),將這個(gè)引用在模塊更新的時(shí)候一并去除。

// app.js
function cleanCache(modulePath) {
 var module = require.cache[modulePath];
 // remove reference in module.parent
 if (module.parent) {
  module.parent.children.splice(module.parent.children.indexOf(module), 1);
 }
 require.cache[modulePath] = null;
}

setInterval(function () {
 var code = require('./code.js');
 cleanCache(require.resolve('./code.js'));
}, 10); 

再執(zhí)行一下,這次好多了,內(nèi)存只會(huì)有輕微的增長(zhǎng),說明老模塊占用的資源已經(jīng)正確的釋放掉了。

使用了新的 cleanCache 函數(shù)后,常規(guī)的使用就沒有問題,然而并非就可以高枕無憂了。在 Node.js 中,除了 require 系統(tǒng)會(huì)添加引用外,通過 EventEmitter 進(jìn)行事件監(jiān)聽也是大家常用的功能,并且 EventEmitter 有非常大的嫌疑會(huì)出現(xiàn)模塊間的互相引用。那么 EventEmitter 能否正確的釋放資源呢?答案是肯定的。

// code.js
var moduleA = require('events').EventEmitter();

moduleA.on('whatever', function () {
});

當(dāng) code.js 模塊被更新,并且所有引用被移出后,只要 moduleA 沒有被其他未釋放的模塊引用, moduleA 也會(huì)被自動(dòng)釋放,包括我們?cè)谄鋬?nèi)部的事件監(jiān)聽。

只有一種畸形的 EventEmitter 應(yīng)用場(chǎng)景在這套體系下無法應(yīng)對(duì),即 code.js 每次執(zhí)行的時(shí)候都會(huì)去監(jiān)聽一個(gè)全局對(duì)象的事件,這樣會(huì)造成全局對(duì)象上不停的掛載事件,同時(shí) Node.js 會(huì)很快的提示檢測(cè)到過多的事件綁定,疑似內(nèi)存泄露。

至此,可以看到只要處理好了 require 系統(tǒng)中 Node.js 為我們自動(dòng)添加的引用,老模塊的資源回收并不是大問題,雖然我們無法做到像 Erlang 一樣實(shí)現(xiàn)下一次熱更新對(duì)還留存的老模塊進(jìn)行掃描這樣細(xì)粒度的控制,但是我們可以通過合理的規(guī)避手段,解決老模塊資源釋放的問題。

在 Web 應(yīng)用下,還有一個(gè)引用問題就是未釋放的模塊或者核心模塊對(duì)需要熱更新的模塊有引用,如 app.use,導(dǎo)致老模塊的資源無法釋放,并且新的請(qǐng)求無法正確的使用新模塊進(jìn)行處理。解決這個(gè)問題的手段就是控制全局變量或者引用的暴露的入口,在熱更新執(zhí)行的過程中手動(dòng)更新入口。如 如何使用新模塊處理請(qǐng)求 中對(duì) router 的封裝就是一個(gè)例子,通過這一個(gè)入口的控制,我們?cè)?router.js 中無論如何引用其他模塊,都會(huì)隨著入口的釋放而釋放。

另一個(gè)會(huì)引起資源釋放問題的就是類似 setInterval 這類操作,會(huì)保持對(duì)象的生命周期無法釋放,不過在 Web 應(yīng)用中我們極少會(huì)使用這類技術(shù),因此方案中并未關(guān)注。

尾聲

至此,我們就解決了 Node.js 在 Web 應(yīng)用下代碼熱更新的三大問題,不過由于 Node.js 本身缺乏對(duì)有效的留存對(duì)象的掃描機(jī)制,因此并不能100%的消除類似 setInterval 導(dǎo)致的老模塊的資源無法釋放的問題。也是由于這樣的局限性,目前我們提供的 YOG2 框架中,主要還是將此技術(shù)應(yīng)用于開發(fā)調(diào)試期,通過熱更新實(shí)現(xiàn)快速開發(fā)。而生產(chǎn)環(huán)境的代碼更新依然使用重啟或者 PM2 的 hot reload 功能來保證線上服務(wù)的穩(wěn)定性。

由于熱更新實(shí)際上與框架和業(yè)務(wù)架構(gòu)緊密相關(guān),因此本文并未給出一個(gè)通用的解決方案。作為參考,簡(jiǎn)單的介紹一下在 YOG2 框架中我們是如何使用這項(xiàng)技術(shù)的。由于 YOG2 框架本身就支持前后端子系統(tǒng) App 拆分,因此我們的更新策略是以 App 為粒度更新代碼。同時(shí)由于類似 fs.watch 這類操作會(huì)有兼容性問題,一些替代方案如 fs.watchFile 則會(huì)比較消耗性能,因此我們結(jié)合了 YOG2 的測(cè)試機(jī)部署功能,通過上傳部署新代碼的形式告知框架需要更新 App 代碼。在以 App 為粒度更新模塊緩存的同時(shí),會(huì)更新路由緩存與模板緩存,來完成所有代碼的更新工作。

如果你使用的是類似 Express 或者 Koa 這類框架,只需要按照文中的方法結(jié)合自身業(yè)務(wù)需要,對(duì)主路由進(jìn)行一些改造,就可以很好的應(yīng)用這項(xiàng)技術(shù)。

相關(guān)文章

  • 詳解如何在NodeJS項(xiàng)目中優(yōu)雅的使用ES6

    詳解如何在NodeJS項(xiàng)目中優(yōu)雅的使用ES6

    本篇文章主要介紹了詳解如何在NodeJS項(xiàng)目中優(yōu)雅的使用ES6,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。
    2017-04-04
  • Node.js安裝配置圖文教程

    Node.js安裝配置圖文教程

    這篇文章主要為大家詳細(xì)介紹了Node.js安裝配置的圖文教程,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-05-05
  • 在windows上用nodejs搭建靜態(tài)文件服務(wù)器的簡(jiǎn)單方法

    在windows上用nodejs搭建靜態(tài)文件服務(wù)器的簡(jiǎn)單方法

    這篇文章主要介紹了在windows上用nodejs搭建靜態(tài)文件服務(wù)器的簡(jiǎn)單方法,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下
    2016-08-08
  • Node.js?源碼閱讀深入理解cjs模塊系統(tǒng)

    Node.js?源碼閱讀深入理解cjs模塊系統(tǒng)

    這篇文章主要為大家介紹了Node.js?源碼閱讀深入理解cjs模塊系統(tǒng),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-09-09
  • 解決nodejs報(bào)錯(cuò)Error:EPERM:operation not permitted,mkdir‘xxxxxxxxxxxxxxxx‘

    解決nodejs報(bào)錯(cuò)Error:EPERM:operation not permitted,mkdi

    這篇文章主要介紹了解決nodejs報(bào)錯(cuò)Error:EPERM:operation not permitted,mkdir‘xxxxxxxxxxxxxxxx‘問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-02-02
  • nodejs更改項(xiàng)目端口號(hào)的方法

    nodejs更改項(xiàng)目端口號(hào)的方法

    今天小編就為大家分享一篇nodejs更改項(xiàng)目端口號(hào)的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2018-05-05
  • Node.js?全局變量無法掛載問題解決分析

    Node.js?全局變量無法掛載問題解決分析

    這篇文章主要為大家介紹了Node.js?全局變量無法掛載問題解決分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-04-04
  • async/await優(yōu)雅的錯(cuò)誤處理方法總結(jié)

    async/await優(yōu)雅的錯(cuò)誤處理方法總結(jié)

    這篇文章主要給大家介紹了關(guān)于async/await優(yōu)雅的錯(cuò)誤處理方法的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-01-01
  • Node.js用Socket.IO做聊天軟件的實(shí)現(xiàn)示例

    Node.js用Socket.IO做聊天軟件的實(shí)現(xiàn)示例

    本文主要介紹了Node.js用Socket.IO做聊天軟件的實(shí)現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-05-05
  • nvm安裝步驟及使用方法

    nvm安裝步驟及使用方法

    nvm是一個(gè)管理nodejs版本的工具。在實(shí)際的開發(fā)中,有些項(xiàng)目的開發(fā)依賴需要低版本的nodejs運(yùn)行環(huán)境,有些則需要高版本的nodejs,此時(shí)我們就需要使用nvm來切換nodejs版本,接下來通過本文給大家講解nvm安裝步驟及使用方法,感興趣的朋友一起看看吧
    2023-01-01

最新評(píng)論