2021年的Node.js最佳实践和性能分析

959 阅读10分钟

对于使用Node.js的团队来说,最大的好处之一是能够在客户端和服务器上使用JavaScript。但是,我们如何确保在Node.js中不编写未经优化的JavaScript代码?另外,你如何在开发过程中和部署到生产后测试你的应用程序的性能?

在这篇文章中,我们将看看编写和测试Node.js代码的一些最佳实践,然后通过一些例子将它们运用起来。

Node.js的最佳实践

首先,让我们从一些最佳实践开始,在涉及到CPU资源时,将其纳入你的代码。

子进程

尽管是单线程的,但Node.js可以产生子进程来处理任务。虽然少用有帮助,但过多的子进程会大大降低你的代码的性能。子进程会给你的CPU带来开销,如内存消耗,这将使用你机器的有限资源。因此,重要的是不要催生比你的机器中的核心更多的子进程。

下面是一个Node.js脚本使用cpuCount 变量催生子进程的例子,它决定是否需要催生更多的子进程。

var counter = 0;
fn: async (inputs, exits) {
  const os = require('os')  
  // Get number of CPU cores
  const cpuCount = os.cpus().length
  if (counter > cpuCount) throw new Error('Can\'t spawn any more child processes')
  var fork = require('child_process').fork;
  var child = fork('./test');
  console.log('Test Started....');
  counter++; // note this line
}

工作者线程

然而,不要将它们用于输入/输出密集型操作。Node.js处理异步输入/输出的效率比你使用工作者要高,这在Node.js文档中有所描述。

在下面的代码片段中,当我们在主应用线程上时,我们正在创建一个Worker 构造函数的实例。之后,我们将在else 块中执行CPU密集型任务,并用parentPort.postMessage('Hello world!') 将消息传回主线程。

const { Worker, isMainThread, parentPort } = require('worker_threads');
if (isMainThread) {
  // This code is executed in the main thread and not in the worker.
  // Create an instance of Worker
  const worker = new Worker(__filename);
  // Listen for messages from the worker and print them.
  worker.on('message', (msg) => { console.log(msg); });
} else {
  // This code is executed in the worker and not in the main thread.
  // Send a message to the main thread.
  // In here you perform CPU-intensive tasks
  parentPort.postMessage('Hello world!');
}

PM2

PM2是一个用于生产Node.js应用程序的进程管理器。它有助于在多个本地进程上扩展Web应用程序,提高应用程序的性能,也有助于监控。通过在你的终端运行以下命令来安装PM2。

npm install -g pm2

通过运行以下代码启动你的应用程序,其中app.js 是入口点。

pm2 start app.js

PM2允许你通过在集群中运行你的应用程序来利用多核系统的优势使你的应用程序可以很容易地向上或向下扩展。我们可以告诉PM2在两个实例上运行我们的应用程序。

pm2 start app.js -i 2

回顾一下,PM2是在后台运行的,这意味着当你不使用它时,你很容易忘记关闭它。添加下面的代码来查看哪些进程正在运行。

pm2 list

通过使用关闭正在运行的进程。

pm2 kill

内存限制

Node.js分配了足够的内存来保持当前范围内的所有对象。默认情况下,内存被限制在512MB。当内存消耗接近极限时,V8将在垃圾收集中花费更多时间,这反过来会影响你的应用程序的性能。

你可以用--max-old-space-size 开关增加内存限制。--max-old-space-size 是一个Node.js V8 CLI选项,允许你覆盖默认的内存大小。

要使用--max-old-space-size 选项,在启动Node.js时将其作为一个选项传递,并给它一个以兆字节为单位的大小。例如,下面的代码片段将增加内存大小到1536MB(1.5GB)。

node --max-old-space-size=1536 index.js

重复使用对象

Node.js应用程序中的大多数对象的生命周期都很短,因此,最好尽可能地重复使用对象。创建太多的新对象会使你的应用程序变慢,因为你必须经常运行Node.js垃圾收集器

要检查你的应用程序中的内存使用情况,你可以使用process.memoryUsage() 方法。

