使用 Node.js Express 的最佳实践

简介: Production best practices: performance and reliability本文讨论部署到生产的 Express 应用程序的性能和可靠性最佳实践。这个话题显然属于“devops”世界,涵盖传统的开发和运营。 因此,信息分为两部分

在您的代码中要做的事情(开发部分)

使用 gzip 压缩

不要使用同步函数

正确记录

正确处理异常

在您的环境/设置中要做的事情(操作部分)

将 NODE_ENV 设置为“生产”

确保您的应用自动重启

在集群中运行您的应用程序

缓存请求结果

使用负载均衡器

使用反向代理

Use gzip compression

Gzip 压缩可以大大减小响应主体的大小,从而提高 Web 应用程序的速度。 在您的 Express 应用程序中使用 compression 进行 gzip 压缩。 例如:

var compression = require('compression')
var express = require('express')
var app = express()
app.use(compression())

对于生产中的高流量网站,实施压缩的最佳方法是在反向代理级别实施它。 在这种情况下,您不需要使用 compression 中间件。 有关在 Nginx 中启用 gzip 压缩的详细信息,请参阅 Nginx 文档中的模块 ngx_http_gzip_module。


Don’t use synchronous functions

同步函数和方法会在它们返回之前阻塞正在执行的进程。 对同步函数的单个调用可能会在几微秒或几毫秒内返回,但是在高流量网站中,这些调用会累加并降低应用程序的性能。 避免在生产中使用它们。


尽管 Node 和许多模块提供了它们功能的同步和异步版本,但在生产中始终使用异步版本。 唯一可以证明同步功能合理的时间是在初始启动时。


如果您使用的是 Node.js 4.0+ 或 io.js 2.1.0+,您可以使用 --trace-sync-io 命令行标志在您的应用程序使用同步 API 时打印警告和堆栈跟踪。 当然,您不想在生产中使用它,而是要确保您的代码已准备好用于生产。


Do logging correctly

通常,从您的应用程序进行日志记录有两个原因:用于调试和记录应用程序活动(本质上是其他所有内容)。 使用 console.log() 或 console.error() 将日志消息打印到终端是开发中的常见做法。 但是当目标是终端或文件时,这些函数是同步的,因此它们不适合生产,除非您将输出通过管道传输到另一个程序。


如果您出于调试目的进行日志记录,那么不要使用 console.log(),而是使用像 debug 这样的特殊调试模块。 此模块使您能够使用 DEBUG 环境变量来控制将哪些调试消息发送到 console.error()(如果有)。 为了保持你的应用完全异步,你仍然希望通过管道将 console.error() 传递给另一个程序。


如果您要记录应用活动(例如,跟踪流量或 API 调用),请不要使用 console.log(),而是使用像 Winston 或 Bunyan 这样的日志库。 有关这两个库的详细比较,请参阅 StrongLoop 博客文章 Comparing Winston and Bunyan Node.js Logging.


Handle exceptions properly

Node 应用程序在遇到未捕获的异常时崩溃。 不处理异常并采取适当的措施将使您的 Express 应用程序崩溃并下线。 如果您遵循下面确保您的应用程序自动重新启动中的建议,那么您的应用程序将从崩溃中恢复。 幸运的是,Express 应用程序的启动时间通常很短。 尽管如此,您首先要避免崩溃,为此,您需要正确处理异常。


为确保您处理所有异常,请使用以下技术:


try-catch

promises

在深入研究这些主题之前,您应该对 Node/Express 错误处理有一个基本的了解:使用错误优先回调,以及在中间件中传播错误。 Node 使用“错误优先回调”约定从异步函数返回错误,其中回调函数的第一个参数是错误对象,后跟参数中的结果数据。 要指示没有错误,请将 null 作为第一个参数传递。 回调函数必须相应地遵循错误优先回调约定才能有意义地处理错误。 而在 Express 中,最佳实践是使用 next() 函数通过中间件链传播错误。


What not to do

您不应该做的一件事是侦听 uncaughtException 事件,当异常冒泡一直返回到事件循环时会发出该事件。 为 uncaughtException 添加事件监听器将改变遇到异常的进程的默认行为; 尽管有异常,该进程仍将继续运行。 这听起来像是防止您的应用程序崩溃的好方法,但在未捕获的异常之后继续运行应用程序是一种危险的做法,不建议这样做,因为进程的状态变得不可靠且不可预测。


