《大前端進階Node.js》系列 內存泄漏(一)

前言

Coding 應當是一輩子的事業,而不只僅是 30 歲的青春飯 本文已收錄 Github https://github.com/ponkans/F2E,歡迎 Star,持續更新前端

大爺: 看小夥子眉清目秀,不妨先跟我講講什麼是內存泄漏?node

小夥:(內心一陣暗喜)官方解釋是程序中己動態分配的堆內存因爲某種緣由程序未釋放或沒法釋放.git

但其本質其實就是一個,那就是應當回收的對象沒有被回收,變成了常駐在老生代中的對象。github

不少人說閉包會形成內存泄漏,其實說法不嚴謹,應該說是,閉包若是使用不當,容易引起內存泄漏,而不是閉包必定會形成內存泄漏。web

大爺:小夥子別激動,那你知道 Node.js 中形成內存泄漏的緣由通常有哪些嘛?redis

小夥:嗯,這個我也知道。能夠從緩存的角度來分析。算法

緩存

緩存在項目中是頗有做用的,由於一旦命中緩存,就能夠節約一次I/O的時間,而且訪問效率比I/O要高不少。npm

好比下面這種方式:緩存

let cache = {}
const setValue = function (value) {  cache[key] = value } const getValue = function () {  if (cache[key]) return cache[key]  // 從其它渠道獲取 } 複製代碼

咱們都知道 V8 的垃圾回收機制是分新生代跟老生代的(若是不熟悉的小夥伴,能夠留言,後面出一篇 V8 垃圾回收機制)。安全

那麼一旦一個對象被當作緩存來使用,那就意味着這個對象會常駐老生代。

同時隨着你緩存對象的不斷增多,緩存對象也會愈來愈多,愈來愈大,垃圾回收在進行處理的時候,就會作不少無用功。

所以,上面的使用方式實際上存在一些問題:

  • 未限定緩存對象大小
  • 沒有實現過時策略,也就是緩存淘汰策略,否則內存會無限制增加,數據一旦多了,很容易出現內存泄漏,一些非熱點的數據過多的堆積在內存,致使內存瞬間衝到峯值

所以,須要對上面的用法,接着改進,接着奏樂~

改進方式:FIFO-Cache

class Cache {
 constructor (limit = 5) {  this.limit = limit  this.map = {}  this.keys = []  }   set (key,value) {  let map = this.map  let keys = this.keys  if (!Object.prototype.hasOwnProperty.call(map,key)) {  if (keys.length === this.limit) {  // 先進先出的策略進行淘汰  delete map[keys.shift()]  }  keys.push(key)  }  map[key] = value  }   get (key) {  return this.map[key]  } } 複製代碼

實現方式很簡單,一旦key超過設定的限制值,就以隊列先進先出的方式進行淘汰。

大爺:小夥子,你上面這種方式只能針對一些比較簡單的場景,若是須要更加高效的緩存,是否能夠再優化?

小夥:這。。小夥欲言又止,始終仍是沒有說出那三個字~

LRU:最近被訪問的數據那麼它未來訪問的機率就大,緩存滿的時候,優先淘汰最無人問津者。相比FIFO,會更加精準跟高效

繼續優化,LRU-Cache

get (key) {
 if (this.cache.has(key)) {  let cache = this.cache  const value = cache.get(key)  cache.delete(key)  cache.set(key, value)  return value  }  return '其它渠道獲取'  }   set (key, value) {  let cache = this.cache  if (cache.has(key)) {  cache.delete(key)  } else if (cache.size === this.limit) {  cache.delete(cache.keys().next().value)  }  cache.set(key, value)  } } 複製代碼

以我的的經驗來看,在Node中,任何試圖拿內存來作緩存的行爲都應該被限制(被限制不是說徹底不能使用,可是須要謹慎使用)。

在特定場景下,仍是可使用的,畢竟走本地內存必定會比走網絡(好比Redis服務)更快

  • 做爲核心數據兜底,好比但願從 Redis 未取到數據時,在走一個本地的內存緩存兜底
  • 針對一些特殊的場景,能夠在結合 LRU 算法作緩存,這樣也不會形成前面說的內存被無限制使用

所以,直接將內存做爲緩存存在不少的問題,須要開發者考慮的事情不少。

除了前面說的限制緩存大小,設置緩存淘汰等機制之外,還須要考慮進程間重複的緩存如何共享(畢竟進程之間沒法共享內存)。

因此,咱們能夠將緩存轉移到外部,不只能夠減小內存中緩存對象數量,讓垃圾回收更加高效,同時還實現了進程間共享緩存。

進程之間共享緩存是不少場景下都是頗有必要的,可參考《大前端進階 Node.js》系列 雙十一秒殺系統,裏面有提到這個問題

我的比較推薦Redis,各方面都作的很成熟,使用 Redis,上面提到的問題都不是問題了~

總結

本文已收錄 Github https://github.com/ponkans/F2E,歡迎 Star,持續更新

寫這篇文章,是由於怪怪跟同事最近一塊兒討論 Node.js 內存泄漏的場景,因此計劃簡單寫一個小系列~

小結:

  • 內存作緩存需考慮的方面較多,需謹慎使用,否則容易引起內存泄漏
  • 若是須要使用大量緩存,建議使用進程以外的緩存,我的推薦 Redis

近期原創傳送門,biubiubiu:


喜歡的小夥伴加個關注,點個贊哦,感恩💕😊

聯繫我 / 公衆號

微信搜索【接水怪】或掃描下面二維碼回覆」加羣「,我會拉你進技術交流羣。講真的,在這個羣,哪怕您不說話,光看聊天記錄也是一種成長。(阿里技術專家、敖丙做者、Java3y、蘑菇街資深前端、螞蟻金服安全專家、各路大牛都在)。

接水怪也會按期原創,按期跟小夥伴進行經驗交流或幫忙看簡歷。加關注,不迷路,有機會一塊兒跑個步🏃 ↓↓↓

相關文章
相關標籤/搜索