js比较时间大小_初探性能分析之Node.js

692fa68df4a160a460aed9d7c6abb4d4.png

字节跳动内推啦

3af67526c0784d52772de0d34bb3a3ec.png

常见性能问题

内存泄露

  1. 作用域未释放:闭包及时释放
  2. 缓存空间没有限制:利用lru-cache模块
  3. 队列消费不及时:限制队列长度

线程阻塞

  1. 采用单进程方案+线程池方案实现对大并发的支持
  2. node 模块中IO密集型任务、CPU密集型任务都是采用线程池完成
  3. 自定义任务也可以通过C++模块箱libuv提交任务​​​
  4. 事件轮询线程本身并不维护队列,而是一大堆文件描述符​​

6f82064b789753666f007e19972105c1.png

同步IO调用(fs.xxxSync)

直接占用事件循环线程
  1. 使用异步调用
  2. 调用函数warp后自动检测并告警和统计

CPU密集型计算占用线程池、或事件循环线程

  1. 避免在事件循环线程处理CPU密集型任务
  2. 最小化任务执行时间
  3. 使用worker线程
  4. 拆分任务,分片执行任务

正则表达式攻击 REDOS

  1. 避免使用嵌套量词 ​/(a+)*b/​
  2. 避免使用或的重复量词 ​/(a|ab)*/​
  3. 避免使用回溯
  4. 简单匹配使用indexOf
  5. 使用safe-regex校验

JSON DOS

  1. JSON.parse/stringify 有高开销的操作,本身复杂度是O(n)
  2. 限制客户端json对象大小

代码上常见的优化方法

  1. 避免使用:debugger,eval,with
  2. 避免使用 delete,替换为 undefined
  3. 数据处理考虑使用lodash的异步计算,避免使用多次循环
  4. ....

常见有哪些性指标

  1. CPU占用
  2. 内存占用
  3. 请求成功率
  4. qps 请求处理能力
  5. TPS 事务处理能力

常见监控

  1. 容器负载监控
  2. 服务特定指标监控
  3. 服务日志监控
  4. CPU、内存监控

性能评估

压力测试工具

常见工具

httpload、wrk、apache bench、vegeta

wrk工具使用

  1. 安装

brew install wrk

  1. 运行压力测试

wrk -t12 -c400 -d30s https://cs.snssdk.com #

参数解释:

-c, --connections  # 总的http并发数  
-d, --duration    # 持续压测时间, 比如: 2s, 2m, 2h  
-t, --threads     # 总线程数  
-s, --script     # luajit脚本,使用方法往下看  
-H, --header     #  添加http header, 比如. "User-Agent: wrk"     
--latency     # 在控制台打印出延迟统计情况     
--timeout     # http超时时间
  1. 压力测试输出结果
Running 30s test @ https://cs.snssdk.com
 12 threads and 400 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   520.26ms  402.04ms   2.00s 77.85%
    Req/Sec    29.02 16.52 110.00 61.88%
 10046 requests in 30.10s, 117.07MB read
  Socket errors: connect 0, read 0, write 0, timeout 1446
Requests/sec:    333.78
Transfer/sec:      3.89MB

分析工具

【CPU分析】Flame Graph 火焰图

火焰图(Flame Graph) 用于 CPU 的使用情况可视化,可以直观地了解到程序的性能瓶颈。我们通常要结合操作系统的性能分析工具(profiling tracer)使用火焰图,常见的性能分析工具如下:

  1. Linux: perf
  2. windows: xpref.exe
  3. Mac OS: DTrace and Instruments
  4. npm: 0x

perf使用

perf_events(简称 perf)是 Linux Kernal 自带的系统性能分析工具,能够进行函数级与指令级的热点查找。它基于事件采样原理,以性能事件为基础,支持针对处理器相关性能指标与操作系统相关性能指标的性能剖析,常用于查找性能瓶颈及定位热点代码。

  1. 安装centos 虚拟机
    1. 直接使用DevBox机器,上面已经安装了Perf软件
    2. mac上面推荐使用Parallels软件,安装 centos 虚拟机
  2. 安装软件

sudo yum install perf # centos

npm i -g stackvis # 火焰图生成工具,不太好使

FlameGraph clone仓库 + 添加path

3. 创建Nodejs测试项目,生成性能文件

开发阶段使用

perf record -e cycles:u -g -- node --perf-basic-prof lib/dispatch.js -w 1 -e development #启动服务

生产环境使用

perf record -F 99 -p PID -g -- sleep 60 # -F 采样率99HZ, -p 采样进程ID, -g 表示记录调用栈 持续时间 60S

  1. 压力测试
执行命令

wrk -t12 -c400 -d30s http://127.0.0.1:8890/ # ip换成你的服务器IP

2. 生成火焰图

推荐使用FlameGraph生成