此外,使用 uncaughtException 被官方认为是粗暴的。所以监听 uncaughtException 只是一个坏主意。 这就是为什么我们推荐多个进程和主管之类的东西:崩溃和重新启动通常是从错误中恢复的最可靠方法。


我们也不建议使用 domains.它通常不能解决问题,并且是不推荐使用的模块。


Use try-catch

Try-catch 是一种 JavaScript 语言结构,可用于捕获同步代码中的异常。 例如,使用 try-catch 来处理 JSON 解析错误,如下所示。


使用诸如 JSHint 或 JSLint 之类的工具来帮助您查找隐式异常,例如未定义变量上的引用错误。


以下是使用 try-catch 处理潜在进程崩溃异常的示例。 这个中间件函数接受一个名为“params”的查询字段参数,它是一个 JSON 对象。

image.png

wrap() 函数是一个包装器,它捕获被拒绝的承诺并调用 next() 并将错误作为第一个参数。


更多细节可以参考这篇博客:Asynchronous Error Handling in Express with Promises, Generators and ES7.


Set NODE_ENV to “production”

NODE_ENV 环境变量指定应用程序运行的环境(通常是开发环境或生产环境)。 为了提高性能,您可以做的最简单的事情之一是将 NODE_ENV 设置为“production”。


将 NODE_ENV 设置为“production”使得 Express:


缓存视图模板。

缓存从 CSS 扩展生成的 CSS 文件。

生成不太详细的错误消息。

如果您需要编写特定于环境的代码,您可以使用 process.env.NODE_ENV 检查 NODE_ENV 的值。 请注意,检查任何环境变量的值都会导致性能下降,因此应谨慎进行。


在开发中,您通常在交互式 shell 中设置环境变量,例如使用 export 或 .bash_profile 文件。 但一般来说,你不应该在生产服务器上这样做; 相反,请使用您操作系统的初始化系统(systemd 或 Upstart)。 下一节提供了有关使用 init 系统的更多详细信息,但设置 NODE_ENV 对性能非常重要(并且易于操作),因此在此处突出显示。


使用 Upstart,在您的作业文件中使用 env 关键字。 例如:

image.png

Ensure your app automatically restarts

在生产中,您永远不希望您的应用程序处于离线状态。 这意味着您需要确保它在应用程序崩溃和服务器本身崩溃时重新启动。 尽管您希望这两种情况都不会发生,但实际上您必须通过以下方式对这两种情况进行说明:


使用进程管理器在崩溃时重新启动应用程序(和节点)。


使用操作系统提供的 init 系统在操作系统崩溃时重新启动进程管理器。 也可以在没有进程管理器的情况下使用 init 系统。


如果遇到未捕获的异常,节点应用程序就会崩溃。 您需要做的最重要的事情是确保您的应用程序经过良好测试并处理所有异常。


但作为一种故障安全措施,应采用一种机制来确保当您的应用程序崩溃时,它会自动重新启动。


Use a process manager

在开发中,您只需使用 node server.js 或类似的东西从命令行启动您的应用程序。 但是在生产中这样做会导致灾难。 如果应用程序崩溃,它将处于离线状态,直到您重新启动它。 为确保您的应用程序在崩溃时重新启动,请使用进程管理器。 流程管理器是应用程序的“容器”,可促进部署、提供高可用性并使您能够在运行时管理应用程序。


除了在应用程序崩溃时重新启动应用程序之外,进程管理器还可以让您:


深入了解运行时性能和资源消耗。


动态修改设置以提高性能。


控制集群(StrongLoop PM 和 pm2)。


下面是三个比较流行的进程管理器:


StrongLoop Process Manager

PM2

Forever

有关三个流程管理器的逐个功能比较,请参阅 http://strong-pm.io/compare/


使用这些进程管理器中的任何一个都足以让您的应用程序保持正常运行,即使它不时崩溃。


Use an init system

下一层可靠性是确保您的应用程序在服务器重新启动时重新启动。 由于各种原因,系统仍可能出现故障。 为确保您的应用程序在服务器崩溃时重新启动,请使用操作系统内置的 init 系统。 目前使用的两个主要初始化系统是 systemd 和 Upstart。


有两种方法可以在 Express 应用程序中使用 init 系统:


