1、自動保存
通常我們希望PM2本身開機自啟,需要執行 pm2 startup
讓其注冊到操作系統的服務管理工具中。
如果我們還希望PM2中的進程能隨着PM2啟動而啟動,就需要每次在新增或刪除進程后執行 pm2 save;
但如果你是一個像筆者一樣記性不好的人,很可能會忘記執行這一步,導致PM2重新啟動后,一個業務都沒啟動。那么這『多余』的一步有沒有辦法能自動執行呢?答案是有的:
pm2 set pm2:autodump true
在Shell中輸入這一行命令,我們就開啟了PM2的自動保存功能,這樣子我們對進程的變更將會被即時保存到 ~/.pm2/dump.pm2
中,無需手動執行pm2 save
。
這里我們使用到了 pm2 set
這個命令,其實這個命令執行的是對 ~/.pm2/module_conf.json
的修改。這個文件是PM2下各模塊的通用配置文件,在安裝其他PM2模塊(如反向代理、負載均衡服務器等)同樣也有可能接觸到這個文件。但對於PM2本身來說,目前可供我們使用的配置項只有 autodump
、registry
、docker
這三個,且沒有集中的文檔對其進行描述,感興趣的讀者可以閱讀這三個配置項在源碼中的實現,此處不再贅述:
- pm2:autodump lib/API/Startup.js#L401
- pm2:registry lib/API/Modules/TAR.js#L319
- pm2:docker lib/API.js#L1551
2、輸出日志到文件
部分業務可能為了省事將日志直接輸出到 stdout 和 stderr,在Shell中直接運行時我們可以使用如Linux和MacOS的重定向符 >
來將stdout輸出到文件,再使用 2>&1
將stderr輸出到stdout。但假設這樣一個『省事』的業務上了生產環境,我們需要使用PM2來運行之,應該怎樣做才能看到日志呢?
PM2同樣為我們提供了日志重定向的功能:
pm2 start --log [file] ...
只需要在啟動進程時指定 --log
參數,並提供日志文件的路徑(是否存在這個文件都沒有關系),就可以將stdout和stderr輸出到我們指定的日志文件了。接下來我們就可以使用如 tail
一類的工具來對日志進行跟蹤,或者你也可以使用PM2自帶的日志顯示功能:
pm2 logs [id] // 這里的id是你在執行 pm2 ps 時候所看到的進程id。
3、設置內存限制
也許你需要在PM2中運行一個內存管理比較差勁的程序,但又不希望這個程序在發生內存泄漏后消耗掉所有資源,影響其他進程,這時候PM2的內存限制功能就可以派上用場了:
pm2 start --max-memory-restart=1024M ...
這里的單位可以為K(iB)、M(iB)和G(iB),使用該參數啟動進程后,PM2就會在進程內存使用率超過限制時強制重啟進程,對於一些存在內存泄漏問題但不便於解決(或沒必要解決)的業務非常實用。
4、查看某個進程的信息
通常我們可以使用 pm2 ps
來查看當前正在運行的所有進程,但這一命令只顯示了最基礎的信息,如環境變量、運行入口、運行參數等信息並沒有在列表中顯示出來。那么我們應該如何查看這些信息呢?PM2提供了這樣的方法:
pm2 show [id]
PM2會輸出關於這個進程的所有信息,如下圖所示:
5、使用總覽面板監控所有進程
上一節我們提到了使用 pm2 show
來查看某個進程的詳細信息,但在生產環境下我們更多時候需要監控所有進程,包括CPU、內存使用、日志輸出等信息,PM2同樣提供了如下的命令來幫助我們監控所有進程:
pm2 monit
PM2會啟動一個面板,如下圖所示:
該面板可以分為四部分:進程列表(左上角)、當前進程日志(右上角)、當前進程性能信息(左下角)、當前進程基礎信息(右下角)。我們可以使用鍵盤左右方向鍵來切換面板,使用上下方向鍵在面板中滾動,對所有進程進行監控。
實際上PM2 Plus還額外提供了資源占用歷史、內存/CPU詳細分析(Profiling)等高級功能,但由於該功能需要付費使用,此處不再展開說明,如果願意付費使用的讀者可以查閱官方文檔:PM2 Plus documentation。
6、使用 SourceMap 獲取錯誤位置
剛剛我們談了那么多『不規范』的業務(如日志輸出到stdout、內存泄漏等),接下來我們舉一個『規范』的例子,也就是使用Webpack(或其他構建工具)對JavaScript代碼進行壓縮后的線上業務。
如果這些業務在線上出現了錯誤,但由於代碼被壓縮,只能顯示錯誤出現在第一行(本來就只有一行),我們要怎樣才能在日志中看到更詳細的信息呢?
PM2考慮到了這一點,並提供了自動加載 SourceMap 的功能:
pm2 start --source-map-support ...
假設你加載的js文件是index.js
,在開啟SourceMap支持后,PM2會自動尋找同目錄下的index.js.map
,並在出現錯誤時加載之,在日志中輸出更易讀的錯誤日志。下面是一個例子:
// error.js
setInterval(function() { triggerError(); }, 1000); function triggerError() { throw new Error("Some error..."); }
這里筆者使用Webpack生成了對應的error.min.js
和error.min.js.map
文件:
webpack ./error.js -o error.min.js --devtool source-map --output-source-map-filename error.min.js.map
然后使用 pm2 加載error.min.js
(不開啟SourceMap):關閉SourceMap支持使用 --disable-source-map-support
pm2 start ./error.min.js --disable-source-map-support
可以看到錯誤信息只顯示出現在第一行(但顯然問題並不是出在第一行)。
接下來我們開啟SourceMap支持,再運行一遍:
pm2 start ./error.min.js --source-map-support
此時就能從日志中看到正常的調用棧信息了,幫助我們更高效的跟蹤問題的來源。
7、業務更新時自動重啟進程
在業務開發與測試過程中,我們經常會遇到文件更新后需要重啟業務的情況。對於本地環境我們可以使用如Webpack Dev Server等工具監聽文件變化,然后在文件發生改變后重新運行服務器。而PM2同樣提供了類似的功能幫助我們實現這一需求:
pm2 start --watch
這樣只要當前目錄下有任意文件發生改變,PM2都會嘗試重啟進程。
在使用這一參數的時候,有幾個需要注意的地方:
(1)請在程序所在的目錄執行啟動命令,否則將會監視的不是程序所在目錄,而是你執行目錄當前所在的目錄。
(2)開啟--watch
參數后,就算你手動停止進程(不刪除),進程也會在文件發生改變后自動啟動,解決該問題的方法是在停止進程的時候加入如下參數:
pm2 stop [id] --watch
(3)如果我們需要忽略一些目錄的變化(如臨時文件)或只監聽某些目錄的變化,就需要使用PM2的API:PM2 Ecosystem,然后撰寫如下配置文件到ecosystem.config.js
(摘自官方文檔):
module.exports = { apps: [{ script: "app.js", watch: ["server", "client"], // Delay between restart
watch_delay: 1000, ignore_watch : ["node_modules", "client/img"], watch_options: { "followSymlinks": false } }] }
8、更聰明的失敗重啟策略
相信很多使用過PM2或Docker的讀者都遇到業務出現運行時錯誤后不斷自動重啟的問題。但很多時候運行時錯誤並非來自於業務本身,比如數據庫服務器中斷、連接數過多、甚至是上文提到的--watch
參數過於靈敏(很多IDE或編輯器支持自動保存,可能保存的版本尚未開發完成,存在語法錯誤)。那么有沒有諸如延時重啟、無縫重啟等功能呢?PM2提供了大量的相關選項:
(1)固定延時
pm2 start --restart-delay=2000 ... // 這里的2000單位為毫秒,即在需要重啟的時候等待兩秒鍾。
(2)靈活延時
更多時候我們需要的是不斷延長的重啟時間,比如Filezilla連接FTP客戶端失敗后的重試時間會隨着重試次數增多不斷延長。PM2也提供了這樣的功能:
pm2 start --exp-backoff-restart-delay=1000
此處的1000單位也是毫秒,PM2會在多次重啟失敗后以設定的時間為初始值,使用指數移動平均算法不斷延長重試時間,最高為15000毫秒(即15秒),並在進程成功啟動30秒后重置重試時間到到初始值。該算法的具體實現可以參考PM2的相關源碼:
(3)零延時高可用
重啟總是需要耗時的,如果我們希望業務在重啟的時候不中斷,就像Kubernetes的滾動更新一樣,那應該怎么做呢?PM2的集群模式可以幫助我們實現這一需求。
需要注意的是,這里我們所指的『集群』並非是Kubernetes這樣邏輯獨立的服務器集群,而是Node.js原生支持的Cluster組件。如果對Cluster組件不了解的讀者可以閱讀這篇Node.js官方文檔:Cluster | Node.js v14.2.0 Documentation。
簡而言之,Cluster組件就類似於PHP中的FPM或是Nginx中的Worker,為單線程的JavaScript運行時增加了能在多CPU上並行接收請求的能力,即運行多個實例作為子進程,並由一個父進程負責請求的調度。
但就算你不懂Cluster組件或是不想為現有業務加入Cluster支持,也沒有關系,因為PM2為你實現了它,這樣我們無需對現有源碼進行任何修改,也能充分利用Cluster組件的功能來實現高可用,任意一個進程的停止,不會對整個業務造成影響。這就是本節筆者要提到的『零延時高可用』。
這里筆者以一個非常簡單的Web服務器為例:
var http = require("http"); http.createServer(function (request, response) { response.writeHead(200, {'Content-Type': 'text/plain'}); response.end('Hello World\n'); }).listen(8081);
然后我們使用如下命令啟動包含四個進程的Cluster(因為筆者的電腦剛好是四個核心)
pm2 start ./server.js -i 4
這里的4就是進程數,也可以設置為max以匹配當前環境最大核心數。
接下來我們就能在pm2 ps
的結果中看到如下四個子進程:
需要注意,Cluster 模式下重啟業務需要使用 reload,而且不能使用進程ID(因為我們需要重啟的是一組進程而非一個),如下所示:
pm2 reload server
這里的server是上面 pm2 ps
結果中進程組共有的name。這樣就既能充分利用服務器性能,又能實現業務的高可用,而我們所需要耗費的只是額外添加一個 -i
參數。
(4)關閉失敗重啟功能
有時候我們還會使用pm2來進行一些盡管耗時,但不需要一直在后台運行的業務,例如爬蟲。
PM2默認會在進程退出后重新啟動,但也提供了參數幫助我們關閉此功能:
pm2 start --no-autorestart ...
使用這個參數后,在業務退出時,狀態會直接變為stop,而不會自動重啟。
9、一條命令操作一組業務
上一節我們提到了可以使用Cluster批量生成進程並對其進行管理,但Cluster只是生成了一批一模一樣的進程。一個常規的業務(尤其是微服務當道的現在)可能會由多個進程組成。這里筆者假設有一個業務,包含API(api.js)、服務端渲染(ssr.js)、數據庫(db.js)、監控(monitor.js)四個組件,如何對它們進行批量管理(比如重啟)呢?
細心的讀者可能在上面 pm2 ps
的輸出結果中看到了namespace的字段,默認為default,其實這就是本節要說的關鍵內容:命名空間。
我們可以使用命名空間對同一類業務進行歸類,然后按命名空間來對業務進行批量管理:
pm2 start api.js --namespace chihu pm2 start ssr.js --namespace chihu pm2 start db.js --namespace chihu pm2 start monitor.js --namespace chihu
這時候我們可以看到,這四個進程的namespace均為chihu
。如果我們需要停止這四個業務,就不需要逐一停止,只需要執行一條命令:
pm2 stop chihu
這樣就能一次停止四個進程了。其他操作同樣類似,只要在對應命令的--help
面板中能看到namespace
的參數就可以,如下所示:
10、PM2的內置HTTP服務器
最后筆者想介紹一個極為實用但極少有人提及的功能:HTTP服務器。
之前和很多同學探討大前端項目前后端分離的時候,發現他們大多都使用Nginx、Apache甚至Tomcat來托管前端的靜態頁面,然后使用PM2來托管后端API。但為了一個簡單的前端頁面專門撰寫一堆配置文件,實在是太浪費時間了,PM2可能也發現了這一點,於是貼心的內置了HTTP服務器:
pm2 serve [path] [port]
是的,就這么簡單,一條命令就能啟動一個HTTP服務器。
由於這個HTTP服務器使用的是Node.js實現,因此性能同樣非常優異,在大部分情況下足夠使用。如果是負載非常大的業務,一般也不會考慮使用PM2,而會使用更具擴展性的Kubernetes。
原文作者:路人甲的世界
原文鏈接:https://zhuanlan.zhihu.com/p/139116801