来源:《NodeJS性能调优》

内存泄露

什么是内存泄露?

程序的运行需要内存。只要程序提出要求,操作系统或者运行时(runtime)就必须供给内存。对于持续运行的服务进程(daemon),必须及时释放不再用到的内存。否则,内存占用越来越高,轻则影响系统性能,重则导致进程崩溃。不再用到的内存,没有及时释放,就叫做内存泄漏(memory leak)
有些语言(比如C语言)必须手动释放内存,程序员负责内存管理。

具体表现

没有内存泄露
[优化]NodeJS性能优化 - 图1
有内存泄露
[优化]NodeJS性能优化 - 图2

成锯齿状(saw-tooth pattern)是由Scavenge创建的(新生代),而出现向下跳跃的则是由Mark-Sueep操作产生的(老生代)。

产生的影响

  • 随着内存泄漏的增长,V8对垃圾收集器越来越具有攻击性,这会使你的应用运行速度变慢
  • 内存泄露可能触发其他类型的失败。内存泄露的代码可能会持续的引用有限的资源。你可能会耗尽文件描述符;你还可能会突然不能建立新的数据库连接。
  • 你的应用迟早会崩溃,并且在你的应用受到欢迎时肯定会发主。
  • 浏览器端也会发主内存泄露,只不过浏览器只针对一端,会造成网页的卡顿

收集(内存快照)

浏览器-开发者工具-Performance(性能管理, 需要⏺)

image.png

浏览器-开发者工具-Performance Monitor(性能监控, 默认不显示,需要在more tools激活)

https://juejin.cn/post/6844903536900177928
image.png

node

一般是用工具抓内存快照,然后进行分析。有很多工具:

  • memwatch-next(简memwatch)是一个用来监测 Node.js 的内存泄漏和堆信息比较的模块
  • heapdump 是一个 dump V8 堆信息的工具
  • v8-profiler 收集一些运行时数据

一些极端的方式, 用pm2设置一个内存值上限,超过自动重启

压力测试(寻找内存泄露)

PV:(Page View)网站当日访问人数
UV: (Unique visitor) 独立访问人数
PV每天几十万甚至上百万就需要考虑压力测试。
换算公式 QPS = PV/t (Query Per Second):每秒请求数
ps∶1000000 / 10*60*60=27.7(100万请求集中在10个小时,服务器每秒处理27.7个业务请求)

压测工具众多:WRK、ab、jmeter、autocannon(基于node)

压测工具WRK

方法:启动服务后, 终端wrk命令进行压力测试,可通过memeye检测到内存的使用情况