process.memoryUsage().heapTotal // Gets available memory heap toal in bytes
process.memoryUsage().heapUsed // Gets used memory heap in bytes

重要的是要避免在生产中运行垃圾收集器,因为它将不断把运行这两个函数的值记录到你的生产日志环境中。然而,这个过程适用于开发环境。你可以在Chrome开发工具(DevTools)的内存标签部分监控应用程序的内存使用情况。

负载测试

在构建你的Node.js应用程序后,最好是进行一次负载测试。负载测试可以确定你的应用程序一次可以有效处理的请求数量,以及应用程序在大请求下的表现。它也将帮助你决定如何扩展你的应用程序。对于负载测试,你可以使用Apache JMeter。

Apache JMeter

根据其网站,Apache JMeter是一个开源的、纯Java的应用程序,设计用于负载测试功能行为和性能测量。你可以下载JMeter并按照入门指南 使用它进行负载测试。

使用Apache JMeter,你可以记录你的Node.js应用程序上的任何行为,如文件上传和客户端会话。你也可以批量请求来测试你的应用程序,然后观察和监测性能瓶颈。

ApacheJmeter Test Plan Display

Apache JMeter

现在,你知道了一些创建Node.js应用程序的最佳实践。然而,你仍然应该检查你的代码是否尽可能地具有性能。在下一节中,我们将介绍一些Node.js的性能分析方法。

对Node.js的性能进行基准测试

一个缓慢的Node.js应用程序通常是资源耗尽的结果。虽然还有其他资源,如网络和硬盘,但我们将专注于这两种。

  1. 处理器
  2. 内存

Node.js开箱即有几个工具,可以让你监测资源利用率。

Node.js检查器

当你用--inspect--inspect-brk 开关启动你的Node.js应用程序时,Node.js进程将监听一个调试客户端。它允许你使用你熟悉的调试客户端JavaScript的工具来调试你的Node.js代码,在我们的例子中就是Chrome Dev tools。

要使用检查器,用node ---inspect-brk app.js 命令运行你的Node.js脚本,这将。

  • 启用Node.js 检查器
  • 将检查器绑定到IP address: 127.0.0.1
  • 聆听port 9229
  • 在用户代码开始前中断

运行该命令后,前往谷歌浏览器。它运行与Node.js相同的JavaScript引擎,即V8引擎。在地址栏中输入about:inspect ,它会将你重定向到chrome://inspect

Nodejs Inspector Chrome Browser Display

要调试你的Node.js会话,点击inspect 链接,启动一个带有Chrome开发工具的弹出窗口。如果你已经知道如何使用Chrome开发者工具,你可以继续对你的应用程序进行剖析,并查看资源消耗。您可以阅读更多关于在Chrome中调试JavaScript的内容。

Chrome Developer Tools Debugger

Chrome调试器

执行线程

如前所述,重要的是要记住,Node.js是单线程的,这意味着你只有一个执行进程可以使用。想象一下,数以百万计的客户在使用这个单线程工作!这是一个非常有限的资源。这是一个非常有限的资源。

尽管这种单线程方法使Node.js保持简单,以便我们不必处理诸如线程安全之类的事情,但它就是不能扩展。

Node使用异步输入/输出来安排读写操作,这不会阻塞执行过程。然而,如果你的代码是在没有遵循最佳实践的情况下编写的,它就能够使用100%的CPU,并会阻塞Node.js的执行过程。

让我们来看看一个能够阻塞执行线程的代码的例子。

function blockProcess() {
        while(true) {
                console.log("Blocking process")
        }
}

虽然这只是一个表面的例子,但如果你运行blockProcess 函数并监控你的CPU,你会看到它是如何将CPU消耗提高到几乎100%的。因此,避免编写像这样阻塞进程的代码是明智的。

测量CPU负载

你需要测量CPU负载,以分析你是否有高CPU负载的问题。检查的一种方法是在运行Node.js进程会话的情况下使用Chrome开发者工具。