在进程管理器中运行您的应用程序,并使用 init 系统将进程管理器安装为服务。 当应用程序崩溃时,进程管理器将重新启动您的应用程序,当操作系统重新启动时,init 系统将重新启动进程管理器。 这是推荐的方法。


直接使用 init 系统运行您的应用程序(和 Node)。 这有点简单,但您无法获得使用进程管理器的额外优势。


Systemd

Systemd 是一个 Linux 系统和服务管理器。 大多数主要的 Linux 发行版都采用 systemd 作为它们的默认初始化系统。


systemd 服务配置文件称为单元文件,文件名以 .service 结尾。 这是一个用于直接管理 Node 应用程序的示例单元文件。 为您的系统和应用替换尖括号中的值:

[Unit]
Description=<Awesome Express App>
[Service]
Type=simple
ExecStart=/usr/local/bin/node </projects/myapp/index.js>
WorkingDirectory=</projects/myapp>
User=nobody
Group=nogroup
# Environment variables:
Environment=NODE_ENV=production
# Allow many incoming connections
LimitNOFILE=infinity
# Allow core dumps for debugging
LimitCORE=infinity
StandardInput=null
StandardOutput=syslog
StandardError=syslog
Restart=always
[Install]
WantedBy=multi-user.target

Run your app in a cluster

在多核系统中,您可以通过启动一组进程来将 Node 应用程序的性能提高许多倍。 一个集群运行应用程序的多个实例,理想情况下每个 CPU 内核上有一个实例,从而在实例之间分配负载和任务。


[图片]


重要提示:由于应用程序实例作为单独的进程运行,因此它们不共享相同的内存空间。 也就是说,对象对于应用程序的每个实例都是本地的。 因此,您无法在应用程序代码中维护状态。 但是,您可以使用像 Redis 这样的内存中数据存储来存储与会话相关的数据和状态。 这个警告基本上适用于所有形式的水平扩展,无论是多进程集群还是多物理服务器。


在集群应用程序中,工作进程可以单独崩溃而不影响其余进程。 除了性能优势之外,故障隔离是运行应用进程集群的另一个原因。 每当工作进程崩溃时,请始终确保记录该事件并使用 cluster.fork () 生成一个新进程。


Using PM2

如果使用 PM2 部署应用程序,则无需修改应用程序代码即可利用集群。 您应该首先确保您的应用程序是无状态的,这意味着没有本地数据存储在进程中(例如会话、websocket 连接等)。


当使用 PM2 运行应用程序时,您可以启用集群模式以在具有您选择的多个实例的集群中运行它,例如匹配机器上可用 CPU 的数量。 您可以使用 pm2 命令行工具手动更改集群中的进程数,而无需停止应用程序。


要启用集群模式,请像这样启动您的应用程序:

image.png

Cache request results

在生产中提高性能的另一个策略是缓存请求的结果,这样您的应用程序就不会重复操作来重复处理相同的请求。


使用 Varnish 或 Nginx 等缓存服务器(另请参阅 Nginx 缓存)可以大大提高应用程序的速度和性能。


Use a load balancer

无论应用程序如何优化,单个实例只能处理有限的负载和流量。 扩展应用程序的一种方法是运行它的多个实例并通过负载均衡器分配流量。 设置负载均衡器可以提高应用程序的性能和速度,并使其能够比单个实例扩展更多。


负载均衡器通常是一个反向代理,用于协调进出多个应用程序实例和服务器的流量。 您可以使用 Nginx 或 HAProxy 轻松地为您的应用程序设置负载均衡器。


通过负载平衡,您可能必须确保与特定会话 ID 关联的请求连接到发起它们的进程。 这称为亲缘会话或粘性会话,可以通过上面的建议解决,使用诸如 Redis 之类的数据存储来存储会话数据(取决于您的应用程序)。


Use a reverse proxy

反向代理位于 Web 应用程序的前面,除了将请求定向到应用程序之外,还对请求执行支持操作。 它可以处理错误页面、压缩、缓存、提供文件和负载平衡等。


将不需要应用程序状态知识的任务移交给反向代理可以释放 Express 来执行专门的应用程序任务。 出于这个原因,建议在生产中使用反向代理(如 Nginx 或 HAProxy)运行 Express。

