性能分析利器火焰图(3)性能优化实战

1,389 阅读2分钟
  • java后台的火焰图生成非常简单,有现成的工具用arthas即可
  • node端的火焰图性能优化实战,实战的实例是网上的前辈有分享过来,借鉴过来使用,比较适合演示实战demo

1. 测试代码

const crypto = require('crypto') 
const Paloma = require('paloma')
const app = new Paloma() const users = {}
app.route({
    method: 'GET',
    path: '/newUser',
    controller(ctx) {
        const username = ctx.query.username || 'test'
        const password = ctx.query.password || 'test'
        const salt = crypto.randomBytes(128).toString('base64') 
        const hash = crypto.pbkdf2Sync(password, salt, 10000, 64, 'sha512').toString('hex') 
        users[username] = {
            salt,
            hash
        }
        ctx.status = 204
    }
}) 

app.route({
    method: 'GET',
    path: '/auth',
    controller(ctx) {
        const username = ctx.query.username || 'test'
        const password = ctx.query.password || 'test'
        if (!users[username]) {
            ctx.
            throw (400)
        }
        const hash = crypto.pbkdf2Sync(password, users[username].salt, 10000, 64, 'sha512').toString('hex') 
        if (users[username].hash === hash) {
            ctx.status = 204
        } else {
            ctx.
            throw (403)
        }
    }
}) app.listen(3000)

2. 通过 perf 参数运行 node.js 程序

$ node --perf_basic_prof app.js &
[1] 3590
$ tail /tmp/perf-3590.map
51b87a7b93e 18 Function:~emitListeningNT net.js:1375
51b87a7b93e 18 LazyCompile:~emitListeningNT net.js:1375
51b87a7bad6 39 Function:~emitAfterScript async_hooks.js:443
51b87a7bad6 39 LazyCompile:~emitAfterScript async_hooks.js:443
51b87a7bcbe 77 Function:~tickDone internal/process/next_tick.js:88
51b87a7bcbe 77 LazyCompile:~tickDone internal/process/next_tick.js:88
51b87a7bf36 12 Function:~clear internal/process/next_tick.js:42
51b87a7bf36 12 LazyCompile:~clear internal/process/next_tick.js:42
51b87a7c126 b8 Function:~emitPendingUnhandledRejections internal/process/promises.js:86
51b87a7c126 b8 LazyCompile:~emitPendingUnhandledRejections internal/process/promises.js:86

注意:使用 —perf_basic_prof_only_functions 参数可以将代码执行时的符号表转换为人能看懂的函数名

3. ab 压测

$ curl "<http://localhost:3000/newUser?username=admin&password=123456>"
$ ab -k -c 10 -n 2000 "<http://localhost:3000/auth?username=admin&password=123456>"

4. 抓取数据绘制火焰图

$ sudo perf record -F 99 -p 28671 -g -- sleep 30
$ sudo chown root /tmp/perf-28671.map
$ sudo perf script > perf.stacks
	$ ./stackcollapse-perf.pl --kernel < perf.stacks | ./flamegraph.pl --color=js --hash > flamegrap1.svg

perf record 会将记录的信息保存到当前执行目录的 perf.data 文件,使用 perf script 读取 perf.data 的 trace 信息写入 perf.stacks, 然后通过FlameGraph 绘制火焰图。

—color=js 指定生成针对 js 配色的 svg,即:

green:JavaScript。 blue:Builtin。 yellow:C++。 red:System(native user-level, and kernel)。

ab 压测用了 30s 左右,浏览器打开 flamegraph.svg,截取关键的部分如下图所示:

从上图可以看出:最上面的绿色小块(即 JavaScript 代码)指向 test/app.js 第 18 行,即 GET /auth 这个路由。再往上看,黄色的小块(即 C++ 代码) node::crypto::PBKDF2 占用了大量的 CPU 时间。

解决方法:将同步改为异步,即将 crypto.pbkdf2Sync 改为 crypto.pbkdf2,修改如下:

const crypto = require('crypto') 
const Paloma = require('paloma')
const app = new Paloma() const users = {}
app.route({
    method: 'GET',
    path: '/newUser',
    controller(ctx) {
        const username = ctx.query.username || 'test'
        const password = ctx.query.password || 'test'
        const salt = crypto.randomBytes(128).toString('base64') 
        const hash = crypto.pbkdf2Sync(password, salt, 10000, 64, 'sha512').toString('hex') 
        users[username] = {
            salt,
            hash
        }
        ctx.status = 204
    }
}) 

app.route({
	method: 'GET',
	path: '/auth',
	async controller(ctx) {
		const username = ctx.query.username || 'test'const password = ctx.query.password || 'test'
		if (!users[username]) {
			ctx.
			throw (400)
		}
		const hash = await new Promise((resolve, reject) = >{
			crypto.pbkdf2(password, users[username].salt, 10000, 64, 'sha512', (err, derivedKey) = >{
				if (err) {
					return reject(err)
				}
				resolve(derivedKey.toString('hex'))
			})
		}) if (users[username].hash === hash) {
			ctx.status = 204
		} else {
			ctx.
			throw (403)
		}
	}
})
app.listen(3000)