使用v8-profiler优化node服务的性能

服务的性能主要表现在两个主要方面:

  1. 吞吐量
  2. 是否存在内存泄漏

吞吐量——我们通常说的速度快慢

指标包含有QPS、并发数和响应时间

QPS(TPS):每秒钟request/事务 数量

并发数: 系统同时处理的request/事务数

响应时间:  一般取平均响应时间

他们之间的关系:并发数 = QPS * 响应时间

当这些指标达不到我们的系统设计要求,或者说系统预估的访问指标时,就需要通过调优提高指标,或者是增加硬件。

更详细的描述就不在赘述了,因为不是此篇文章的重点。

测量

首先我们先准备一个测试项目,本例的代码稍后展示

接下来我们就可以配置jmeter来对这个服务进行性能测试了。

  1. 首先为测试计划添加线程组
    使用v8-profiler优化node服务的性能_第1张图片
  2. 添加http请求
    使用v8-profiler优化node服务的性能_第2张图片
  3. 根据服务端情况填写配置项
    使用v8-profiler优化node服务的性能_第3张图片
  4. 添加查看结果树
    使用v8-profiler优化node服务的性能_第4张图片
  5. 添加聚合报告
    使用v8-profiler优化node服务的性能_第5张图片

到这里,一个最基本的测试组就完成了。可以点击启动查看执行效果了,在查看结果树模块中可以看到发送的请求和收到的相应数据
使用v8-profiler优化node服务的性能_第6张图片
再来看看聚合报告
使用v8-profiler优化node服务的性能_第7张图片

聚合报告中列出了这次测试的报告信息,这些指标的含义:

  • Label:每个JMeter的element的Name值。例如HTTP Request的Name
  • #Samples:发出请求数量
  • Average:平均响应时间(单位:)。默认是单个Request的平均响应时间,当使用了Transaction Controller时,也可以以Transaction为单位显示平均响应时间
  • Median:中位数,也就是50%用户的响应时间
  • 90%Line:90%用户的响应时间
  • 95%Line:95%用户的响应时间
  • 99%Line:99%用户的响应时间
  • Min:最小响应时间
  • Max:最大响应时间
  • Error%:本次测试中出现错误的请求的数量/请求的总数
  • Throughput:吞吐量。默认情况下标示每秒完成的请求数(具体单位如下图)
  • KB/sec:每秒从服务器端接收到/发送的数据量

以上的测试与其说是对node服务的测试,倒不如说是对jmeter的测试,因为我们只发送了一次请求,接下来我们就来一次压测,看看结果会怎么样。
如图修改线程组的设置,在10秒内启动200线程,循环执行10次
使用v8-profiler优化node服务的性能_第8张图片

执行的结果
在这里插入图片描述

从该结果中可以看出,qps为11,中位数大于16秒,接下来我们看看是否有提升的空间。

调优

目前可以检测服务执行瓶颈的方式很多,我使用了应用最为广泛的v8-profiler。

  1. 首先在项目中引入v8-profiler
const profiler = require('v8-profiler');
  1. 使用API生成监控文件
profiler.startProfiling('1', true);
setTimeout(()=>{
    var profile1 = profiler.stopProfiling();
    profile1.export()
      .pipe(fs.createWriteStream('profile1.cpuprofile'))
      .on('finish', function() {
        profile1.delete();
      });
}, 120000)

这段代码会在项目初始化是启动profiler,然后在120秒后终止,并生成报告。

  1. 启动node服务,启动jmeter对服务加压,直到生成报告
  2. 使用chrome浏览器,找到JavaScript Profiler项目,查看报告
    使用v8-profiler优化node服务的性能_第9张图片
    报告会将最耗时的函数罗列在最上方,而在这份报告中,排在首位的是pow函数,而且还可以查看它的调用栈app.get->slow->pow

好了,现在我们回过头来看一下我们的测试项目的全部代码:

const app = require('express')();
const fs = require('fs');
const profiler = require('v8-profiler-node8');
profiler.startProfiling('1', true);
setTimeout(()=>{
    var profile1 = profiler.stopProfiling();
    profile1.export()
      .pipe(fs.createWriteStream('profile1.cpuprofile'))
      .on('finish', function() {
        profile1.delete();
      });
}, 120000)
function slow(factor) {
  for (let i = 0; i < Math.pow(10, factor); ++i) {
    // blocking the event loop
  }
}

app.get('/', (req, res, next) => {
  slow(7);
  res.json({ success: true });
});

app.listen(3000, () => {
  console.log('Listening at http://localhost:3000 ..\n');
});

到了这里,我想不需多说,大家已经非常清楚了。

接下来我们去掉这个slow再测试一次
因为现在接口没有做任何处理,为了更好地查看效果,我们先对线程组的任务稍作调整
使用v8-profiler优化node服务的性能_第10张图片

将循环设置为永远持续时间设置为1分钟
测试结果
在这里插入图片描述

JMeter的其它常用配置

HTTP Cookie 管理器

上面说的这个应用只是一个单纯的测试项目,而在实际项目中往往是需要进行登录验证的,可以使用HTTP Cookie 管理器配置cookie进行接口测试
使用v8-profiler优化node服务的性能_第11张图片

CSV Data Set Config

CSV Data Set Config可以通过csv提前设置好接口需要使用的一个或多个变量

  1. 首先准备一个文本文件,如图
    使用v8-profiler优化node服务的性能_第12张图片
  2. 添加配置元件CSV Data Set Config
    使用v8-profiler优化node服务的性能_第13张图片
  3. 在http request中使用这些变量
    使用v8-profiler优化node服务的性能_第14张图片

其它更丰富的元件各位请自行探索吧。

内存泄漏

如何检查node内存泄漏的文章很多,这里使用一个非常简单的项目来告诉大家如何使用v8-profiler、JMeter以及Chrome相结合来排查内存漏洞。

//app.js
const app = require("express")();
const fs = require("fs");
const profiler = require("v8-profiler");
let timesRun = 0;
const Leak = require("./leak");

//v8-profiler start
const takeSnapshot = () => {
  if (timesRun >= 3) {
    clearInterval(interval);
  }
  var snapshot1 = profiler.takeSnapshot();
  snapshot1.export(function(error, result) {
    fs.writeFileSync("snapshot" + timesRun + ".heapsnapshot", result);
    snapshot1.delete();
  });
  timesRun++;
  return takeSnapshot;
};

const interval = setInterval(takeSnapshot(), 30000);
//v8-profiler end

app.get("/", (req, res, next) => {
  Leak.leak();
  res.json({ success: true });
});

app.listen(3000, () => {
  console.log("Listening at http://localhost:3000 ..\n");
});

//leak.js
let leakArray = [];
function emptyObject() {}
exports.leak = function() {
  leakArray.push(new emptyObject());
};

该项目接收一个get请求,然后去执行leak.js文件提供的函数leakleak会向leakArray中添加1个对象emptyObject
我们通过使用v8-profiler每隔20秒生成一张内存快照。
接下来启动服务,我们仍然使用JMeter向服务加压。
在这里插入图片描述
持续向服务加压1分20秒,共发送请求83万多次

此时在项目目录中已经生成了3个heapsnapshot文件,我们现在使用chrome来打开这些文件
使用v8-profiler优化node服务的性能_第15张图片

对于这个测试项目,直接对比第一张和第三张快照,就可以清楚地看到emptyObject对象所占用的内存增长最快,其在服务运行40秒后被创建了355175个,而没有一个被销毁。

点击其中一个对象可以看到它的引用关系
使用v8-profiler优化node服务的性能_第16张图片
因为它一直在被leak对象引用,所以无法被释放。

全文完

你可能感兴趣的:(nodejs)