51Testing软件测试论坛

 找回密码
 (注-册)加入51Testing

QQ登录

只需一步,快速开始

微信登录,快人一步

手机号码,快捷登录

查看: 748|回复: 0
打印 上一主题 下一主题

对node工程进行压力测试与性能分析

[复制链接]
  • TA的每日心情
    擦汗
    昨天 09:02
  • 签到天数: 1046 天

    连续签到: 4 天

    [LV.10]测试总司令

    跳转到指定楼层
    1#
    发表于 2022-8-1 10:13:13 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
    在系统上线前,为了看下系统能承受多大的并发和并发下的负载情况,进行了一轮压测。在压测过程中,发现服务器的cpu飚的的非常高,而tps,接口耗时、服务可用等都是正常的,卧槽,这就奇了怪了,自己想了半天也没想出为啥,不得已求助了大佬,大佬说先查看 cpu processor  what?这是啥??虽然听不懂,但可以查嘛╭(╯^╰)╮,可还没等我查出来,大佬直接上手,一顿骚操作,便找出了原因~ 这着实让自己汗颜啊,内功远远不足啊,回来网上找了资料,恶补一把如何分析node工程中的性能问题。
      在开发过程中,因为过于只关注了业务逻辑的实现,一些可能出现性能的点被忽略掉,而且这些点只能在量稍微大些的并发场景下才会出现,忘了在哪看到一句话 可能会出问题的点,便一定会出问题  性能问题进行分析必不可少。
      样例项目
      为了便于演示,写了个简单的小例子:
    1. // app.js
    2.   const crypto = require('crypto')
    3.   const Koa = require('koa')
    4.   const Router = require('koa-router');
    5.   const app = new Koa();
    6.   const router = new Router();
    7.   router.get('/crypto', async(ctx, next) => {
    8.       const salt = crypto.randomBytes(128).toString('base64')
    9.       const hash = crypto.pbkdf2Sync('crypto', salt, 10000, 64, 'sha512').toString('hex')
    10.       ctx.body = { hash: hash }
    11.       console.log(hash)
    12.       ctx.status = 200
    13.       next()
    14.   });
    15.   let reqNum = 0
    16.   router.get('/empty', async(ctx, next) => {
    17.       ctx.body = { hash: 'empty' }
    18.       reqNum++;
    19.       ctx.status = 200
    20.       next()
    21.   });
    22.   app.use(router.routes()).use(router.allowedMethods());
    23.   app.listen(3000, () => {
    24.       console.log("listen 3000")
    25.   })
    复制代码
    基于koa2,有两个路由,一个/crypto,其中的业务逻辑是,使用crypto库对字符串加密;一个是 /empty,没有业务逻辑的接口,就是个空接口。
     压力测试
      压力测试工具市面上有很多种,就不一一列举了,在社区看到有人推荐 autocannon ,就对这个工具做个介绍,官方的简介是 fast HTTP/1.1 benchmarking tool written in Node.js ,使用node编写的压测工具,能比wrk生成更多负载。
      install
    npm i autocannon -g
      npm i autocannon --save

    use
      提供两种使用方式:
      1. 命令行 autocannon -c 100 -d 5 -p 2 http://127.0.0.1:3000/test 简单快速。
      2. api调用 autocannon(opts[, cb]) 便于编写脚本。

      关键参数有这么几个:
      -c/--connections NUM 并发连接的数量,默认10。
      -p/--pipelining NUM 每个连接的流水线请求请求数。默认1。
      -d/--duration SEC 执行的时间,单位秒。
      -m/--method METHOD 请求类型 默认GET。
      -b/--body BODY 请求报文体。
      还有很多参数,大家可以查看官网文档。
      这个库目前只能支持一个接口压测,我写了个脚本,可以支持批量压测和生成测试报告,具体代码见文末。
      report
      下图是对 /empty 接口压测 autocannon -c 100 -d 5 -p 1 http://127.0.0.1:3000/empty 结果如下:

    可看到,每秒有100个链接,每个链接一个请求,持续5秒,一共产生 31k 次请求。
      报告分三部分,第一行表示接口的延迟,第二行表示每秒的请求数(tps),第三行表示每秒返回的字节数。那么,延迟越低,tps越高,就表示接口性能越好,因为empty 是个空接口,所以它的tps=6221还不错,响应时间也很快,我们换成 /crypto 接口在试试。

    立马看出差距了,这个接口tps只有77,接口耗时达到了1100ms,说明这个接口有很大的优化空间啊。
      生成性能文件与分析
      通过压测工具我们找到了有问题的接口,那接下来,就要对接口进行剖析了,可是光看接口代码,不好分析啊,毕竟没有说服力,我们就需要一份性能报告,用数据说话,下面介绍这个两个方法给大家。
      V8 Profiler
      V8 官方已经为大家考虑到这点了,提供了Profiler工具 使用方式也很快捷,步骤如下。以app.js为例)
      生成报告
      在启动命令中加上 --prof ,如 node --prof app.js ,在项目根目录会生成isolate-xxxxxxx-v8.log格式的文件,用来记录运行期间的调用栈和时间等信息,其中内容如下。(文件较大,就截取最顶端一小截)
    1.  v8-version,6,1,534,47,0
    2.   shared-library,"C:\Program Files\nodejs\node.exe",0x7ff7505f0000,0x7ff751c0f000,0
    3.   shared-library,"C:\WINDOWS\SYSTEM32\ntdll.dll",0x7ff8718a0000,0x7ff871a61000,0
    4.   shared-library,"C:\WINDOWS\system32\KERNEL32.DLL",0x7ff870590000,0x7ff87063d000,0
    5.   shared-library,"C:\WINDOWS\system32\KERNELBASE.dll",0x7ff86e830000,0x7ff86ea18000,0
    6.   shared-library,"C:\WINDOWS\system32\WS2_32.dll",0x7ff86ee00000,0x7ff86ee6b000,0
    复制代码
    分析报告
      对刚刚生成的log文件分析,还是使用官方提供的工具 node --prof-process isolate-xxxxxxxx-v8.log,生成结果如下。(去掉无用的部分)
    1. Statistical profiling result from isolate-00000209B99A60A0-v8.log, (17704 ticks, 8 unaccounted, 0 excluded).
    2.    [Shared libraries]:
    3.      ticks  total  nonlib   name
    4.     13795   77.9%          C:\WINDOWS\SYSTEM32\ntdll.dll
    5.     ...
    6.    [JavaScript]:
    7.      ticks  total  nonlib   name
    8.        12    0.1%   11.3%  Builtin: CallFunction_ReceiverIsAny
    9.        ...
    10.    [C++]:
    11.      ticks  total  nonlib   name
    12.    [Summary]:
    13.      ticks  total  nonlib   name
    14.        94    0.5%   88.7%  JavaScript
    15.         0    0.0%    0.0%  C++
    16.         8    0.0%    7.5%  GC
    17.     17598   99.4%          Shared libraries
    18.         8    0.0%          Unaccounted
    19.    [C++ entry points]:
    20.      ticks    cpp   total   name
    21.    [Bottom up (heavy) profile]:
    22.     Note: percentage shows a share of a particular caller in the total
    23.     amount of its parent calls.
    24.     Callers occupying less than 1.0% are not shown.
    25.      ticks parent  name
    26.     13795   77.9%  C:\WINDOWS\SYSTEM32\ntdll.dll
    27.      3795   21.4%  C:\Program Files\nodejs\node.exe
    28.      3768   99.3%    C:\Program Files\nodejs\node.exe
    29.      3287   87.2%      Function: ~pbkdf2 crypto.js:633:16
    30.      3287  100.0%        Function: ~exports.pbkdf2Sync crypto.js:628:30
    31.      3287  100.0%          Function: ~router.get D:\github\webapp\js\usen\app.js:8:23
    32.      3287  100.0%            Function: ~dispatch D:\github\webapp\js\usen\node_modules\_koa-compose@3.2.1@koa-compose\index.js:37:23
    33.       ...
    复制代码
    报告包含六部分:Shared libraries、JavaScript、C++、Summary、C++ entry points 和 Bottom up (heavy) profile,[JavaScript] 部分列出了 JavaScript 代码执行所占用的 CPU ticks(CPU 时钟周期),[C++] 部分列出了 C++ 代码执行所占用的 CPU ticks,[Summary] 列出了各个部分的占比,[Bottom up] 列出了所有 CPU 占用时间从大到小的函数及堆栈信息。
      根据 3287 87.2% Function: ~pbkdf2 crypto.js:633:16 可看出这个函数消耗了 87.2% 的cpu。
      文件的方式不直观,那我们换个UI界面的,步骤如下:
      ·先clone v8的仓库下来 git clone https://github.com/v8/v8.git
      · 将日志文件转换成 json格式 node --prof-process --preprocess isolate-xxxxxxxxxx-v8.log > v8.json
      · 打开 v8/tools/profview/index.html 文件,是个静态界面,在界面中心选择刚生成的 v8.json文件,文件解析成功后,界面如下:

    具体的功能就不一一解释啦,我们逐层展开,寻找耗时的点,很快便找到耗cpu的地方,如下图:

    node占比是45%,其中 pbkdf2 crypto.js便占用了92%。
      v8-profiler
      除了官方提供之外,我们还可以选择开源大佬的库,v8-profiler ,这个库的创建的时间比较早,6年前便创建了,最近一次更是在一年半前,社区评价还是不错的。
      生成报告
      生成方式很简单,不足的是,需要硬编码在项目中,如下:

    1. profiler.startProfiling('', true);
    2.   setTimeout(function() {
    3.     var profile = profiler.stopProfiling('');
    4.     profile.export()
    5.        .pipe(fs.createWriteStream(`cpuprofile-${Date.now()}.cpuprofile`))
    6.        .on('finish', () => profile.delete())
    7.   }, 1000);
    复制代码
    解析报告
      ·Chrome
      我们的大Chrome要出马啦,在Chrome的控制台,有一栏 JavaScript Profile 如下图:

    点击load,选择刚刚生成的文件,解析后如下:

    逐层查看,便了然。
      · flamegraph-火焰图
      使用 flamegraph 生成酷炫的火焰图,用在报告那是酷炫的一逼,官网图如下:

    使用方式就不细说啦。
      · v8-analytics
      这个是社区大佬们,写的一个开源库 v8-analytics,官方介绍如下
      解析v8-profiler和heapdump等工具输出的cpu & heap-memory日志,可以提供:
      1)v8引擎逆优化或者优化失败的函数标红展示以及优化失败原因展示;
      2)函数执行时长超过预期标红展示;
      3)当前项目中可疑的内存泄漏点展示。
      对应的命令如下:
      va test bailout --only 这个命令可以只把那些v8引擎逆优化的函数列出来展示。
      va test timeout 200 --only 这个命令可以只把那些执时长超过200ms的函数列出来展示。
      va test leak 可疑展示出测试的heapsnapshot文件中可疑的内存泄漏点。
      这个库的好处是,省的我们一个个去点开查找,这样可以更加便于我们筛选问题啦~
      批量压力测试及生成报告
      autocannon 只能运行一个接口,要想在测试下一个接口,就得修改代码,比如想批量测试多个接口,就需要来回改代码,操作就比较麻烦,所以我基于 autocannon 写了个脚本,可以逐一压测定义好的接口,同时还可以生成测试报告。
    1.  'use strict'
    2.   const autocannon = require('autocannon')
    3.   const reporter = require('autocannon-reporter')
    4.   const path = require('path')
    5.   const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
    6.   /**
    7.    * @description
    8.    * 运行autocannon
    9.    * [url=home.php?mod=space&uid=267564]@Author[/url] lizc
    10.    * @param {*} param
    11.    */
    12.   function makeAutocannon(param) {
    13.       autocannon(param).on('done', handleResults)
    14.   }
    15.   /**
    16.    * @description
    17.    * 处理接口
    18.    * @author lizc
    19.    * @param {*} result
    20.    */
    21.   function handleResults(result) {
    22.       const reportOutputPath = path.join(`./${result.title}_report.html`)
    23.       reporter.writeReport(reporter.buildReport(result), reportOutputPath, (err, res) => {
    24.           if (err) console.err('Error writting report: ', err)
    25.           else console.log('Report written to: ', reportOutputPath)
    26.       })
    27.   }
    28.   // 请求参数
    29.   const autocannonParam = {
    30.       url: 'http://127.0.0.1:6100/',
    31.       connections: 100,
    32.       duration: 10,
    33.       headers: {
    34.           type: 'application/x-www-form-urlencoded'
    35.       }
    36.   }
    37.   // 请求报文参数
    38.   const requestsParam = {
    39.       method: 'POST', // this should be a put for modifying secret details
    40.       headers: { // let submit some json?
    41.           'Content-type': 'application/json; charset=utf-8'
    42.       }
    43.   }
    44.   /**
    45.    * @description
    46.    * 启动批量压测
    47.    * @author lizc
    48.    * @param {*} methodList 接口列表
    49.    */
    50.   async function run(methodList) {
    51.       const autocannonList = methodList.map(val => {
    52.           return {
    53.               ...autocannonParam,
    54.               url: autocannonParam.url + val,
    55.               title: val,
    56.               requests: [
    57.                   {
    58.                       ...requestsParam,
    59.                   }
    60.               ],
    61.           }
    62.       })
    63.       for (let i = 0; i < autocannonList.length; i++) {
    64.           if (i !== 0) {
    65.               await sleep((autocannonList[i - 1].duration + 2) * 1000)
    66.               makeAutocannon(autocannonList[i])
    67.           } else {
    68.               makeAutocannon(autocannonList[i])
    69.           }
    70.       }
    71.   }
    72.   // 启动
    73.   run(['order', 'crypto'])
    复制代码
    小结
     我是github的搬运工

    以上的方法基本上能满足我们的需求,当然性能涉及的方方面面很多比如内存泄漏、事物等,性能调优路漫漫呀,文章大部分东西都是来自大佬们的总结,我只是在做一次整理汇总,便于自己理解与查阅,希望能帮到小伙伴们~











    本帖子中包含更多资源

    您需要 登录 才可以下载或查看,没有帐号?(注-册)加入51Testing

    x
    分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
    收藏收藏
    回复

    使用道具 举报

    本版积分规则

    关闭

    站长推荐上一条 /1 下一条

    小黑屋|手机版|Archiver|51Testing软件测试网 ( 沪ICP备05003035号 关于我们

    GMT+8, 2024-11-15 03:51 , Processed in 0.066120 second(s), 24 queries .

    Powered by Discuz! X3.2

    © 2001-2024 Comsenz Inc.

    快速回复 返回顶部 返回列表