Nodejs性能调优
内存泄露
概述
程序的运行需要内存,主程序提出申请,操作系统或者运行时就必须供给内存,对于持续运行的服务进程(daemon),必须及时释放不再用到的内存,否则,内存占用越来越高,轻则影响系统性能,重则导致系统进程崩溃。不再用到的内存,没有及时释放,就叫做内存泄露(memory leak)。
有些语言(比如C语言)必须手动释放内存,程序员负责内存管理。
具体表现
如下图就存在内存泄露:

没有内存泄露:

成锯齿状(saw-tooth pattern)是由Scavenge创建的,而向下跳跃的则是由Mark-Sweep操作产生的。
随着内存泄露的增长,V8对垃圾收集器越来越具有攻击性,这使得应用程序运行速度变慢。
内存泄露可能触发其他类型的失败。内存泄露的代码可能会储蓄的引用有限的资源。可能会耗尽文件描述符。也可能会突然不能建立新的数据库连接。应用迟早会崩溃,尤其是访问量大的时候。浏览器端也会发生内存泄露,只不过浏览器只针对一端,会造成网页的卡顿。
如何得到程序逇内存泄露信息
node自带的process.memoryUsage返回一个对象包括如下四个字段:
- res 所有的内存占用
- heapTotal堆占用的内存,用到的没用到的都有
- heapUsed用到的堆的部分
- exter V8内部的C++对象占中的内存
判断内存泄露以heapUsed为准。
第三方插件
memwatch(监听内存GC)
它是一个内存泄露事件发射器,内存仍被持续分配没有得到释放,就会生成快照。
heapdump(生成内存快照)
一个状态事件发射器
压力测试寻找内存泄露
PV:网站当日访问人数
UV:独立访问人数
PV每天几十万甚至上百万就需要考虑压力测试。
换算公式QPS=PV/t
ps:100000/10*60*60=27.7(100万请求集中在10个小时,服务器每秒处理27.7个业务请求)
测试工具wrk
可以通过wrk
| 项目 | 名称 | 说明 |
|---|---|---|
| Avg | 平均值 | 每次测试的平均值 |
| Stdev | 标准偏差 | 结果的离散程度,越高说明越不稳定 |
| Max | 最大值 | 最大的一次结果 |
| +/- Stdev | 正负一个标准差占比 | 结果的离散程度,越大越不稳定 |
Latency: 可以理解为响应时间
Req/Sec:每个 线程每秒钟完成的请求数,一般来说我们主要关注平均值和最大值,标准差如果太大说明样本本身离散程度比较高,有可能系统性能波动很大。
Wrk属性参数:
- -t需要模拟的线程数
- -c需要模拟的连接说
- -d测试的持续时间
执行完之后会返回信息,latency响应时间,req/sec(qps)每个线程每秒完成的请求数。
#用12个线程模拟100个连接,时间为30秒。一般线程数不宜过多,核数的2到4倍即可
./wrk -t12 -c400 -d30s address
package.json
{
"name": "nodedemo",
"version": "1.0.0",
"description": "",
"main": "demo1.js",
"scripts": {//使用make命令生成wrk文件
"test": "./wrk -t12 -c200 -d 60s http://127.0.0.1:3000"
},//t线程 用了3个核,每个四个线程
"keywords": [],
"author": "",
"license": "ISC"
}
js代码
const http = require('http');
let s = '';
for(let i = 0;i<1024*10;i++>){
s+='a';
}
const str = s;
const bufferStr = Buffer.form(s)//实验结果提升20%左右
const server = http.createServer((req.res) => {
if(req.url === './buffer'){
res.end(bufferStr)
} else if(req.url === 'str'){
res.end(str);
}
});
server.listen(3002)
测试工具jmeter
使用场景
- 功能测试
- 压力测试
- 分布式压力测试
- 纯java开发
- 上手容易、高性能
- 提供测试数据分析功能
- 各种报表数据图形展示
引用内存泄露的原因及编码器规范
- 内存膨胀和内存泄露有一些差异,内存膨胀主要是表现为程序员对内存管理的不科学,比如只需要50M内存就可以搞定的,有些程序员却要花费500M内存。
- 函数内的变量是可以随着函数执行被回收的,但是全局不行,如果是再也无需求白银避免使用对象为缓存,可以移步到Redis等。
memeye
memeye是一个轻量级的Nodejs进程监控工具,它提供了进程内存、V8堆空间内存、操作系统内存三大维度的数据可视化展示。
const http = require('http');
const memeye = require('memeye');
memeye();
let leakArray = [];
const server = http.createServer((req,res) => {
if(req.url = '/'){
leakArray.push(Math.rendom());
console.log(leakArray);
res.end('hello word' )
}
});
server.listen(3000);
//红线会居高不下,一直增长无法被gc
node --expose gc
// test.js
global.gc();
//返回当前Node.js使用情况
console.log('第一次', process.memoryUsage());
let map = new Map();
let key = new Array(5 * 1024 * 1024);
map.set(key, 1);
global.gc();
console.log('第二次', process.memoryUsage());
key = null;
console.log('第三次', process.memoryUsage());
map.delete(key);
key = null;
global.gc();
// console.log('第三次', process.memoryUsage());
console.log('第四次', process.memoryUsage());
node --expose-gc test.js
第一次 {
rss: 19636224,
heapTotal: 4644864,
heapUsed: 1767296,
external: 761096,
arrayBuffers: 9382
}
第二次 {
rss: 62472192,
heapTotal: 49217536,
heapUsed: 44176880,
external: 918630,
arrayBuffers: 9382
}
第三次 {
rss: 62664704,
heapTotal: 49217536,
heapUsed: 44195880,
external: 918630,
arrayBuffers: 9382
}
第四次 {
rss: 62787584,
heapTotal: 11464704,
heapUsed: 2234472,
external: 918630,
arrayBuffers: 9382
}
生产消费不及时
当消费大于生产时没有问题,但是当审查你大于消费时就会产生堆积,就会内存泄露。
例:node发生错误,做了错误处理输出了日志,正常情况是这样,但是遇到大批量的 访问,同事都触发了这个错误 ,那么日志在一条一条输出的时候不够及时,后方就产生了堆积,这样内存使用越来越大,那么就造成了内存泄露。
这种情况下如何处理?log4js的包就是一种解决方案,但是最佳方案还是PM2,PM2可以console.log的时候把log打到文件中,速度非常快。这样就不会产生消费不及时。
闭包
闭包一定会引发内存泄露
function foo () {
var tem_obj = {
x: 1,
y: 2,
arr: new Array(20000)
}
//缓存x,用谁存谁,减少内存泄漏, 不要使用 temp_obj, 否则无法回收 tem_obj
let closure = tem_obj.x
return function () {
console.log(closure)
}
}
总结
- 没经过压力测试的Node代码基本只完成10%
- 准确计算QPS未雨绸缪
- 合理利用压力测试工具
- 缓存队列内存泄露耗时较长的代码
- 开发健壮的NodeJS应用