读书笔记:没有理由去强烈坚持自己选择的技术就是最好的,而轻视甚至无视其他技术。如果固执己见,最终受损的的你自己。如果你愿意对技术保持开放的心态,而不是固守自己已经了解的技术,声称它是最好的,你会发现有更多的机会为你敞开大门。~~~
内存泄漏
node 对内存泄漏十分的敏感,有成千上万的流量下,一个字节的泄漏都会造成堆积,在垃圾回收时,扫描对象时耗时更多,影响整个应用的响应时间,知道进程内存溢出,应用崩溃。
造成内存溢出的因素:
- 缓存
- 队列消费不及时
- 作用域未释放
缓存(谨将内存当缓存)
- 缓存在应用中的作用占很重要的地位,可以十分有效的节省资源。如有命中缓存,可以节省一个I/O的时间
- Node中若用一个对象当做缓存,则它将常驻老生代。若缓存的键多,长期存活的对象也多,导致垃圾回收时对这些对象做无用功。影响整个应用。
- JS开发者喜欢用一个对象的键值来缓存东西,严格意义上和缓存又存在这区别,严格的缓存有相应的过期策略,而对象没有。受垃圾回收机制的影响,对象做缓存只能小量使用。
- 缓存的限制策略
为了解决缓存中对象无法释放的问题,需要加入一种策略来限制缓存的无限增长
var LimitableMap = function (limit) {
this.limit = limit || 10; //限制相应的大小
this.map = {};
this.keys = [];
};
var hasOwnProperty = Object.prototype.hasOwnProperty;
LimitableMap.prototype.set = function (key, value) {
var map = this.map;
var keys = this.keys;
if (!hasOwnProperty.call(map, key)) {
if (keys.length === this.limit) {
var firstKey = keys.shift(); //当超过大小时,就做出队列处理
delete map[firstKey];
}
keys.push(key);
}
map[key] = value;
};
LimitableMap.prototype.get = function (key) {
return this.map[key];
};
module.exports = LimitableMap;
- 模块机制造成的内存泄漏,解决策略
模块的引入,为了加速模块的引入,所有的模块都会进行编译执行,缓存起来,由exports导出的函数,可以访问文件模块中的私有变量,这样子文件模块编译执行生成的作用域因为模块的缓存而不会被释放。由于模块的缓存机制,模块是常驻老生代的。(要十分小心内存泄漏)
var leakArray = [];
exports.leak = function () {
leakArray.push("leak" + Math.random());
};
<!--每次外部调用leak时, leakArray 会一直增加,不停的占用内存,且不被释放。-->
若模块不可避免的要使用上面的设计,那么就添加一个清空队列的接口。
- 缓存的解决方案
直接将内存作为缓存要十分谨慎,一不小心就会有很多的问题
- 要限制缓存的大小,不然对垃圾回收,以及内存空间都有很大的影响
- 进程间无法贡献内存信息(缓存的信息)
- 每一个进程都有一个缓存,对物理内存的使用是一种浪费。
- 解决方案
- 若大量使用缓存,比较好的解决方案就是用进程外的缓存,进程自身不缓存状态。外部的缓存有良好的缓存过期淘汰策略和内存的管理,不影响node进程的性能。
- Redis 和 Memcached
关注队列的状态
在解决了缓存带来的内存泄漏后,另一个不经意会产生内存泄漏的就是队列
- JS可以通过队列(数组对象)来完成一些特殊的需求,其在消费者-生成者中经常充当中间产物。
- 在大多数情况下:消费的速度远远大于生成的速度,内存不易泄漏。但是一旦消费速度小于生成速度,将会造成堆积。
- 举个栗子: 写日志,若用数据库来存日志,日志通常是海量的,数据库建在文件系统上,写入效率远低于文件写入,于是就形成数据库写入堆积,Js的作用域也不会得到释放,内存占用不会回落,造成内存泄漏。
- 解决方法: 1. 用文件写入,但是也存在内存泄漏的情况(生成速度因某些原因剧增,或消费因某些原因骤减)。 2.监控队列的长度,一旦出现堆积,就警报,通知开发人员。或者在异步调用里加入超时机制。一旦超时就传递超时异常。