手记

提升Node.js微服务性能的NPM库有哪些?

性能是一个非常广泛的领域,涉及多种话题。我们可以讨论代码优化、数据库查询优化、横向扩展服务以及更多。但在本文中,我主要关注的是Node.js微服务层,并探讨了在这些层面提升性能时可以做出哪些架构决策。在编写我的Node.js CRUD API时,我想知道是否有比其他框架更高效的框架?有哪些架构模式可以帮助提升性能?

所以在本文中,我分析了所选择的 Web 框架、日志和数据访问层,提出一些建议,为开发人员在设计 Node.js CRUD 微服务时提供帮助。

选择一个高性能的Web框架

当你编写你的 Node.js 微服务应用时,第一步就是选择一个用来编写 API 的 web 框架。你的应用程序性能将极大地受到这个选择的影响——因为 web 框架是你代码运行的基础。如果这个基础很慢,会严重影响你的应用程序性能。

Node.js微服务中最流行的Web框架是Express。Express在NPM上的每周下载量约为2900万。但从性能上来说,Express是否是最好的选择?

Express vs Fastify

我比较了 Express 和 Fastify——一个在过去一年半里越来越受欢迎的新 web 框架,Fastify 已经在 Node.js 生态中崭露头角。Fastify 在 NPM 上的每周下载量已达到约 160 万次。Fastify 宣称其性能远超 Express。

基准测试是在两台服务器上进行的,一台用Express编写,另一台用Fastify编写。为了确保基准测试尽可能准确无误,两台服务器具备相同的特性:两台服务器实现了相同的API接口,向MongoDB插入一个具有相同属性的简单Person对象记录。

第一张图片显示了Express微服务基准测试的结果图。第二张图片显示了Fastify的测试结果。

在 Express 中,基准测试期间总共发送了 101,000 个请求,平均每秒约有 10,000 个请求。在 Fastify 中,基准测试期间总共发送了 193,000 个请求,平均每秒约有 17,500 个请求。

这里有一张图表,展示了通过观察每秒平均请求数来实现的性能提升。

根据测试结果,我们可以看到Fastify的表现具有显著的优势,因为它已经证明比Express服务器快两倍,而Express服务器则更为普及。

有两类情况需要你考虑这些问题:开始一个新的项目,或者重构你的微服务来使用不同的 web 框架。在这种情况下,性能考虑应优先考虑,因为 web 框架可能比你在代码和数据库优化上花费大量时间更显著地提升应用程序性能。总之,说到性能,Fastify 是最好的选择。

日志层性能优化
选择一个性能高效的日志库

我们的计划是编写一个总是稳定的应用程序。实际上,总是会出现错误、崩溃、数据丢失等各种奇怪的现象,我们需要日志来找出生产环境中的真正原因。

所以日志在我们的应用程序中是必不可少的,它们在实际应用中大大节省了调试时间——但日志会拖慢性能。想想看,确实很有道理——你添加了更多的命令,软件需要执行,而日志操作常常涉及IO。

这里有一个性能基准测试结果,使用了两个 Node.js 服务器,两台服务器都用了 Express 和 MongoDB,它们实现相同的 API。第一个有日志,第二个没有。测试用 NPM Autocannon 做的。

这里是没有日志的服务器。平均每秒请求约8600次,基准测试中的总请求量达到86,000次。

下面你可以看到使用winston NPM库的服务器基准测试结果。可以看到,每秒请求数降到了大约7800,总请求数则降到79,000。

整体性能下降了大约10%,如下图所示:

在选择日志库时,尽管有许多需要考虑的因素,日志库本身的性能也应是重要考量。建议选择能给你的代码带来最小负担的日志库。

为了了解哪种日志库在我的代码中开销最小且最有效,我使用不同的 NPM 日志库对同一服务器进行了基准测试:Winston、Pino、Log4js 和 Bunyan。以下是这些库的基准测试结果,同时也与无日志记录的服务器进行了比较。第一个图表展示了每秒平均请求数,第二个图表展示了总请求数。

总之——目前Pino.js是效率最高的库,并且对你的应用代码负担最小。所以Pino.js是推荐使用的库。