相关实践学习
部署高可用架构
本场景主要介绍如何使用云服务器ECS、负载均衡SLB、云数据库RDS和数据传输服务产品来部署多可用区高可用架构。
负载均衡入门与产品使用指南
负载均衡(Server Load Balancer)是对多台云服务器进行流量分发的负载均衡服务,可以通过流量分发扩展应用系统对外的服务能力,通过消除单点故障提升应用系统的可用性。 本课程主要介绍负载均衡的相关技术以及阿里云负载均衡产品的使用方法。
相关文章
|
16天前
|
开发框架 JavaScript 安全
js开发:请解释什么是Express框架,以及它在项目中的作用。
【4月更文挑战第24天】Express是Node.js的Web开发框架,简化路由管理,支持HTTP请求处理。它包含中间件系统用于日志、错误处理和静态文件服务,集成多种模板引擎如EJS、Jade、Pug。框架还提供安全中间件提升应用安全,并具有良好的可扩展性,便于项目功能扩展和开发效率提升。
25 3
|
18天前
|
JSON JavaScript 前端开发
❤Nodejs 第九章(token身份认证和express-jwt的安装认识)
【4月更文挑战第9天】Node.js第九章介绍了Token身份认证,特别是JWT(JSON Web Token)作为跨域认证的解决方案。JWT由Header、Payload和Signature三部分组成,用于在客户端和服务器间安全传输用户信息。前端收到JWT后存储在localStorage或sessionStorage中,并在请求头中发送。Express-JWT是一个中间件,用于解析JWT。基本用法包括设置secret和algorithms。注意安全问题,避免混合使用不同算法以防止降级攻击。
38 0
|
2月前
|
开发框架 JavaScript 安全
js开发:请解释什么是Express框架,以及它在项目中的作用。
Express是Node.js的Web开发框架,简化路由管理,支持HTTP请求处理。它采用中间件系统增强功能,如日志和错误处理,集成多种模板引擎(EJS、Jade、Pug)用于HTML渲染,并提供安全中间件提升应用安全性。其可扩展性允许选用合适插件扩展功能,加速开发进程。
|
1月前
|
开发框架 JavaScript 中间件
node+express搭建服务器环境
node+express搭建服务器环境
node+express搭建服务器环境
|
2天前
|
JavaScript 中间件 API
中间件应用Express.js(Node.js)
我们定义了一个名为 `logger` 的中间件函数。它接受请求对象、响应对象以及下一个中间件函数作为参数。当接收到请求时,它会打印出请求的 HTTP 方法和 URL,然后调用 `next()` 函数来将控制权传递给下一个中间件或路由处理器。我们使用 `app.use()` 方法将 `logger` 中间件添加到了应用级别的中间件堆栈中,这意味着它将对所有请求生效。
10 3
|
10天前
|
缓存 监控 JavaScript
Node.js中构建RESTful API的最佳实践
【4月更文挑战第30天】本文介绍了在Node.js中构建RESTful API的最佳实践:选择合适的框架(如Express、Koa)、设计清晰的API接口(遵循HTTP动词和资源路径)、实现认证授权(JWT、OAuth 2.0)、错误处理、限流缓存、编写文档和测试,以及监控性能优化。这些实践有助于创建健壮、可维护和易用的API。
|
10天前
|
开发框架 JavaScript 中间件
深入探索Node.js的Express框架:使用与中间件详解
【4月更文挑战第30天】本文深入探讨了Node.js的Express框架,介绍了其作为Web开发的强大工具,主要聚焦于基本使用和中间件。Express是基于Node.js的Web应用框架,用于构建高效的应用和API。文章详细讲解了如何安装Express,创建简单应用,以及中间件的工作原理和应用,包括中间件的顺序、错误处理和挂载位置。此外,还提到了使用第三方中间件扩展功能。理解Express基础和中间件对于开发高质量Web应用至关重要。
|
25天前
|
JavaScript 中间件 API
node.js之express的基础知识
node.js之express的基础知识
|
28天前
|
开发框架 JavaScript 前端开发
【Node系列】Express 框架
Express.js 是一个基于 Node.js 平台的极简、灵活的 web 应用开发框架,提供一系列强大的特性来帮助你创建各种 web 和移动设备应用。
36 2
|
1月前
|
JavaScript Windows
Win7内网安装高版本的Node方法,亲测有效node-v16.16.0
Win7内网安装高版本的Node方法,亲测有效node-v16.16.0
58 0