有时候我也在想我是否真的知道不少东西。html
就在几周前,我正在和一个朋友谈话,他不经意间提到,“你永远都不会在生产中直接使用 Node 来运行程序”。我强烈点头,表示我也不会在生产中直接运行 Node,缘由可能每一个人都知道。可是我并不知道,我应该知道缘由吗?我还能继续写代码吗?前端
若是让我绘制一个维恩图来表示我所知道的和其余人都知道的东西,它看起来就像这样:node
顺便一提,我年纪越大,那个小点就会越小。android
Alicia Liu 建立了一个更好的图表,改变了个人生活。她说这种状况更像是...ios
我很是喜欢这个图表,由于我但愿它是真实的。我不想把余生都过得像一个微不足道的萎缩蓝点同样。git
太戏剧化了。怪潘多拉。当我写这篇文章的时候,我没法控制接下来要发生什么。而 Dashboard Confessional 真是一副毒药。github
好吧,假设 Alicia 的图表是真的,我想与你们分享一下我如今对在生产中运行 Node 应用程序的一些认识。也许在这个问题上咱们的相对维恩图没有重叠。express
首先,让咱们弄清楚“永远不要在生产中直接经过 Node 运行程序”的说法。npm
这个说法可能对,也可能不对。咱们一块儿来探讨下这个说法是怎么获得的。首先,让咱们看看为何不对。json
假设咱们有一个简单的 Express 服务器。我能想到的最简单的 Express 服务器以下:
const express = require("express");
const app = express();
const port = process.env.PORT || 3000;
// 查看 http://localhost:3000
app.get("/", function(req, res) {
res.send("Again I Go Unnoticed");
});
app.listen(port, () => console.log(`Example app listening on port ${port}!`));
复制代码
经过定义在 package.json
中的启动脚原本运行它。
"scripts": {
"start": "node index.js",
"test": "pfffft"
}
复制代码
这里会有两个问题。第一个是开发问题,还有一个是生产问题。
开发问题指的是当咱们修改代码的时候,咱们必须中止而且再次启动应用程序来获得咱们更改后的效果。
咱们一般会使用某种 Node 进程管理器,例如 supervisor
或 nodemon
来解决这个问题。这些包会监听咱们的项目,并会在咱们提交更改时重启服务器。我一般都会这样作。
"scripts": {
"dev": "npx supervisor index.js",
"start": "node index.js"
}
复制代码
而后运行 npm run dev
。请注意,我在这里运行 npx supervisor
,它容许咱们在未安装 supervisor
包的状况下使用它。
咱们的另一个问题是咱们仍然直接在针对 Node 运行,咱们已经说过这很糟糕,如今咱们将要找出缘由。
我要在这里添加另外一个路由,尝试从不存在的磁盘读取文件。这是一个很容易在任何实际应用程序中出现的错误。
const express = require("express");
const app = express();
const fs = require("fs");
const port = process.env.PORT || 3000;
// 查看 http://localhost:3000
app.get("/", function(req, res) {
res.send("Again I Go Unnoticed");
});
app.get("/read", function(req, res) {
// 这个路由不存在
fs.createReadStream("my-self-esteem.txt");
});
app.listen(port, () => console.log(`Example app listening on port ${port}!`));
复制代码
若是咱们直接针对 Node 运行 npm start
而且导航到 read
端点,页面会报错,由于这个文件不存在。
这没什么大不了的,对吧?这只是一个错误,碰巧发生了。
不。这很重要。若是你返回终端,你会看到应用程序已经彻底关闭了。
这意味着,若是你回到浏览器并尝试访问站点的根 URL,你会获得相同的错误页面。一个方法中的错误致使应用中全部的路由都失效了。
这是很糟糕的。这就是人们说 “永远都不要直接在生产中运行 Node”
好。若是咱们没法在生产中直接运行 Node,那么在生产中运行 Node 的正确方法是什么?
咱们有几个选择。
其中之一就是在生产中简单地使用相似 supervisor
或 nodemon
的工具,就像咱们在开发中使用它们同样。这可行,但这些工具备点轻量级。更好的选择是 pm2。
pm2 是一个 Node 进程管理器,有不少有用的功能。就像其余的 “JavaScript” 库同样,你可使用 npm
全局安装它 —— 或者你也能够再次使用 npx
。这里再也不赘述。
有不少使用 pm2 运行程序的方法。最简单的就是在入口文件调用 pm2 start
。
"scripts": {
"start": "pm2 start index.js",
"dev": "npx supervisor index.js"
},
复制代码
终端会显示这些内容:
这是咱们在 pm2 监控的后台运行的进程。若是你访问 read
端点把程序搞崩了,pm2 将自动从新启动它。你在终端不会看到任何内容,由于它在后台运行。若是你想看到 pm2 正在作什么,你能够运行 pm2 log 0
。这个 0
是咱们想要查看日志的进程的 ID。
接下来!你能够看到,pm2 会在应用程序因为未处理的错误而停机时从新启动应用程序。
咱们还能够提取开发命令,并为咱们准备PM2监视文件,在任何更改发生时从新启动程序。
"scripts": {
"start": "pm2 start index.js --watch",
"dev": "npx supervisor index.js"
},
复制代码
请注意,由于 pm2 在后台运行,因此你不能只是经过 ctrl+c
来终止正在运行的 pm2 进程。你必须经过传递进程的 ID 或者名称来中止进程。
pm2 stop 0
pm2 stop index
另请注意,pm2 会保存对进程的引用,以便你能够从新启动它。
若是要删除该进程引用,则须要运行 pm2 delete
。你可使用一个命令 delete
来中止和删除进程。
pm2 delete index
咱们也可使用 pm2 来运行咱们应用程序的多个进程。pm2 会自动平衡这些实例的负载。
pm2 有不少配置选项,它们包含在一个 “ecosystem” 文件中。能够经过运行 pm2 init
来建立一个。你会获得如下的内容:
module.exports = {
apps: [
{
name: "Express App",
script: "index.js",
instances: 4,
autorestart: true,
watch: true,
max_memory_restart: "1G",
env: {
NODE_ENV: "development"
},
env_production: {
NODE_ENV: "production"
}
}
]
};
复制代码
本文不会去讲关于“部署”的内容,由于我对“部署”也不太了解。
“应用程序”部分定义了但愿 pm2 运行和监视的应用程序。你能够运行不止一个应用程序。许多这些配置设置多是不言而喻的。这里我要关注的是实例设置。
pm2 能够运行你的应用程序的多个实例。你能够传入多个你想运行的实例,pm2 均可以启动它们。所以,若是咱们想运行 4 个实例,咱们能够建立如下配置文件。
module.exports = {
apps: [
{
name: "Express App",
script: "index.js",
instances: 4,
autorestart: true,
watch: true,
max_memory_restart: "1G",
env: {
NODE_ENV: "development"
},
env_production: {
NODE_ENV: "production"
}
}
]
};
复制代码
而后咱们使用 pm2 start
来运行它。
pm2 如今会以“集群”模式运行。这些进程中的每个都会在个人计算机上的不一样 CPU 上运行,具体取决于我拥有的内核数量。若是咱们想在不知道拥有多个内核的状况下为每一个内核运行一个进程,咱们就能够将 max
参数传递给 instances
参数来进行配置。
{
...
instances: "max",
...
}
复制代码
让咱们看看个人这台机器上有几个内核。
8个内核!哇。我要在个人微软发行的机器上安装 Subnautica。别告诉他们我说过。
在分隔的 CPU 上运行进程的好处是,即便有一个进程运行异常,占用了 100% 的 CPU,其余进程依然能够保持运行。pm2 将根据须要将 CPU 上的进程加倍。
你可使用 pm2 进行更多操做,包括监视和以其余方式处理那些讨厌的 environment variables。
另一个注意事项:若是因为某种缘由,你但愿 pm2 运行 npm start
指令。你能够经过运行 npm 做为进程并传递 -- start
。“start” 以前的空格在这里很是重要。
pm2 start npm -- start
复制代码
在 Azure AppService 中,默认在后台包含 pm2。若是要在 Azure 中使用 pm2,则无需将其包含在 package.json
文件中。你能够添加一个 ecosystem 文件,而后就可使用了。
好!既然咱们已经了解了关于 pm2 的全部内容,那么让咱们谈谈为何你可能不想使用它,并且它可能确实能够直接针对 Node 运行。
我对此有一些疑问,因此我联系了 Tierney Cyren,它是巨大的橙色知识圈的一部分,特别是在 Node 方面。
Tierney 指出使用基于 Node 的进程管理器(如 pm2)有一些缺点。
主要缘由是不该该使用 Node 来监视 Node。你确定不但愿用你正在监视的东西监视它本身。就像你在周五晚上让我十几岁的儿子监督他本身同样:结果会很糟糕吗?可能,也可能不会。。
Tierney 建议你不要使用 Node 进程管理器来运行应用程序。相反,在更高级别上有一些东西能够监视应用程序的多个单独实例。例如,一个理想的设置是,若是你有一个 Kubernetes 集群,你的应用程序在不一样的容器上运行。而后 Kubernetes 能够监控这些容器,若是其中任何容器发生故障,它能够将它们恢复而且报告健康情况。
在这种状况下,你能够直接针对Node运行,由于你正在更高级别进行监视。
事实证实,Azure 已经在作这件事了。若是咱们不将 pm2 ecosystem 文件推送到 Azure,它将使用咱们的 package.json
文件运行脚原本启动应用程序,咱们能够直接针对Node运行。
"scripts": {
"start": "node index.js"
}
复制代码
在这种状况下,咱们直接针对 Node 运行,不要紧。若是应用程序崩溃,你会发现它又回来了。那是由于在 Azure 中,你的应用程序在一个容器中运行。Azure 负责容器调度,并知道什么时候去更新。
但这里仍然只有一个实例。容器崩溃后须要一秒钟才能从新联机,这意味着你的用户可能会有几秒钟的停机时间。
理想状况下,你但愿运行多个容器。解决方案是将应用程序的多个实例部署到多个 Azure AppService 站点,而后使用 Azure Front Door 在单个 IP 地址下对应用程序进行负载均衡。Front Door 知道容器什么时候关闭,而且将流量路由到应用程序的其余健康实例。
Azure Front Door Service | Microsoft Azure
使用 Azure Front Door 交付,保护和跟踪全球分布式微服务应用程序的性能
Tierney 的另外一个建议是使用 systemd
运行 Node。我不是很了解(或者说根本不知道)systemd
,我已经把这句话弄错过一次了,因此我会用 Tierney 本身的原话来表述:
只有在部署中访问 Linux 并控制 Node 在服务级别上启动的方式时,才有可能实现此选项。若是你在一个长时间运行的 Linux 虚拟机中运行 node.js 进程,好比说 Azure 虚拟机,那么使用 systemd 运行 node.js 是个不错的选择。若是你只是将文件部署到相似于 Azure AppService 或 Heroku 的服务中,或者运行在相似于 Azure 容器实例的容器化环境中,那么你能够避开此选项。
Tierney 还但愿你知道 Node 中有工做线程,这可让你在多个线程上启动你的应用程序,从而无需像 pm2 这样的东西。也许吧。我不知道,我没看过这篇文章。
Tierney 的最后一个建议就是像一个成熟的开发者同样处理错误和编写测试。可是谁有时间呢?
如今你知道这个蓝色小圆圈里的大部分东西了。剩下的只是关于 emo 乐队和啤酒的无用事情。
有关 pm2, Node 和 Azure 的更多信息,请查看如下资源:
若是发现译文存在错误或其余须要改进的地方,欢迎到 掘金翻译计划 对译文进行修改并 PR,也可得到相应奖励积分。文章开头的 本文永久连接 即为本文在 GitHub 上的 MarkDown 连接。
掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 Android、iOS、前端、后端、区块链、产品、设计、人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划、官方微博、知乎专栏。