前言

在做性能优化的时候关注的重点一般是web端的性能,但在有Node作为中间层使用的情况下Node端的性能同样需要去关注。

比如遇到网络请求慢的情况,原因除了网速问题以外可能也是Node端响应时间较长,所以就需要去抓取出性能耗费点在哪里。

性能指标

服务端的性能指标比较多,所以我们只需要关注RT和QPS这两个影响页面性能和服务器消耗的就行了

  • RT:响应时长,系统对请求作出的响应时间(单次请求耗时)
  • QPS:单台机器每秒处理查询数量
    QPS的统计方式:
    1
    QPS = 总请求数 / ( 进程总数 * 请求时间 )

假如一个请求的RT从500ms降低到100ms,那么理论上QPS就可以提升4倍,以前需要五台机器才能扛住的流量现在只需要一台,这样可以在减少响应时间的时候同时减少服务器资源消耗

抓取Node端性能

那应该怎样去抓取Node端请求的RT呢,Node本身是自带分析profile的,但是它导出的是日志分析,不太好判断性能消耗点在什么地方

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Concurrency Level:      20
Time taken for tests: 46.932 seconds
Complete requests: 250
Failed requests: 0
Keep-Alive requests: 250
Total transferred: 50250 bytes
HTML transferred: 500 bytes
Requests per second: 5.33 [#/sec] (mean)
Time per request: 3754.556 [ms] (mean)
Time per request: 187.728 [ms] (mean, across all concurrent requests)
Transfer rate: 1.05 [Kbytes/sec] received

...

Percentage of the requests served within a certain time (ms)
50% 3755
66% 3804
75% 3818
80% 3825
90% 3845
95% 3858
98% 3874
99% 3875
100% 4225 (longest request)

所以我们可以用v8-profiler-next,这个插件可以对CPU和堆内存进行抓取,因为我们想要的是请求耗时的数据所以只抓CPU就行了。

在开发/测试环境下还需要使用loadtest压力测试工具做一次CPU密集计算获取耗时平均值。

以电影站请求为例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// router
// 首页请求api
router.get('/api/home/movie-list', controller.home.getMovieList)
// cpu数据上报
router.get('/api/cpuprofile',controller.home.exportCPUProfile)

// controller/home
const profiler = require('v8-profiler-next')
const fs = require('fs')

...
async getMovieList(ctx) {
return ctx.helper.commonStr({ ctx, type: “get”, serverName: “home”, fnName: “getHomeList” })
}

// service/home
async getHomeList(opt: { domain: string, isAmp: string }) {
let { domain, isAmp } = opt

let url = this.ctx.helper.parseParam({
auth: this.app.config.apiAuth,
domain
}, API.MOVIE_HOME_LIST_URL)
try {
let data: any = await this.ctx.service.mc.getMC(url)
...
} catch (err) {
..
}
}

// controller/home
async exportCPUProfile() {
profiler.startProfiling('CPU profile')
//Stop Profiling after default time 60s
setTimeout(() => {
const profile = profiler.stopProfiling()
profile.export()
.pipe(fs.createWriteStream(`cpuprofile-${Date.now()}.cpuprofile`))
.on('finish', () => profile.delete())
console.log('finish')
}, 60000)
this.ctx.body = {
status: 'success'
}
}
...

然后开两个终端分别运行

1
curl http://127.0.0.1:8083/api/cpuprofile
1
loadtest http://127.0.0.1:8083/api/home/movie-list/?domain=movierulz.video&&isAmp=false -n 100

结束抓取后导出的.cpuprofile文件我们是不能直接去看的,还要通过Chrome devtools提供的CPU分析页面展示抓取到的数据。
01.png
进入JavaScript Profiler页面后点击Load按钮导入上面的profile文件就能得到分析结果。
02.png
分析结果有两个指标:

  • Self Time: 函数调用所耗费的时间,仅包含函数本身的声明,不包含任何子函数的执行时间。
  • Total Time: 函数调用所耗费的总时间,包含函数本身的声明及所有子函数执行时间

所以只需要看Total Time即可,然后根据Total Time去定位问题作出具体的优化措施。

参考资料

1.Node.js 应用故障排查手册 —— 正确打开 Chrome devtools
2.使用 v8-profiler 分析 CPU 的使用情况
3.React同构与极致的性能优化
4.QPS、PV 、RT(响应时间)之间的关系