有時候我也在想我是否真的知道不少東西。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、前端、後端、區塊鏈、產品、設計、人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。