//app.js … const memeye = require(‘memeye’); memeye();

  1. - wrk压力测试工具, 需要安装环境
  2. [https://www.cnblogs.com/quanxiaoha/p/10661650.html](https://www.cnblogs.com/quanxiaoha/p/10661650.html)

wrk -t12 -c200 -d30s http://127.0.0.1:3000

  1. Wrk 属性参数
  2. - -t 需要模拟的线程数<br />
  3. - -c 需要模拟的连接数<br />
  4. - -timeout 超时的时间<br />
  5. - -d 测试的持续时间<br />
  6. 执行完之后会返回信息,latency 响应时间,req/sec(qps) 每个线程每秒钟完成的请求数。<br />测试指标说明<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/601538/1616831587103-ea39b692-0800-4907-b84c-98398fc9a903.png#align=left&display=inline&height=274&margin=%5Bobject%20Object%5D&name=image.png&originHeight=548&originWidth=1124&size=69746&status=done&style=none&width=562)<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/601538/1616830963336-820b19ff-010d-471b-8e75-76de83fea289.png#align=left&display=inline&height=209&margin=%5Bobject%20Object%5D&name=image.png&originHeight=418&originWidth=1318&size=625505&status=done&style=none&width=659)
  7. ![image.png](https://cdn.nlark.com/yuque/0/2021/png/601538/1616830995256-ec409d8c-e04f-447b-b94e-06fb304768c2.png#align=left&display=inline&height=210&margin=%5Bobject%20Object%5D&name=image.png&originHeight=420&originWidth=1338&size=637469&status=done&style=none&width=669)
  8. 实践,压力测试目标

// const http = require(‘http’); const Koa = require(‘koa’); const Router = require(‘@koa/router’); const memeye = require(‘memeye’); memeye();

const app = new Koa(); const router = new Router();

let s = ‘’; for (let i = 0; i < 1024 * 10; i++) { s += ‘a’; }

const str = s; const buffStr = Buffer.from(s);//实验结果提升20%左右

router.get(‘/buffer’, (ctx, next) => { ctx.res.end(buffStr); }) router.get(‘/string’, (ctx, next) => { ctx.res.end(str); })

app.use(router.routes()) .use(router.allowedMethods)

app.listen(3000);

  1. <a name="ocoL0"></a>
  2. ### 压测工具 jmeter(基于java)
  3. 使用场景
  4. 1. 功能测试
  5. 2. 压力测试
  6. 3. 分布式压力测试
  7. 4. 纯java开发
  8. 5. 上手容易,高性能
  9. 6. 提供测试数据分析
  10. 7. 各种报表数据图形展示
  11. <a name="QtMAr"></a>
  12. ## 诊断工具
  13. node自带

node —expose-gc

  1. [实践] node gc对比map weakmap

// node —expose-gc index.js global.gc(); console.log(process.memoryUsage().heapTotal);

let cache = new WeakMap(); // let cache = new Map(); let k1 = new Array(1024 * 1024); cache.set(k1, null); global.gc(); console.log(process.memoryUsage().heapTotal);

k1 = null; // delete k1; // console.log(Object.keys(cache)); [] global.gc(); console.log(process.memoryUsage().heapTotal);

  1. <a name="NCVNZ"></a>
  2. ### 诊断工具Memeye
  3. memeye 是一个轻量级的 NodeJS 进程监控工具,它提供 进程内存、V8 堆空间内存、操作系统内存 三大维度的数据可视化展示
  4. 测试过程, 启动服务, 1. 先压力测试接口/buffer ,并等待完成 2. 压力测试接口/string

node app.js wrk -t12 -c200 -d30s http://127.0.0.1:3000/buffer wrk -t12 -c200 -d30s http://127.0.0.1:3000/string

  1. 测试结果: 对比发现, buffer处理数据,相对string节省内存<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/601538/1616830854690-d37d5cd7-25ba-43c8-ae21-0fe7f9ae477a.png#align=left&display=inline&height=456&margin=%5Bobject%20Object%5D&name=image.png&originHeight=912&originWidth=2334&size=133354&status=done&style=none&width=1167)
  2. <a name="dILxE"></a>
  3. ### 诊断工具node-clinic
  4. 测试过程, 启动服务,压力测试,终止监测生成报告

clinic doctor — node app.js wrk -t12 -c200 -d30s http://127.0.0.1:3000/buffer wrk -t12 -c200 -d30s http://127.0.0.1:3000/string ctl+c 生成报告 ``` image.png
对比没有压测状态下
image.png

编码规范

  1. 内存膨胀和内存泄漏有一些差异,内存膨胀主要表现在程序员对内存管理的不科学,比如只需要50M内存就可以搞定的,有些程序员却花费了500M 内存。
  2. 函数内的变量是可以随着函数执行被回收的,但是全局不行。如果实在业务需求应避免使用对象作为缓存,可移步到Redis等。

队列消费不及时

还有一种情况,当消费大于生产时没有问题。但是当生产大于消费时就会产生堆积,那么就会内存泄漏。
比如说,node发生错误,做了容错处理输出了日志,正常情况日这样。但是遇到大批量的访问,同时都触发了这个错误,那么日志在一条一条输出的时候不够及时,后方就产生了堆积。这样内存使用越来越大,那么就造成了内存泄漏。
这种情况怎么处理?log4js 的包是一种解决方案,但是最佳方案还是PM2,PM2可以console.log的时候把log的内容打到文件当中,速度非常的快。这样就不会产生消费不及时。

闭包

闭包一定会引发内存泄露

https://hondrytravis.github.io/docs/node/node_optimization/
https://wizardforcel.gitbooks.io/node-in-debugging/content/1.2.html
https://chenshuyao.cn/2019/06/27/memory-leak.html
GC调优 https://www.jianshu.com/p/1edea2f6fd4d