进入Chrome开发者工具的剖析工具部分,它可以提供一个CPU配置文件,告诉你某个函数、子进程或资源需要多少时间。接下来,寻找一个CPU消耗时间较高的文件。点击它,找出是哪个函数/语句导致了高CPU负载,这样你就可以努力解决这个问题了。

Measure CPU Load Profiling Tool Section

Chrome浏览器中的CPU分析

执行时间

执行时间是指我们的应用程序的某些块从开始到结束所需的执行时间。要测量执行时间,请使用
console.timeconsole.timeEnd 方法将您要测量的代码块包裹起来。作为一个例子,下面的代码是在Sails.js网络应用程序中进行网络请求。

fn:  async function(inputs, exits) {
var wavesNGNPrice = await sails.helpers.fetch('https://waves-africa-api.herokuapp.com/ngn');
exits.success(wavesNGNPrice)
}

该函数的实现对于我们现在想要实现的目标来说并不那么重要,但假设我们想知道该函数的第一行需要多长时间来执行。我们会这样包装它。

fn:  async function(inputs, exits) {
console.time(‘waiting’)
var wavesNGNPrice = await sails.helpers.fetch('https://waves-africa-api.herokuapp.com/ngn');
console.timeEnd(‘waiting’)

exits.success(wavesNGNPrice)
}

当我们启动应用程序时,我们会在控制台看到一个输出,显示代码运行所需的时间,单位是毫秒。 我们可以用这个输出来看看什么东西可能阻挡了我们的代码,使应用程序变慢。

Execution Time Measure Sailsjs

性能钩子

另一个用于测量性能的Node.js工具是 [perf_hooks](https://www.npmjs.com/package/perf_hooks)模块,当你在代码中创建标记时,它允许你测量应用程序的不同方面的性能。

让我们通过使用它来测量我们上面所做的同样的代码块来看看它的运行情况。首先,我们需要从perf_hooks 模块中导入PerformanceObserverperformance

const { PerformanceObserver, performance } = require(‘perf_hooks’)

接下来,我们将创建一个新的观察者对象。

const obs = new PerformanceObserver(items => {
        console.log(items.getEntries()[0].name, items,getEntries()[0].duration);
performance.clearMarks();
});

然后,我们将在该对象上调用观察者方法,告诉它我们想要什么条目类型。

obs.observe({ entryTypes: [‘measure’] });

最后,我们将用performance.mark 方法来包装代码,并通过运行performance.measure 来输出测量结果。

fn:  async function(inputs, exits) {

performance.mark(‘start waiting’)
var wavesNGNPrice = await sails.helpers.fetch('https://waves-africa-api.herokuapp.com/ngn');
performance.mark(‘end waiting’)

performance.mark(‘waiting period’, ‘start waiting’, ‘end waiting’)

exits.success(wavesNGNPrice)
}

你应该得到一个类似于下图的输出。

Performance Hooks Observer Method Output

在上述PerformanceObserver 的实现中,我们将执行时间记录到控制台。Node.js关于性能测量API的文档将有助于深入了解这一功能。

perf_hooks 模块没有副作用,因此当你把它放在你的代码中时,它不会改变任何东西。当你怀疑某一行是造成你的Node.js应用程序缓慢状态的罪魁祸首时,它就会派上用场。

如果你遇到了任何性能问题,perf_hooks 观察者可以被包含在生产中,以测量现场项目的性能分析。然而,你不应该在生产中的每一种情况下都打开它,因为它可能会导致你的应用程序滞后。

总结

让我们回顾一下我们所涉及的测量你的代码性能的最佳实践。

  • 使用实时数据进行测试:为你的应用程序模拟一个真实世界的状态,而不是使用假数据
  • 定期进行负载测试:检查新功能是否会给你的系统性能带来开销。
  • 预先定义你的性能指标并对其进行观察:事先知道你要测试和观察什么

因为我们的Node.js应用程序的性能会直接影响到我们的用户,所以一个好的开发人员知道要确保应用程序的性能和优化!

The postNode.js best practices and performance analytics in 2021 appeared first onLogRocket Blog.