有许多第三方工具可用来分析Node.js应用程序,比如火焰图。但在许多情况下,最简单的选择是使用内置的分析器使用V8内部的分析器,在程序执行期间定期对堆栈进行采样。它将这些示例的结果以及重要的优化事件记录为一系列滴答,本文假设读者已经对nodejs异步回调的特性有一点了解,不了解也不所谓,只是了解的话,文章阅读会更清晰。
下面介绍一下,使用nodejs内置的V8分析器,分析并优化一个web接口:
首先准备好web服务,我们使用express框架搭一个简单的接口服务,下面看代码:
const express = require("express");
const crypto = require("crypto");
const app = express();
app.get("/register",async(req, res)=>{
// 获取传入的用户名和密码
let username = req.query.username;
let password = req.query.password;
// 生成加强密码强度的盐值
let slat = crypto.randomBytes(16).toString("base64")
const hash = crypto.pbkdf2Sync(password,slat,10000,512,"sha512")
// 将结果数据返回
res.send(JSON.stringify({username,hash:hash.toString("base64")}));
});
app.listen(8080);
上面我们准备好了一个web服务,下面我们把这个服务跑起来,并用测试工具对它进行测试:
启动:node --prof app.js
// 使用--prof参数,表示使用内部的剖析器,使用这个参数启动应用后,当有请求访问时,会在应用应用程序当前目录下生成一个.log文件,这个文件里面记录了nodejs的堆栈信息
测试并发:ab -k -c 20 -n 250 "http://localhost:8080/register?username=matt&password=password"
// 使用上面的命令去请求我们启动好的服务,ab是linux下的一个并发测试工具,-c 表示并发数,-n 表示总共要完成多少个请求,ab的详细用法,这里不再解释,大家自行了解。
下面是我运行ab命令后的结果:
下面是我本机生成的log文件的结果:
这个log文件是不可读的,需要用nodejs转换为我们可分析的文件,使用下面的命令:
node --prof-process isolate-0xnnnnnnnnnnnn-v8.log > processed.txt
转换后的文件内容有点多,我只粘贴出对我们分析有用的部分:
上图给出的摘要部分:
可以看到99%的耗时都消耗在了C++对应的库,因为nodejs的加密模块底层是调用C++的库来实现的;javascript代码执行只占用了0.9%;剩余的GC(垃圾回收)占用0.1%,这也是一个需要关注的值;文中的例子比较简单,所以nodejs的垃圾回收在这个例子中占用的很少,当接口很复杂的时候,GC肯定会变大,当GC占用过多的时候,就要考虑对代码格式进行优化,比如声明过多的变量,导致GC明显增大;这是可以考虑使用变量复用,前面声明的变量生命周期已经结束来,就可以考虑在后面需要声明新变量的时候,复用前面的变量名,唯一的缺点就是,变量的复用,会使得代码可读性变差,这就需要详细的代码注视了。这样一定程度可以减小GC的占用,因为当GC运行的时候,程序是不能做其它任何事情的。
我们接着分析文件,既然C++代码占用这么多时间,我们来看它具体做了什么,转换后的log文件中有C++的详细部分:
上图可以看到 ,PBKDF2这个函数占用了97%,说明C++代码耗时的地方就在这里。找到了程序主要耗时的地方,那么我们接下来对这一块进行改进。
原来,我们用的pbkdf2Sync这个方法进行加密获得hash,这是一个同步的方法,nodejs的优点是异步回调,我们用异步来充分发挥nodejs的特性,pbkdf2Sync对应有一个异步的方法,看下面的代码:
app.get("/register",async(req, res)=>{
// 获取传入的用户名和密码
let username = req.query.username;
let password = req.query.password;
// 生成加强密码强度的盐值
let slat = crypto.randomBytes(16).toString("base64")
// 使用异步来加密获得hash值
crypto.pbkdf2(password,slat,10000,512,"sha512",(err,hash)=>{
// 将结果数据返回
res.send(JSON.stringify({username,hash:hash.toString("base64")}));
});
});
我们再运行程序,然后使用ab测试工具,重新测试,看一下结果:
这次的运行结果每个请求16毫秒,之前是48毫秒,性能提高了2倍多;再一次证明,nodejs的异步回调的优势。
我们再来看滴答文件:
这次,javascript代码占用为15.9%,GC占用1.3%,二者都有明显的上升,因为之前pbkdf2Sync同步方法,会阻塞后面的请求,改为异步后,在执行C++代码时,nodejs仍然可以接受后面的请求,待加密完成后,再响应前面的请求。从而提高了nodejs程序的性能。