當前位置:編程學習大全網 - 編程語言 - 熱更新真的那麽重要嗎?

熱更新真的那麽重要嗎?

背景:相信使用過Node.js做Web應用的同學,壹定都被新修改的代碼需要重啟Node.js進程才能更新的問題所困擾。習慣用PHP的同學會很不適應,大喊真的是我大PHP才是世界上最好的編程語言。手動重啟進程不僅是壹件非常煩人的重復性工作,而且在應用規模稍大的情況下,啟動時間也是不可忽視的。當然,作為程的,不管妳用哪種語言,妳都不會讓這樣的事情折磨妳。解決這類問題的最直接、最通用的方法是監控文件修改並重啟進程。這個方法也提供了很多成熟的解決方案,比如已經放棄的node-supervisor,現在比較火的PM2,或者比較輕量級的node-dev。本文提供了另壹種思路,只需稍加修改,即可實現真正的0重啟熱更新代碼,解決Node.js Web應用中惱人的代碼更新問題。總體思路說起代碼熱更新,目前Erlang語言最著名的熱更新功能是。這種語言的特點是高並發、分布式編程,主要應用場景類似於證券交易、遊戲服務器等領域。這些場景都要求服務在運營中要有運維的手段,而代碼熱更新是非常重要的壹環,所以我們可以先簡單了解壹下Erlang的做法。由於我也沒用過Erlang,以下內容都是道聽途說。想要深入準確的了解二郎的代碼熱更新,最好查閱官方文檔。Erlang的代碼加載由壹個名為code_server的模塊管理,除了啟動時壹些必要的代碼外,大部分代碼都由code_server加載。當code_server發現模塊代碼已經更新時,它會重新加載模塊,然後用新的模塊執行新的請求,而仍在執行的原請求將繼續用舊的模塊執行。加載新模塊後,舊模塊將被標記為舊模塊,新模塊將被標記為當前模塊。在接下來的熱更新中,Erlang會掃描並殺死仍在執行的舊模塊,然後按照這個邏輯繼續更新模塊。Erlang中並不是所有代碼都允許熱更新,內核、stdlib、編譯器等基礎模塊默認不允許更新。我們可以發現Node.js中有類似code_server的模塊,也就是reuire系統,所以Erlang的做法也應該在Node.js上嘗試壹下,通過了解Erlang的做法,可以大致總結出解決Node.js中代碼熱更新的要點,比如如何更新模塊代碼,如何使用新模塊處理請求,如何釋放舊模塊的資源等。那麽我們就逐壹分析這幾點。如何更新模塊代碼要解決模塊代碼的更新問題,我們需要直接讀取Node.js的模塊管理器實現,鏈接module.js。通過簡單的閱讀,可以發現核心代碼在於模塊。_load,這樣我們就可以簡化代碼並發布。//檢查緩存中重新引用的文件。// 1.如果模塊已經存在於緩存中:返回它的導出對象。// 2.如果模塊是本機的:用//文件名調用` NativeModule.reuire()`並返回結果。// 3.否則,為該文件創建壹個新模塊,並將其保存到緩存中。//然後讓它在返回其exports //對象之前加載文件內容。模塊。_load = function(reuest,parent,isMain) { var filename = Module。_resolveFilename(reuest,parent);var cachedModule =模塊。_ cache[文件名];if(cached module){ return cached module . exports;} var module = new Module(文件名,父項);模塊。_ cache[文件名] =模塊;module.load(文件名);返回模塊. exports;};reuire.cache = Module。_ cache妳可以發現它的核心是模塊。_cache。只要這個模塊緩存被清除,模塊管理器將在下次重新調用時重新加載最新的代碼。寫個小程序驗證//in.js函數cleaner(module){ var path = re uire . resolve(module);re uire . cache[path]= null;} setInterval(function(){ clean cache('。/code . js’);var code = reuire('。/code . js’);console.log(代碼);}, );//code . js module . exports = ' hello world ';我們執行in.js,同時修改code.js的內容,可以發現我們的代碼在控制臺中已經成功更新為最新的代碼。那麽模塊管理器更新代碼的問題就解決了。讓我們看看如何讓新模塊在Web應用程序中實際執行。如何使用新模塊處理請求為了更好的滿足大家的使用習慣,我們就直接以Express為例來展開這個問題。其實用類似的思路,大部分Web應用都可以應用。首先,如果我們的服務像Express的DEMO壹樣,所有代碼都在同壹個模塊裏,我們不能用var express = reuire('express ')熱加載模塊;var app = express();app.get('/'),function(re,RES){ RES . send(' hello world ');});app . listen();要實現熱加載,就像Erlang中不允許的基本庫壹樣,我們需要壹些不能熱更新的基本代碼來控制更新過程。而且,如果重新執行app.listen這樣的操作,和重啟Node.js進程沒有太大區別。因此,我們需要壹些聰明的代碼來區分頻繁更新的業務代碼和不頻繁更新的基本代碼。// app.js基本代碼var express = re uire(' express ');var app = express();var router = reuire('。/router . js’);app.use(路由器);app . listen();// router.js服務代碼var express = re uire(' express ');var路由器= express。路由器();//這裏加載的中間件也可以自動更新router . use(express . static(' public ');router.get('/'),function(re,RES){ RES . send(' hello world ');});module.exports =路由器;不過很遺憾的是,經過這樣的處理,雖然成功分離了核心代碼,但是router.js仍然無法熱更新。首先,由於缺乏更新的觸發機制,服務無法知道何時更新模塊。其次,app.use操作會壹直保存舊的router.js模塊,所以即使更新了模塊,請求還是會被舊的模塊處理,而不是新的模塊。然後繼續完善,需要稍微調整app.js,啟動文件監控作為觸發機制,通過閉包//app . js var express = re uire(' express ')解決app.use的緩存問題;var fs = re uire(' fs ');var app = express();var router = reuire('。/router . js’);App.use (function (re,res,next){//利用閉包的特性獲取最新的路由器對象,避開App。使用緩存路由器對象router(re,res,next);});app . listen();//監聽文件修改,重新加載代碼fs.watch (reuire.resolve('。/router.js ')、function(){ clean cache(re uire . resolve ')。/router . js '));請嘗試{ router = reuire('。/router . js’);} catch (ex) { console.error(“模塊更新失敗”);} });函數clean cache(module path){ re uire . cache[module path]= null;}如果妳再次嘗試修改router.js,妳會發現我們的代碼熱更新已經成型,新的請求會使用最新的router.js代碼。除了修改router.js的返回內容,還可以嘗試修改路由函數,按預期更新。當然,要實現壹個完美的熱更新方案,還需要結合自己的方案做壹些改進。首先,在中間件的使用上,我們可以在app.use聲明壹些不需要熱更新或者每次更新都重復的中間件,而在router.use聲明壹些希望可以靈活修改的中間件,其次,文件監控不僅可以監控路由文件,還可以監控所有需要熱更新的文件。除了文件監控,還可以結合編輯器的擴展功能,在保存時發送或訪問Node.js進程的特定URL來觸發更新。如何釋放舊模塊的資源要解釋清楚如何釋放舊模塊的資源,妳其實需要先了解Node.js的內存回收機制,本文不打算詳細描述。有很多文章和書籍解釋Node.js的內存回收機制,有興趣的同學可以自行拓展閱讀。簡單總結壹下,當壹個對象沒有被任何對象引用時,它會被標記為可回收,內存會在下壹次GC處理時被釋放。那麽我們的話題就是如何更新舊模塊的代碼,保證沒有對象保留模塊的引用。首先,我們以《如何更新模塊代碼》中的代碼為例,看看如果不回收舊的模塊資源會怎麽樣。為了讓結果更有意義,我們修改code . js//code . js var array =[];for(var I = 0;我& lt;i++){ array . push(' mem _ leak _ when _ re uire _ cache _ clean _ test _ item _ '+I);} module.exports =數組;// app.js函數clean cache(module){ var path = re uire . resolve(module);re uire . cache[path]= null;} setInterval(function(){ var code = re uire('。/code . js’);cleanCache('。/code . js’);}, 10);好吧~我們用了壹個很笨拙但是很有效的方法來改善router.js模塊的內存占用,所以再次啟動in.js後會發現內存大幅飆升,不壹會Node.js就會提示進程內存不足。然而實際上,從app.js和router.js的代碼中,我們並沒有發現舊模塊的引用保存在哪裏。我們可以借助壹些分析工具(如node-heapdump)快速定位問題。在module.js中,我們發現Node.js會自動增加壹個引用函數模塊(id,parent){ this . id = id;this . exports = { };this.parent = parent如果(父母& amp& ampparent . children){ parent . children . push(this);} this.filename = nullthis.loaded = falsethis . children =[];因此,我們可以調整cleanCache函數,在模塊更新時刪除這個引用。// app.js函數clean cache(module path){ var module = re uire . cache[module path];//移除module . parent if(module . parent){ module . parent . children . splice(module . parent . children . index of(module),1)中的引用;} re uire . cache[module path]= null;} setInterval(function(){ var code = re uire('。/code . js’);cleanCache(reuire.resolve('。/code . js '));}, 10);再執行壹次,這次好多了,內存只會稍微增加,說明老模塊占用的資源已經正確釋放了。使用新的cleanCache功能後,常規使用沒有問題,但還不夠高枕無憂。在Node.js中,除了添加對reuire system的引用,通過EventEmitter進行事件監控也是壹個常用的功能,而且很有EventEmitter在模塊間交叉引用的嫌疑。那麽EventEmitter能正確釋放資源嗎?答案是肯定的。//code . js var moduleA = re uire(' events ')。event emitter();moduleA.on('whatever ',function(){ });當code.js模塊被更新,所有引用都被移除後,只要moduleA沒有被其他未發布的模塊引用,moduleA就會被自動釋放,包括我們內部的事件監控。只有壹種異常的EventEmitter應用場景是這個系統無法處理的,那就是code.js每次執行都會監聽壹個全局對象的事件,這會造成全局對象上的掛載事件不斷,同時Node.js會快速提示檢測到過多的事件綁定,有內存泄漏的嫌疑。此時我們可以看到,只要處理好reuire系統中Node.js自動添加的引用,舊模塊的資源回收問題不大。雖然我們無法像Erlang壹樣實現下壹次熱更新掃描剩余老模塊的細粒度控制,但是可以通過合理的規避手段解決老模塊的資源釋放問題。在Web應用下,還存在壹個引用問題,未釋放的模塊或核心模塊對需要熱更新的模塊有引用,如app.use,導致舊模塊的資源無法釋放,新的請求由新模塊處理。解決這個問題的方法是控制全局變量或引用的暴露條目,並在熱更新執行期間手動更新該條目。例如,如何使用新模塊處理請求中路由器的封裝就是壹個例子。通過對這個portal的控制,我們無論如何引用router.js中的其他模塊,都會隨著portal的釋放而釋放,另壹個會造成資源釋放的問題是setInterval之類的操作會使對象的生命周期無法被釋放。但是我們在Web應用中很少用到這種技術,所以在方案中沒有關註。至此,我們已經解決了Web應用下Node.js的代碼熱更新三大問題。但是由於Node.js本身缺乏對保留對象的有效掃描機制,無法%消除舊模塊的資源無法釋放的問題,類似於setInterval造成的問題。也正是因為這個限制,目前我們提供的YOG2框架中,這項技術主要應用在調試期,通過熱更新,速度很快。生產環境的代碼更新仍然使用重啟或PM2的熱重裝功能來保證在線服務的穩定性。因為熱更新實際上與框架和業務架構密切相關,所以本文沒有給出通用的解決方案。作為參考,簡單介紹壹下我們是如何在YOG2框架中使用這項技術的。由於YOG2框架本身支持前後端系統App拆分,所以我們的更新策略是以App為粒度更新代碼。同時,由於fs.watch這樣的操作會有兼容性問題,所以fs.watchFile等壹些替代品會消耗更多的性能。所以我們結合YOG2的測試機部署功能,通過上傳部署新代碼的方式通知框架需要更新App代碼。在以App為粒度更新模塊緩存的同時,會更新路由緩存和模板緩存,完成所有代碼的更新。如果妳使用的是Express或者Koa之類的框架,只需要按照文中的方法和自己的業務需求對主路由做壹些改動,就可以很好的應用這項技術了。
  • 上一篇:DCS系統跟PLC控制系統有什麽區別?
  • 下一篇:讀技校學什麽專業好就業?
  • copyright 2024編程學習大全網