运行命令 ​perf script > perfs.out​ 导出堆栈信息

利用stackvis生成

​stackvis perf < perfs.out > flamegraph.htm​

利用​FlameGraph​ 生成

stackcollapse-perf.pl --kernel < ./perfs.out | flamegraph.pl --color=js --hash > ./flamegraph.svg

其他问题:关闭 centos 防火墙关闭方式

0x使用

安装软件

npm i -g 0x # 安装软件

启动服务

0x lib/dispatch.js # 使用0x启动Node服务

启动压力测试

./wrk.sh

自动生成flamegraph文件

【内存分析】heapdump

Make a dump of the V8 heap for later inspection.

Heapdump

安装软件

npm i heapdump

dispatch.js

require('heapdump');# 可以仅仅引用即可,通过信号产生快照

执行快照生成

kill -USR2 `pgrep -n node`

小技巧:可以导出多个快照,通过对比快照之前的差异发现GC异常,切多个快照的时间尽可能长一些,这样存在问题的差异会更大,比较容易看出问题

Chrome DevTools(Memory)

通过​DevTools -> Memory -> Profiles -> Load​ 导入生成的快照,导入后如下图:

6ca654429a762d87e5ba043a80fa80e1.png

快照展示方式有4种:

  1. Summary:以构造函数名分类显示
  2. Comparison:比较多个快照之间的差异
  3. Containment:查看整个 GC 路径
  4. Statistics:以饼状图显示内存占用信息

切换到Summary视图

ffca753deb3890410cd12a557e910c2d.png

相关信息解释:

  1. Constructor: 构造函数名称,带有括号的表示内置对象,比如:(string),(array)
  2. Distance: 距离GC Root对象的距离,距离越远就越可能存在内存泄露可能
  3. Shallow Size:对象自身占用内存大小
  4. Retained Size: 对象+对象引用的占用内存总和

如何分析内存泄露?

这是一份经典的内存泄露代码演示代码

let leakObject = {};
let count = 0; 
async memory(ctx: RouterContext) {
      setInterval(() => {
 const originLeakObject = leakObject;
 const unused = function() {
 if (originLeakObject) {
 console.log('originLeakObject');
          }
        };
        leakObject = {
          count: String(count++),
          leakStr: new Array(1e7).join('*'),
          leakMethod: function() {
 console.log('leakMessage');
          },
        };
      }, 100);
      ctx.body = 'ok';
} 

这份在Demo仓库中,项目运行后,访问​http://127.0.0.1:8890/memory/​,我们导出一份内存快照,过1-2分钟导出第二份快照,并导入ChromeDevTools中进行分析。

  1. 对比快照区别,首先选择第二份快照,然后在视图上面切换为Comparison,在后面选择第一份快照

277be8d24e058232fddffb50d59787ca.png

对比时,可以关注SizeDelta 和FreedSize,SizeDelta记录增加或者减少,FreedSize则是内存释放大小。对于这个例子来说,对比大小增加了244134字节,释放了0字节。内存没有释放,但是一直在增加,说明这个 string 对象有问题,

3439e53851421343a9b26f45575ad1bf.png

展开对象,可以看到一层一层的嵌套引用,其中会显示变量名称,可以通过变量名称去找到代码位置进行进一步分析排查。

开箱即用的工具node-clinic

Clinic.js diagnoses your Node.js performance issues

  1. 可视化的CPU占用+内存占用图
  2. 生成CPU 火焰图
  3. 生成特有的bubbleprof 图
安装

npm install -g clinic

生成诊断图

启动服务

clinic docker -- node lib/dispatch.js # 启动服务

wrk -t12 -c400 -d30s http://127.0.0.1:8890/cpu # 启动压力测试

停止服务

CTRL+C 停止服务,自动生成诊断文件

e5cb98d6be90d3a4cc9936a5748d5777.png

Event Loop 被阻塞,CPU 也居高不下,一定是有 CPU 密集计算,接下来可以使用提供的火焰图来进行分析。

火焰图

启动服务

clinic flame -- node lib/dispatch.js # 启动服务

wrk -t12 -c400 -d30s http://127.0.0.1:8890/cpu # 启动压力测试

停止服务

CTRL+C 停止服务,自动生成诊断文件

f8cb67572c512340e6d89bcfc6015293.png

Bubbleprof图

启动服务

clinic bubbleprof -- node lib/dispatch.js # 启动服务

wrk -t12 -c400 -d30s http://127.0.0.1:8890/cpu # 启动压力测试

停止服务

CTRL+C 停止服务,自动生成诊断文件

0941e0703e5fcfafe3121f1b18be09c5.png

其他分析工具

  1. v8-profiler HEAP Snapshot API + CPU Profile
  2. memwatch GC回收监控
  3. benchmark 测试函数性能

