月度归档:2016年08月

Node.js 内存泄露

#监控日志输出
Process: heapTotal 606.50 MB heapUsed 604.66 MB rss 642.38 MB
-----------------------------------------------------------
Process: heapTotal 607.48 MB heapUsed 604.72 MB rss 642.36 MB
-----------------------------------------------------------
Process: heapTotal 607.48 MB heapUsed 604.73 MB rss 642.36 MB
-----------------------------------------------------------
Process: heapTotal 607.48 MB heapUsed 605.01 MB rss 642.71 MB
-----------------------------------------------------------
Process: heapTotal 607.48 MB heapUsed 605.03 MB rss 642.71 MB
-----------------------------------------------------------
Process: heapTotal 607.48 MB heapUsed 605.64 MB rss 643.43 MB
-----------------------------------------------------------
Process: heapTotal 607.48 MB heapUsed 605.68 MB rss 643.43 MB
-----------------------------------------------------------
Process: heapTotal 608.47 MB heapUsed 605.70 MB rss 643.39 MB
-----------------------------------------------------------
Process: heapTotal 608.47 MB heapUsed 605.72 MB rss 643.39 MB
-----------------------------------------------------------
Process: heapTotal 608.47 MB heapUsed 605.82 MB rss 643.56 MB
-----------------------------------------------------------

可以看到,RSS一直不断变大,主要原因是堆内存持续增加。在Node中,堆内存是V8管理的,那么必定是某变量无法被回收导致。

代码如下:

var config = require('./config');

var http = require('http');
var cheerio = require('cheerio');

// 如果没有输出则list为空数组
var parseHtml = function(html, itemid) {
	$ = cheerio.load(html);
	// ...
};

var showMem = function () {
        var mem = process.memoryUsage();
        var format = function (bytes) {
            return (bytes / 1024 / 1024).toFixed(2) + ' MB';
        };

        console.log('Process: heapTotal ' + format(mem.heapTotal) + ' heapUsed ' + format(mem.heapUsed) + ' rss ' + format(mem.rss));
        console.log('-----------------------------------------------------------');
};

var server = http.createServer(function (req, res) {
	//showMem();
        //调用parseHtml 
});

Http内部调用parseHtml, 所以问题必定出现在parseHtml()函数内,经过一番测试最终发现问题出现在$ = cheerio.load(html)这个语句,由于没有使用var定义变量,导致每次调用都有一份变量挂到global.$上,在进程周期内,全局变量无法被垃圾回收器回收,所以堆内存持续增长。

把var添加到$前,内存终于开始回收了。从测试来看,内存上涨,然后在某个点下降一截,故而可以推测,V8的垃圾回收器视乎不太勤快。

对于小内存机器,V8的垃圾回收内存太慢,短时间过多并发也会导致大量内存占用,为了保护系统安全,有必要设置一个阀值,一旦超越则重启进程(重启必定所有内存均被回收):

#!/bin/bash

live=`ps -efH | grep 'agent.js' | grep -v 'grep' | wc -l`
if [ $live -eq 0 ]; then
	/usr/local/bin/node /var/www/ebay-ap/agent.js 2>&1 >> /var/www/ebay-ap/log.txt &
fi

pid=`ps aux | grep 'agent.js' | grep -v grep | awk '{print $2}'`
rss=`ps aux | grep 'agent.js' | grep -v grep | awk '{print $6}'`

threshold=204800

if [ $rss -gt $threshold ]; then
	cmm="/usr/bin/kill"
	if [ ! -f "$cmm" ]; then
		cmm="/bin/kill"
	fi
        kill=`$cmm -9 $pid`
        if [ $kill ]; then
                echo PID: ${pid} Kill Failed [RSS: ${rss} gt ${threshold}]
        else
                echo PID: ${pid} Kill Success [RSS: ${rss} gt ${threshold}]
		/usr/local/bin/node /var/www/node/agent.js 2>&1 >> /var/www/node.log.txt &
        fi
fi

这段SHELL脚本作用为:
1 如果进程异常退出,自动拉起进程
2 如果进程内存占用超过了阀值,则先杀掉进程,然后启动进程

内存超越阀值,重启进程不但可以保护系统,同时也可以有效防止内存泄露。

Linux 查看进程

杀掉登录用户:

w
 20:46:40 up 12 days,  4:28,  4 users,  load average: 0.30, 0.12, 0.07
USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
root     pts/0    112.97.63.53     20:28    0.00s  0.07s  0.00s w
root     pts/2    125.94.151.83    14:08    3:31m  0.04s  0.04s -bash

ps -ef | grep pts/2

#向上寻找父进程ID
kill -9 pid

动态查看进程CPU内存

#-d 1表示1秒刷新
top -d 1 -p pid

