《大前端进阶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、蘑菇街资深前端、蚂蚁金服安全专家、各路大牛都在)。

接水怪也会按期原创,按期跟小伙伴进行经验交流或帮忙看简历。加关注,不迷路,有机会一块儿跑个步🏃 ↓↓↓

相关文章
相关标签/搜索