Node性能调优
内存泄露
程序运行需要内存,对于持续运行的附近进程,必须及时释放不再用到的内存,否则,内存占用会越来越高,轻则影响系统性能,重则导致进程奔溃。不再用到的内存,没有及时释放,叫做内存泄露。
具体表现

随着内存泄露的增长,
V8对垃圾收集器越具有攻击性在,这使得应用运行速度变慢。内存泄漏可能触发其他类型的失败. 内存泄漏的代码可能会持续引用有限的资源,可能会耗尽文件描述符;还可能突然不能建立连接新的数据库连接。
应用会因为资源耗尽奔溃。
点击
Memory收集内存快照内存测试寻找内存泄露
什么是QPS
PV:网站当日访问人数UV独立访问人数
QPS=PV/t如:1000000/106060 = 27.7(100万个请求集中在10个小时,服务每秒需处理27.7个业务请求)
wrk压力测试
wrk是一个用来做HTTP benchmark测试的工具
安装wrk
brew install wrk
命令行输入wrk。查看帮助
使用方法: wrk <选项> <被测HTTP服务的URL>
Options:
-c, --connections <N> 跟服务器建立并保持的TCP连接数量
-d, --duration <T> 压测时间
-t, --threads <N> 使用多少个线程进行压测
-s, --script <S> 指定Lua脚本路径
-H, --header <H> 为每一个HTTP请求添加HTTP头
--latency 在压测结束后,打印延迟统计信息
--timeout <T> 超时时间
-v, --version 打印正在使用的wrk的详细版本信息
<N>代表数字参数,支持国际单位 (1k, 1M, 1G)
<T>代表时间参数,支持时间单位 (2s, 2m, 2h)
做一次简单压力测试
"scripts": {
"test": "wrk -t12 -c200 -d 60s http://127.0.0.1:3000"
}
启动命令
node index.js
node run test
压测结果如下:

| 属性 | 名称 | 含义 |
|---|---|---|
| Avg | 平均值 | 每次测试的平均值 |
| Stdev | 标准偏差 | 结果的离散程度,越高说明越不稳定 |
| Max | 最大值 | 最大结果 |
| +/- SStdev | 正负一个标准差占比 | 结果的离散程度,越大越不稳定 |
Latency:响应时间
Req/Sec:每个下沉每秒钟完成的请求数,一般来说主要关注平均值和最大值,标准差如果太大说明样本本身离散程度比较高,有可能系统性能波动很大。
也可以使用autocannon
使用Memeye查看内存
Memeye是一个轻量级的NodeJS进程监控工具,它提供进程内存、V8堆空间内存、操作系统内存三大维度的数据可视化展示。
npm install memeye -D
用法如下:
const http = require('http');
const memeye = require('memeye');
memeye();
let leakArray = [];
const server = http.createServer((req, res) => {
if (req.url == '/') {
leakArray.push(Math.random());
console.log(leakArray);
res.end('hello world');
}
});
server.listen(3000);
再次启动Node服务,会在本地启动另一个服务http://localhost:2333如下:
process.memoryUsage
使用process.memoryUsage可以查看内存使用情况如下:
console.log(process.memoryUsage())
let map = new Map();
let key = new Array(5 * 1024 * 1024);
map.set(key, 1);
key = null
console.log(process.memoryUsage());
终端hi显示内存占用情况
{
rss: 18481152,
heapTotal: 4014080,
heapUsed: 2237704,
external: 764981,
arrayBuffers: 9382
}
{
rss: 61792256,
heapTotal: 46886912,
heapUsed: 44959104,
external: 933057,
arrayBuffers: 9382
}
内存泄露原因以及编码规范
内存泄露
如下针对某个接口进行统计:
console.log('statr', process.memoryUsage())
let leakArray = []
app.get('/', function (req, res) {
leakArray.push('leak' + Math.random())
console.log('end', process.memoryUsage());
res.send('hello word')
})
这时内存会不断膨胀,因为函数内的变量是随着函数执行被回收的,但全局的不行。如果实在业务需要应避免使用对象作为缓存,可移步到Redis等。
队列消费不及时
例:日志收集功能,如果日志产生速度大于文件写入速度,就容易产生内存泄露(压测工具已经收到返回,服务器log4j日志还在不停写入)。简单解决办法可以更换消费速度更快的速度,但这不治本。
根本解决办法应该是监测队列长度,一旦堆积就报警或者拒绝新的请求,还有一种是将所有的异步调用都设置超时时间,一旦达到超时时间调用未得到结果就报警。
编码不规范
闭包
function foo() {
var tem_obj = {
x: 1,
y: 2,
arr: new Array(20000)
}
// 目前闭包只引用了clone
let clone = tem_obj.x
return function () {
return clone
}
}
如上,闭包中只使用了x,那么只需要引入tem_obj.x即可,而不是返回整个tem_obj。
- 闭包不可怕,可怕的是闭包里面引用了大对象
GC无法回收
let map = new Map();
let key = new Array(5 * 1024 * 1024)
map.set(key, 1)
key = null
正确的是先移除map对key的引用,再把key设置null
map.delete(key)
key=null
频繁的垃圾回收会让GC没有机会工作
function strToArray(str, bufferView) {
let i = 0;
const len = str.length;
for (; i < len; i++) {
bufferView[i] = str.charCodeAt(i) + Math.random();
}
return bufferView;
}
function foo() {
let i = 0;
let str = 'test v8 GC';
// SharedArrayBuffer = 连续的内存
let bufferView = [];
while (i++ < 10000) {
strToArray(str, bufferView);
}
}
foo();
[8135:0x1045e7000] 43 ms: Scavenge 2.3 (3.0) -> 1.9 (4.0) MB, 0.8 / 0.0 ms (average mu = 1.000, current mu = 1.000) allocation failure
[8135:0x1045e7000] 54 ms: Scavenge 2.4 (4.0) -> 2.0 (5.0) MB, 0.7 / 0.0 ms (average mu = 1.000, current mu = 1.000) allocation failure
[8135:0x1045e7000] 55 ms: Scavenge 3.0 (5.0) -> 2.0 (7.0) MB, 0.2 / 0.0 ms (average mu = 1.000, current mu = 1.000) allocation failure
[8135:0x1045e7000] 57 ms: Scavenge 4.0 (7.0) -> 2.0 (7.0) MB, 0.1 / 0.0 ms (average mu = 1.000, current mu = 1.000) allocation failure
# 优化后
node --trace-gc demo4.js
[8139:0x1045e7000] 36 ms: Scavenge 2.3 (3.0) -> 1.9 (4.0) MB, 0.8 / 0.0 ms (average mu = 1.000, current mu = 1.000) allocation failure
[8139:0x1045e7000] 46 ms: Scavenge 2.4 (4.0) -> 2.0 (5.0) MB, 0.8 / 0.0 ms (average mu = 1.000, current mu = 1.000) allocation failure
[8139:0x1045e7000] 47 ms: Scavenge 3.0 (5.0) -> 2.0 (7.0) MB, 0.2 / 0.0 ms (average mu = 1.000, current mu = 1.000) allocation failure
Buffer
调试工具clinicjs
总结
- 准确计算QPS未雨绸缪
- 利用压测工具发现内存是否有异常
- 缓存 队列内存泄露 耗时较长的任务