Node.js 提供的性能测量模块

  1. 跟踪事件:提供集中接口管理性能事件、异步钩子事件、启动性能等信息监控的能力
  2. 性能钩子:用于性能测量,例如打点、函数性能测试
import { PerformanceObserver, performance, monitorEventLoopDelay } from 'perf_hooks';
import crypto from 'crypto';


const obs = new PerformanceObserver((items) => {
  items.getEntries().forEach((item) => {
 console.log(item.name, item.duration);
  });
});


obs.observe({ entryTypes: ['function', 'measure'], buffered: true });


function jobA() {
 const salt = crypto.randomBytes(128).toString();
 const encryptedPassword = crypto.pbkdf2Sync('test', salt, 100000, 64, 'sha512').toString();
 return encryptedPassword;
}


function jobB() {
 let x = 0;
 for (let i = 0; i < 10000; i++) {
    x += i;
  }
}
const el = monitorEventLoopDelay({ resolution: 10 });


el.enable();
performance.mark('1');
jobA();
performance.mark('2');
jobB();
performance.mark('3');


performance.measure('jobA', '1', '2');
performance.measure('jobB', '2', '3');


function someFunction() {
  jobA();
  jobB();
}


const wrapped = performance.timerify(someFunction);
wrapped();


const mod = require('module');


// mod.Module.prototype.require = performance.timerify(mod.Module.prototype.require);
// require = performance.timerify(require);


el.disable();
setImmediate(() => {
 console.log(el);
});
异步钩子:跟踪异步操作,例如socket链接、文件操作等
import fs from 'fs';
import asyncHooks from 'async_hooks';


let indent = 0;
const eid = asyncHooks.executionAsyncId();


const tid = asyncHooks.triggerAsyncId();
console.log(eid, tid);


asyncHooks
 .createHook({
    init(asyncId, type, triggerId) {
 const cId = asyncHooks.executionAsyncId();
      print(`${getIndent(indent)}${type}(${asyncId}): trigger: ${triggerId} scope: ${cId}`);
    },
    before(asyncId) {
      print(`${getIndent(indent)}before:  ${asyncId}`);
      indent += 2;
    },
    after(asyncId) {
      indent -= 2;
      print(`${getIndent(indent)}after:   ${asyncId}`);
    },
    destroy(asyncId) {
      print(`${getIndent(indent)}destroy: ${asyncId}`);
    },
  })
 .enable();


function print(str: string) {
  fs.writeSync(1, str + 'n');
}


function getIndent(n: number) {
 return ' '.repeat(n);
}

Debug

Node.js 的调试方式也有很多,常见的有:

  1. 打印日志:console.log
  2. 手动断点:debugger
    1. 使用繁琐,需手动打点。
    2. 若忘记删除 debugger,还会引起性能问题
  3. Chrome DevTools Debug
  4. VSCode Debug
  5. ndb

Chrome DevTools Debug

  1. 启动Nodejs

node --inspect lib/dispatch.js # 开启deubg端口默认是9229端口,当然你可以自定义

可以通过​--inspect-brk​启动立即断点

  1. Chrome打开​chrome://inspect​ 点击inspect

d0969e748a236388f1cbc8d321a3ffd8.png
  1. 添加工作目录,定位源代码

1d3e654a76b9a7823ccaa56256c49495.png
  1. 调试技巧(在断点上面右键编辑)
    1. 支持表达式断点
    2. 支持打印消息

VSCode Debug

  1. 配置​./vscode/launch.json​
{
 // 使用 IntelliSense 了解相关属性。 
 // 悬停以查看现有属性的描述。
 // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
 "version": "0.2.0",
 "configurations": [
    {
 "type": "node",
 "request": "launch",
 "name": "启动程序",
 "skipFiles": [
 "<node_internals>/**"
      ],    
 "args": ["-e","development"],
 "program": "${workspaceFolder}/lib/dispatch",
 "outFiles": [
 "${workspaceFolder}/**/*.js"
      ]
    }
  ]
}
  1. 如果是typescript 项目,需要在编译时添加sourceMap选项,这样才能在源代码上面断点调试
  2. 按下F5启动调试,可直接在源代码上面断点
  3. 断点调试技巧(在断点上面右键编辑)
    1. 表达式:​a.x===true​
    2. 次数断点: ​>=2​
    3. 记录消息: 打印消息到控制台
其他方案,例如:使用ndb进行调试

定位问题技巧

  • 最小化代码复杂度,不断的测试,找到存在问题的模块
  • 断点调试
  • 打印日志
  • 工具分析定位问题代码

参考文章

  1. HTTP性能测试
  2. Nodejs火焰图
  3. Nodejs调试指南
  4. 关于火焰图的解读
  5. 安装perfTool
  6. Perf工具参数介绍
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值