天天看點

js定時器一直開啟會增加記憶體麼_Node.js一年開發經驗總結

點選上方“藍字”關注本公衆号

js定時器一直開啟會增加記憶體麼_Node.js一年開發經驗總結

寫在前面

不知不覺的,寫Node.js已經一年了。不同于最開始的demo、本地工具等,這一年裡,都是用Node.js寫的線上業務。從一開始的Node.js同構直出,到最近的Node接入層,也算是對Node開發入門了吧。目前,我一個人維護了大部分組内流傳下來的Node服務,包括内部系統和線上服務。新增的背景服務,也是盡可能地使用Node進行開發。本文是一下自己最近的一些小小的總結和思考。

本文不會深入講解Node.js本身的特性,架構等等。我也沒有寫過Node擴充或者庫什麼的,對Node.js的了解也并不夠深入。

為何用Node

對于我來說,對于團隊來說,使用Node的原因其實很簡單:開發起來快。熟悉JS的前端同學可以很快上手,節省成本。選一個http server庫起一個server,選擇合适的中間件,比對好請求路由,看情況合理使用ORM庫連結資料庫、增删改查即可。

Node的适用場景

Node.js 使用了一個事件驅動、非阻塞式 I/O 的模型,使其輕量又高效。這種模型使得Node.js 可以避免了由于需要等待輸入或者輸出(資料庫、檔案系統、Web伺服器...)響應而造成的 CPU 時間損失。是以,Node.js适合運用在高并發、I/O密集、少量業務邏輯的場景。

對應到平時具體的業務上,如果是内部的系統,大部分僅僅就是需要對某個資料庫進行增删改查,那麼Server端直接就是Node.js一把梭。

對于線上業務,如果流量不大,并且業務邏輯簡單的情況下,Server端也可以完全使用Node.js。對于流量巨大,複雜度高的項目,一般用Node.js作為接入層,背景同學負責實作服務。如下圖:

js定時器一直開啟會增加記憶體麼_Node.js一年開發經驗總結

同樣是寫JS,Node.js開發和頁面開發有什麼差別

在浏覽器端開發頁面,是和使用者打交道、重互動,浏覽器還提供了各種Web Api供我們使用。Node.js主要面向資料,收到請求後,傳回具體的資料。這是兩者在業務路徑上的差別。而真正的差別其實是在于業務模型上(業務模型,這是我自己瞎想的一個詞)。直接用圖表示吧。

js定時器一直開啟會增加記憶體麼_Node.js一年開發經驗總結
js定時器一直開啟會增加記憶體麼_Node.js一年開發經驗總結

開發頁面時,每一個使用者的浏覽器上都有一份JS代碼。如果代碼在某種情況下崩了,隻會對目前使用者産生影響,并不會影響其他使用者,使用者重新整理一下即可恢複。而在Node.js中,在不開啟多程序的情況下,所有使用者的請求,都會走進同一份JS代碼,并且隻有一個線程在執行這份JS代碼。如果某個使用者的請求,導緻發生錯誤,Node.js程序挂掉,server端直接就挂了。盡管可能有程序守護,挂掉的程序會被重新開機,但是在使用者請求量大的情況下,錯誤會被頻繁觸發,可能就會出現server端不停挂掉,不停重新開機的情況,對使用者體驗造成影響。

Node.js 開發時的注意事項

使用者在通路Node.js服務時,如果某一個請求卡住了,服務遲遲不能傳回結果,或者說邏輯出錯,導緻服務挂掉,都會帶來大規模的體驗問題。server端的目标,就是要 快速、可靠 地傳回資料。

緩存

由于Node.js不擅長處理複雜邏輯(JavaScript本身執行效率較低),如果要用Node.js做接入層,應該避免複雜的邏輯。想要快速處理資料并傳回,一個至關重要的點:使用緩存。

例如,使用Node做React同構直出,

renderToString

這個Api,可以說是比較重的邏輯了。如果頁面的複雜度高,每次請求都完整執行

renderToString

,會長時間占用線程來執行代碼,增加響應時間,降低服務的吞吐量。這個時候,緩存就十分重要了。

實作緩存的主要方式:記憶體緩存。可以使用Map,WeakMap,WeakRef等實作。參考以下簡單的示例代碼:

