Node.js 应用开发实战 - 高级前端开发工程师 - 拉勾教育

在该模块的几讲中,我们都是先有问题,然后再定位分析解决问题,那么是否有方法能够在出现问题之前检测问题呢?那就需要用到本讲要介绍的 clinicjs 工具,在上线之前通过自动化的方式来发现问题。

clinicjs 是什么

介绍

clinicjs 是一个 Node.js 的第三方工具,clinicjs 官网介绍了其核心的目的是,帮助诊断和查明 Node.js 性能问题的工具。那么它具体能帮助我们定位查询哪些性能问题呢?

  • CPU 异常问题,当 CPU 存在密集计算占用时可以检测出来。
  • 事件循环延迟问题,当主线程存在过载时,事件循环的执行时间存在延迟,可以检测出来。
  • 内存泄漏问题
  • 句柄泄漏问题

以上几个问题也是我们非常关注的几个点。

我们先使用 npm 来安装该工具,如下所示:

  1. npm install -g clinic
  2. clinic --help

安装完成后,我们需要把《12 | 性能分析:性能影响的关键路径以及优化策略》中的 wrk 也安装了,因为其需要 wrk 来进行压测,都安装完成后,需要着重掌握以下三个命令。

你可以先把我们的服务器启动起来,然后尝试在我们项目根目录去运行下面的命令 1 。

  1. clinic doctor --on-port "wrk http://127.0.0.1:3000/local-cache/no" -- node app.js

这个命令 1 是指对服务的 local-cache/no 进行性能全局分析

我们再来看下命令 2,如下所示:

  1. clinic flame --on-port "wrk http://127.0.0.1:3000/local-cache/no" -- node app.js

命令 2 是当全局分析出现事件延迟或者高 CPU 占用时,就需要使用该工具进行具体深入分析,关于如何应用,你可以参考clinic flame 官网说明。

以下是命令 3:

  1. clinic bubbleprof --on-port "wrk http://127.0.0.1:3000/local-cache/no" -- node app.js

命令 3 是当全局分析出现 I/O 问题时,我们就需要使用该工具进一步分析,关于如何应用,你可以参考 clinic bubbleprof 官网说明。

示例分析

我们来看几个例子,以下例子都是来自 clinicjs 测试的结果。

我们先来看图 1 所示的结果。

14 | 工具应用:使用 clinicjs 工具实现通用性安全检查 - 图1

图 1 clinicjs 无异常检测结果

从图 1 可以看到一个总结性的话,正如第一行 Detected no issue 表明,本次测试没有任何问题,具体可以再看下四个结果:CPU 占用、内存占用、事件延迟和句柄

我们再来看一种异常的情况,如图 2 所示的结果。

14 | 工具应用:使用 clinicjs 工具实现通用性安全检查 - 图2

图 2 clinicjs 异常检测结果

上图的第一行很清晰地告诉我们,存在 CPU 问题、内存问题和事件延迟的情况,看到以上问题后,我们接下来怎么处理呢?

在 clinicjs 中如果分析出存在 CPU 和事件延迟,可以使用上面我们所介绍的 clinic flame 工具。而 I/O 问题则使用 clinic bubbleprof 来进一步定位。

框架中接入自动化

以上是一个工具的应用,并不是我们本讲的重点,我们本讲的核心是将该工具作为一个自动化模块接入我们的框架中

框架中测试的思考

clinicjs 接入后,使用方法非常简单,我们只需要使用以下命令就可以进行测试检测。

  1. clinic doctor --on-port "wrk http://127.0.0.1:3000/local-cache/no" -- node app.js

但是需要思考一下,如果每次新接口上线我们都需要一个个跑一遍,那么岂不是很花费人力,这时就需要思考下有没有办法可以自动化地做接口测试,而我们只需要深入分析有问题的接口就可以了。

有了以上思考后,我们就开始来规划下,如何进行自动化测试。

自动化

首先来整理一个流程图,来分析下我们应该如何实现这个功能。

14 | 工具应用:使用 clinicjs 工具实现通用性安全检查 - 图3

图 3 自动化方案流程图

我们分析一下以上流程的每个过程:

  1. 我们将需要测试的接口组装成一个配置信息,其中包含了待测试的请求路径、请求方式以及请求的参数;
  2. 因为 clinicjs 生成的是一个 html 文件,并且这个 html 文件是一个 JavaScript 执行生成的结果文件,因此需要启动一个本地服务来读取 html 并解析生成具体的 DOM 结构;
  3. 从数组取出一个待测试的接口数据,来开始测试,根据 clinicjs 的命令,生成具体的执行命令行,然后借助 child process 的 exec 来执行,并获取其执行结果;
  4. 最终会生成一个运行期间的结果,在输出的最后一行会提供一个生成后的 html 文件路径地址,一般这个文件都会生成在当前目录下的 .clinic 文件夹中;
  5. 使用一个 html 的解析工具 puppeteer 从本地服务中读取 html 文件,并解析获取其中的测试结果的 DOM 信息;
  6. 判断 DOM 信息中是否有异常结果,有异常显示异常并记录测试数据,没有则跳过;
  7. 最终测试完成以后,显示所有测试异常的接口,并把测试信息给到开发者。

根据以上的流程,我们画一个逻辑执行过程来更清晰描述下这个过程。

14 | 工具应用:使用 clinicjs 工具实现通用性安全检查 - 图4

图 4 自动化方案逻辑执行图

上面的逻辑和流程图基本上是一个过程,只是这里用函数和模块来表示了,具体我们可以看下部分代码的实现,如图 5 所示,源码在GitHub 中的 bin/clinic_test.js 文件中。

