来源:《NodeJS性能调优》
内存泄露
什么是内存泄露?
程序的运行需要内存。只要程序提出要求,操作系统或者运行时(runtime)就必须供给内存。对于持续运行的服务进程(daemon),必须及时释放不再用到的内存。否则,内存占用越来越高,轻则影响系统性能,重则导致进程崩溃。不再用到的内存,没有及时释放,就叫做内存泄漏(memory leak)。
有些语言(比如C语言)必须手动释放内存,程序员负责内存管理。
具体表现
没有内存泄露
有内存泄露
成锯齿状(saw-tooth pattern)是由Scavenge创建的(新生代),而出现向下跳跃的则是由Mark-Sueep操作产生的(老生代)。
产生的影响
- 随着内存泄漏的增长,V8对垃圾收集器越来越具有攻击性,这会使你的应用运行速度变慢。
- 内存泄露可能触发其他类型的失败。内存泄露的代码可能会持续的引用有限的资源。你可能会耗尽文件描述符;你还可能会突然不能建立新的数据库连接。
- 你的应用迟早会崩溃,并且在你的应用受到欢迎时肯定会发主。
- 浏览器端也会发主内存泄露,只不过浏览器只针对一端,会造成网页的卡顿。
收集(内存快照)
浏览器-开发者工具-Performance(性能管理, 需要⏺)
浏览器-开发者工具-Performance Monitor(性能监控, 默认不显示,需要在more tools激活)
https://juejin.cn/post/6844903536900177928
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检测到内存的使用情况
- Memeye 可视工具, 启动后一句提示访问 http://localhost:23333/ ``` node app.js
//app.js … const memeye = require(‘memeye’); memeye();
- wrk压力测试工具, 需要安装环境
[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
Wrk 属性参数
- -t 需要模拟的线程数<br />
- -c 需要模拟的连接数<br />
- -timeout 超时的时间<br />
- -d 测试的持续时间<br />
执行完之后会返回信息,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)
![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)
实践,压力测试目标
// 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);
<a name="ocoL0"></a>
### 压测工具 jmeter(基于java)
使用场景
1. 功能测试
2. 压力测试
3. 分布式压力测试
4. 纯java开发
5. 上手容易,高性能
6. 提供测试数据分析
7. 各种报表数据图形展示
<a name="QtMAr"></a>
## 诊断工具
node自带
node —expose-gc
[实践] 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);
<a name="NCVNZ"></a>
### 诊断工具Memeye
memeye 是一个轻量级的 NodeJS 进程监控工具,它提供 进程内存、V8 堆空间内存、操作系统内存 三大维度的数据可视化展示
测试过程, 启动服务, 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
测试结果: 对比发现, 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)
<a name="dILxE"></a>
### 诊断工具node-clinic
测试过程, 启动服务,压力测试,终止监测生成报告
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 生成报告
```
对比没有压测状态下
编码规范
- 内存膨胀和内存泄漏有一些差异,内存膨胀主要表现在程序员对内存管理的不科学,比如只需要50M内存就可以搞定的,有些程序员却花费了500M 内存。
- 函数内的变量是可以随着函数执行被回收的,但是全局不行。如果实在业务需求应避免使用对象作为缓存,可移步到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