const cache = new Map();router.get('/getContent', async (req, res) => {  const id = req.query.id;  // 命中緩存  if(cache.get(id)) {    return res.send(cache.get(id));  }  // 請求資料  const rsp = await rpc.get(id);   // 經過一頓複雜的操作,處理資料  const content = process(rsp);  // 設定緩存  cache.set(id, content);  return res.send(content);});
           

使用緩存時,有一個很重要的問題是:記憶體緩存如何更新。一種最簡單的方法,開一個定時器,定期删除緩存,下一次請求到來時,重新設定緩存即可。在上述代碼中,增加如下代碼:

setTimeout(function() {  cache.clear();}, 1000 * 60); // 1分鐘删除一次緩存
           

如果server端完全使用Node實作,需要用Node端直接連接配接資料庫,在資料時效性要求不太高、且流量不太大的情況下,就可以使用上述類似的模型,如下圖。這樣可以降低資料庫的壓力且加快Node的響應速度。

js定時器一直開啟會增加記憶體麼_Node.js一年開發經驗總結

另外,還需要注意記憶體緩存的大小。如果一直往緩存裡寫入新資料,那麼記憶體會越來越大,最終爆掉。可以考慮使用LRU(Least Recently Used)算法來做緩存。開辟一塊記憶體專門作為緩存區域。當緩存大小達到上限時,淘汰最久未使用的緩存。

記憶體緩存會随着程序的重新開機而全部失效。

當背景業務比較複雜,接入層流量,資料量較大時,可以使用如下的架構,使用獨立的記憶體緩存服務。Node接入層直接從緩存服務取資料,背景服務直接更新緩存服務。

js定時器一直開啟會增加記憶體麼_Node.js一年開發經驗總結

當然,上圖中的架構是最簡單的情形,現實中還需要考慮分布式緩存、緩存一緻性的問題。這又是另外一個話題了。

錯誤處理

由于Node.js語言的特性,Node服務是比較容易出錯的。而一旦出錯,造成的影響就是服務不可用。是以,對于錯誤的處理十分的重要。

處理錯誤,最常用的就是

try catch

了。可是

try catch

無法捕獲異步錯誤。Node.js中,異步操作是十分常見的,異步操作主要是在回調函數中暴露錯誤。看一個例子:

const readFile = function(path) {  return new Promise((resolve,reject) => {    fs.readFile(path, (err, data) => {      if(err) {         throw err; // catch無法捕獲錯誤,這和Node的eventloop有關。        // reject(err); // catch可以捕獲      }      resolve(data);    });  });}router.get('/xxx', async function(req, res) {  try {    const res = await readFile('xxx');    ...  } catch (e){    // 捕獲錯誤處理    ...    res.send(500);  }});
           

上面的代碼中,readFile 中 throw 出來的錯誤,是無法被catch捕獲的。如果我們把

throw err

換成

Promise.reject(err)

,catch中是可以捕獲到錯誤的。 

我們可以把異步操作都Promise化,然後統一使用  async 、try、catch 來處理錯誤。

但是,總會有地方會被遺漏。這個時候,可以使用process來捕獲全局錯誤,防止程序直接退出,導緻後面的請求挂掉。示例代碼:

process.on('uncaughtException', (err) => {  console.error(`${err.message}\n${err.stack}`);});process.on('unhandledRejection', (reason, p) => {  console.error(`Unhandled Rejection at: Promise ${p} reason: `, reason);});
           

關于Node.js中錯誤的捕獲,還可以使用

domain

子產品。現在這個子產品已經不推薦使用了,我也沒有在項目中實踐過,這裡就不展開了。Node.js 近幾年推出的 async_hooks 子產品,也還處于實驗階段,不太建議線上環境直接使用。做好程序守護,開啟多程序,錯誤告警及時修複,養成良好的編碼規範,使用合适的架構,才能提高Node服務的效率及穩定性。

寫在後面

本文總結了Node.js開發一年多以來的實踐總結等。Node.js的開發與前端網頁的開發思路不同,着重點不一樣。我正式開發Node.js的時間也不算太長,一些點并沒有深入的了解,本文僅僅是一些經驗之談。歡迎交流。

推薦閱讀:

【譯】深入了解V8

V8引擎的記憶體管理

【譯】JavaScript工作原理:V8編譯器的優化

js定時器一直開啟會增加記憶體麼_Node.js一年開發經驗總結