这个可以查看百分比,比如内存占用,如果要查看进程实际霸占的内存,可以通过ps命令。

查看进程霸占内存:

ps aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND

RSS对应实际霸占的内存(KB),这里需要知道,进程占用内存一般是指堆内存(其它一般占比较少),而堆内存内中是有空闲的,不过这个都归到进程内存占用。另外,如果要查看进程更加详细的信息:

cat /proc/pid/status

Name:	node
State:	S (sleeping)
Tgid:	31897
Ngid:	0
Pid:	31897    进程号
PPid:	1        父进程
TracerPid:	0
Uid:	0	0	0	0
Gid:	0	0	0	0
FDSize:	256
Groups:	0 
VmPeak:	 1302912 kB
VmSize:	 1301888 kB
VmLck:	       0 kB
VmPin:	       0 kB
VmHWM:	  123372 kB
VmRSS:	  122772 kB实际霸占内存
VmData:	 1262976 kB
VmStk:	     136 kB
VmExe:	   19980 kB
VmLib:	    4056 kB
VmPTE:	     636 kB
VmSwap:	       0 kB
Threads:	9  线程数量
SigQ:	0/31225
SigPnd:	0000000000000000
ShdPnd:	0000000000000000
SigBlk:	0000000000000000
SigIgn:	0000000000001000
SigCgt:	0000000188004202
CapInh:	0000000000000000
CapPrm:	0000001fffffffff
CapEff:	0000001fffffffff
CapBnd:	0000001fffffffff
Seccomp:	0
Cpus_allowed:	7fff
Cpus_allowed_list:	0-14
Mems_allowed:	00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000001
Mems_allowed_list:	0
voluntary_ctxt_switches:	1373
nonvoluntary_ctxt_switches:	4465

Node.js入门基础知识

Node.js基础知识

/////////////////////////////////////////////////////////
Node.js控制台
1 console.log()与console.error()

console.log("字符串");

console.log("%s", "字符串1", "字符串2");
console.log("%d", 10,10.5);

2 console.dir方法(查看一个对象内容并输出到控制台)

var user = {"name": "vfeelit"};
console.dir(user);

3 console.time()和console.timeEnd()
两个方法都接收一个参数,参数值可以为任何字符串,但两个方法所使用的参数字符串必须相同才能正确统计出开始和结束时间之间锁经过的耗秒数。

4 console.trace方法
将当前位置处的栈信息作为标准错误信息进行输出。

5 console.assert方法
对一个表达式的执行结果进行评估。如果结果为false,则输出一个消息字符串并抛出AssertionError异常。

/////////////////////////////////////////////////////////
Node.js全局作用域及全局函数
1 全局作用域
Node.js中,定义了一个global对象,代码Node.js中的全局命名空间,任何全局变量、函数或对象都是该对象的一个属性值。

2 setTimeout与clearTimeout
3 setInterval与clearInterval
4 定时器对象的unref方法与ref方法

5 与模块相关的全局函数与对象
5.1 require函数加载模块

var f = require('../f.js');
var h = require('http');

模块在首次加载后将缓存在内存缓冲区中。这意味对于相同模块的多次引用得到的都是同一个模块对象,对相同模块的多次引用不会引起模块内代码的多次执行(但是引用到同一个模块,可看成是代码空间,第一运行载入内存)。

5.2 require.resolve()查询完整模块名

5.3 require.cache对象
在Node.js中,定义了一个require.cache对象,该对象代码缓存了所有已被加载模块的缓冲区。可用console.log(require.cache)来查看缓冲区中的内容。require.cache对象具有一个“键名/键值”结构,键名为每个模块的完整文件名,键值为各模块对象,可通过键名访问某一个模块:

require.cache["模块文件名"];
console.log(require.cache[require.resolve('./tm.js')]);

可用delete关键字删除缓存区中的缓存的某个模块对象。

delete require.cache[require.resolve('./tm.js')];

6 __filename 与 __dirname变量
用于获取当前模块文件名的__filename变量与用于获取当前目录名的__dirname变量。

7 EventEmitter类
在Node.js的用于实现各种事件处理的event模块中,定义了一个EventEmitter类。所有可能触发事件的对象都是一个继承了EventEmitter类的子类的实例对象(如果一个对象要可以触发事件,那么它应该是EventEmitter的子类,这样才能针对事件绑定函数)。

addListener(event, listener)	
on(event, listener)
once(event, listener)
removeListener(event, listener)
removeAllListeners([event])
setMaxListeners(n)
listeners(event)
emit(event, [arg1], [arg2])

多次调用on来对同一个事件绑定多个处理函数。默认,针对同一个事件,最多可以绑定10个事件处理函数。可以通过setMaxListeners方法修改最多可以绑定的事件处理函数数量:emitter.setMaxListeners(n)