不要在应用层生成日志消息——让日志库来搞定消息

这里我想强调的一点是,日志消息应该通过日志库来构建,而不是在你的应用程序中构建。

在下面的图中,你可以看到2个消息构建过程的例子,而在第一个例子中,可以看到消息是在日志库的层面构建的,而是在应用程序之外。

为什么重要?

大多数日志库不会为多余的日志级别生成消息。例如,如果应用程序运行在错误日志级别上,那么调试日志级别的消息将不会被生成——如果它们是通过日志库中的 API 获取参数并组装消息生成的。然而,如果你在应用程序中拼接字符串的话,这些消息将被构建,不论日志级别如何,甚至是否会被实际打印出来。

虽然它看起来很小,但性能上来说——对于那些每天生成大量日志的应用程序而言——性能会逐渐累积。

数据访问层
优先使用原生驱动而不是抽象库,而非类似 ORM 的数据库抽象层工具。

我记得当我开始对Node.js感兴趣的时候,我做的第一个关于写服务器的教程里包含了Mongoose。而且我最初写的几个服务器也都用到了Mongoose。Mongoose是一个对象模型工具,旨在简化使用MongoDB的开发过程。那么,使用ORM这样的工具编写应用程序时,可能会遇到什么问题呢?

数据抽象工具在应用程序代码和数据库之间引入了一层抽象,这在对象导向代码和查询之间的转换中增加了转换的开销。在很多情况下,这些工具可能难以优化复杂的查询,或者无法高效支持某些特定的高级数据库功能需求。此外,数据库实体与应用程序对象之间的映射可能会增加开销,尤其是在处理大量数据集时。这个映射过程可能导致内存使用增加和处理时间变长,影响性能表现。

不过让我们来对比一下性能。我对比了两台服务器的性能,一台使用Mongoose,另一台使用MongoDB原生驱动。这两台服务器都使用相同的web框架——Express。每台服务器都执行了一个POST请求来插入一个简单的对象。第一张图显示的是使用Mongoose的服务器的结果,第二张图显示的是使用原生MongoDB驱动的服务器的结果。

我们可以看到,移除Mongoose提升了大约40%的性能。如下的图表所示:

总之——我们能看到,在非常简单的场景下,通过去掉 Mongoose,可以将服务性能提升大约 40%。我知道很多情况下,开发者更倾向于使用 ORM 工具,为了简化开发。随着规模的扩大,数据场景变得更加复杂时,这种开销对你的应用影响越来越大,以至于你可能需要重构代码直接操作数据库。

基础设施层上的数据库连接池管理

在与数据库打交道时,你通常会使用一个连接池。连接池是一组预先分配的固定数量的数据库连接。每次你需要执行数据库操作时,都会从连接池中取出一个可用连接,并用它来执行你的查询。

管理连接池在你的基础设施层很重要。你需要确保开发人员不能从不同层次随意打开连接。最好的方法是创建一个 API,在后台获取可用连接。此外,当微服务即将关闭时,基础设施层还需要关闭连接池。

如果数据访问层中的每个组件都可以打开和关闭数据库连接,这将导致多个连接因为开发人员不小心忘记关闭而一直保持打开状态。这将导致连接泄露问题,最终将导致资源耗尽、性能下降、内存泄漏等其他严重问题。

简单来说——在构建你的微服务并考虑其架构设计时,在基础设施层设计你的连接管理策略,不要在数据访问层开启新的连接,并确保在服务关闭时关闭所有未使用的连接。

简要

这篇文章中有几个要点你可以参考,在设计你的 Node.js 微服务时:

  1. 选择一个性能高效的 web 框架——目前 Fastify 在这方面表现突出。
  2. 选择一个高效且对代码开销较小的日志库——NPM Pino 在这方面表现突出。
  3. 让日志库自动构建日志消息。
  4. 在数据访问层——尽量避免使用类似 ORM 的工具,而使用原生数据库驱动。
  5. 数据库连接管理——使用连接池。打开和关闭连接应在基础设施层处理,以避免连接泄露(leaks)。
0人推荐
随时随地看视频
慕课网APP