14 | 工具应用:使用 clinicjs 工具实现通用性安全检查 - 图5

图 5 startTestLink 代码实现

在图 5 中的第 57-63 行逻辑中,主要是在组装 clinicjs 的测试命令,最终的命令会变成以下的命令行。

  1. cd ..
  2. clinic doctor --on-port "wrk http://127.0.0.1:3000/xxxx" -- node app.js
  3. exit

以上是 GET 方法,如果是 POST 方法则需要写入 POST 数据,并且需要在命令行中加上 POST 参数,如下命令示例结果:

  1. cd ..
  2. clinic doctor --on-port "wrk http://127.0.0.1:3000/xxxx -s ./bin/post_tmp.lua" -- node app.js
  3. exit

拿到命令以后,再使用 Node.js 的 execSync 方法获取执行结果,请注意这里的执行结果是非常长的日志,但是在最后一行会提示 html 生成的文件地址,在图 5 中的 64 到 67 行就是获取到相应的 html 文件,拿到 html 文件后再向本地服务读取到 html 的内容,最后 parseResult 来分析是否存在性能问题。

接下来我们看下 parseResult 的方法实现,代码如图 6 所示。

14 | 工具应用:使用 clinicjs 工具实现通用性安全检查 - 图6

图 6 parseResult 代码实现

这里有个小技巧,就是在 Node.js 中可以模拟浏览器去读取一个 html 文件,如果 html 文件不是通过 Javascript 动态生成的,那么你可以直接 fs.readFile 去获取,如果是动态生成的则必须用 puppeteer 模拟浏览器解析 html DOM 结构了

拿到 DOM 结构后,就可以进行分析判断是否存在异常了,后面的逻辑就比较简单,都是一些判断方法了。通过以上方式,我们就可以在配置文件中增加一些接口,从而实现自动化的方式,每次我们在线上发布之前,在测试环境或者预发布环境中跑一遍测试就可以提前发现问题了。接下来我们来演示一下这个功能。

实践测试

之前我们的项目中有几个存在 CPU 密集计算的问题,这里我们用这个工具来检测下,看下是否能帮我们定位到问题。我们把配置文件修改为如下内容:

  1. const testMapping = [
  2. {
  3. 'urlPath' : 'cache/local',
  4. 'method' : 'get',
  5. 'getParams' : {
  6. },
  7. 'postParams' : {
  8. }
  9. },
  10. {
  11. 'urlPath' : 'local-cache/no',
  12. 'method' : 'get',
  13. 'getParams' : {
  14. },
  15. 'postParams' : {
  16. }
  17. },
  18. {
  19. 'urlPath' : 'local-cache/yes',
  20. 'method' : 'get',
  21. 'getParams' : {
  22. },
  23. 'postParams' : {
  24. }
  25. }
  26. ];

接下来我们在项目根目录执行下面两个命令:

  1. cd bin
  2. node clinic_test.js

运行完成后,你将会看到如下结果:

  1. 启动服务开始测试...
  2. 开始检测 cache/local 的接口性能问题
  3. 该接口无任何异常问题
  4. 开始检测 local-cache/no 的接口性能问题
  5. 该接口存在异常
  6. 具体详情请查看项目根目录下的
  7. ./.clinic/21097.clinic-doctor.html
  8. 开始检测 local-cache/yes 的接口性能问题
  9. 该接口无任何异常问题
  10. 你需要处理以下问题汇总,具体请查看下面详细信息
  11. [{"resultLink":"21097.clinic-doctor.html","url":"http://127.0.0.1:3000/local-cache/no","command":"clinic doctor --on-port \"wrk http://127.0.0.1:3000/local-cache/no\" -- node app.js","problem":"Detected data analysis issue"}]

从原来的分析来看也是这样的结果,因为我们对 cache/local 和 local-cache/yes 做了缓存优化,所以无任何异常问题,而 local-cache/no 存在性能问题,然后我们打开 .clinic/21097.clinic-doctor.html 这个文件,可以看到如图 7 所示的结果。

14 | 工具应用:使用 clinicjs 工具实现通用性安全检查 - 图7

图 7 local-cache/no 检测结果

如果遇到了 CPU 占用和事件延迟这类问题就使用 clinic flame 来进一步分析,我们从刚才的运行结果中,取出测试方法,把 doctor 修改为 flame 即可,如下命令所示(请注意要回到项目根目录去运行,不要在当前 bin 目录)。

  1. clinic flame --on-port "wrk http://127.0.0.1:3000/local-cache/no" -- node app.js

成功运行后,打开相应的 html 文件,可以看到如图 8 所示的结果。

14 | 工具应用:使用 clinicjs 工具实现通用性安全检查 - 图8

图 8 clinic flame 运行结果

在图 8 中我们可以非常清晰地看到提示,具体在 localCache.js 中的第 20 行占用的问题,你都不用去详细分析就可以轻松得到结论。

为了使用方便,我们将此工具集成到了 package 中,只需要运行下面命令就可以了。

以上就完成了该工具的自动化方法。

总结

你可以想象一下如果没有该工具,我们每次开发完的功能都像是一次冒险,心里很忐忑,但是有了 clinicjs 工具以后,我们再也不需要担心这个事情了,在发布之前就可以预先发现这些问题,我希望如果应用 Node.js 做后台开发时,它都应该作为一个必备的工具。

我们本讲实现的自动化工具,还可以继续抽离细化,希望你可以根据自己的想法生成一个比较通用的工具,如果有人抽离出来后,请给出 GitHub 地址,让大家可以一起来使用,一起来维护。

下一讲我们将进入实战模块,基本把当前 Node.js 的相关知识都介绍完了,我们也开始进行一些项目尝试。