Node.js 進程平滑離場剖析

来源:https://www.cnblogs.com/qcloud1001/archive/2019/01/24/10313707.html
-Advertisement-
Play Games

本文由雲+社區發表 作者:草小灰 使用 Node.js 搭建 HTTP Server 已是司空見慣的事。在生產環境中,Node 進程平滑重啟直接關係到服務的可靠性,它的重要性不容我們忽視。既然是平滑重啟,就涉及到新舊進程的接替過渡: 首先,保證新進程平滑入場 其次,保證舊進程平滑離場 本文主要談論下 ...


本文由雲+社區發表

作者:草小灰

使用 Node.js 搭建 HTTP Server 已是司空見慣的事。在生產環境中,Node 進程平滑重啟直接關係到服務的可靠性,它的重要性不容我們忽視。既然是平滑重啟,就涉及到新舊進程的接替過渡:

  • 首先,保證新進程平滑入場
  • 其次,保證舊進程平滑離場

本文主要談論下,在新舊進程接替過渡期間,如何保證舊進程平滑離場。那怎樣的離場才算平滑的呢?

如何定義平滑離場

以進程離場作為時間分割點,我們可以把請求分為兩類:增量請求存量請求

  • 在進程離場前,停止接收新的(增量)請求
  • 在進程離場前,保證未完成的(存量)請求正常響應

所以,達成以上兩個目標,基本上我們就認為進程的離場是平滑的。在談如何做到進程平滑離場前,我們需要一種機制,這種機制能讓我們主動通知進程何時離場,這就涉及到進程間通信(IPC)的知識了,我們先簡單瞭解下。

進程間通信

對 Unix 或類 Unix 系統而言,進程間通信的方式有很多種 —— 信號(Signal)是其中的一種。

信號的種類有很多,如 SIGINTSIGTERMSIGKILL 等。這些信號視具體需要用於不同的場景,比如 SIGKILL 一般用於強殺進程。

我們可以在命令行執行 kill -l 查看所有的信號,如下所示(其中的數字表示 signal number):

$ kill -l
 1) SIGHUP   2) SIGINT   3) SIGQUIT  4) SIGILL
 5) SIGTRAP  6) SIGABRT  7) SIGEMT   8) SIGFPE
 9) SIGKILL 10) SIGBUS  11) SIGSEGV 12) SIGSYS
13) SIGPIPE 14) SIGALRM 15) SIGTERM 16) SIGURG
17) SIGSTOP 18) SIGTSTP 19) SIGCONT 20) SIGCHLD
21) SIGTTIN 22) SIGTTOU 23) SIGIO   24) SIGXCPU
25) SIGXFSZ 26) SIGVTALRM   27) SIGPROF 28) SIGWINCH
29) SIGINFO 30) SIGUSR1 31) SIGUSR2

我們可以使用 kill 命令向進程發送指定信號:

# 發送 SIGTERM 信號(預設,無須指定信號類型)給進程
$ kill <pid>

# 發送 SIGINT 信號給進程,其中 <pid> 為具體的進程 ID
$ kill -INT <pid>

# 發送 SIGKILL 信號給進程
$ kill -KILL <pid>

# 或者
$ kill -9 <pid>

進程可以對接收到的信號作出回應。對 Node 應用而言,信號是被當作事件發送給 Node 進程的,進程接收到 SIGTERMSIGINT 事件有預設回調,官方文檔是這麼描述的:

'SIGTERM' and 'SIGINT' have default handlers on non-Windows platforms that reset the terminal mode before exiting with code 128 + signal number. If one of these signals has a listener installed, its default behavior will be removed (Node.js will no longer exit).

這句話寫的很抽象,它是什麼意思呢?我們以一個簡單的 Node 應用為例。

新建文件,鍵入如下代碼,將其保存為 server.js:

const http = require('http');

const server = http.createServer((req, res) => {
  setTimeout(() => {
    res.writeHead(200, { 'Content-Type': 'text/plain' });
    res.end('It works');
  }, 5000);
});

server.listen(9420);

這裡為了方便測試,對應用接收到的每個 http 請求,等待 5 秒後再進行響應。

執行 node server.js 啟動應用。為了給應用發送信號,我們需要獲取應用的進程 ID,我們可以使用 lsof 命令查看:

$ lsof -i TCP:9420
COMMAND   PID       USER   FD   TYPE             DEVICE SIZE/OFF NODE NAME
node    70826 myunlessor   13u  IPv6 0xd250033eef8912eb      0t0  TCP *:9420 (LISTEN)

事實上,我們也可以在代碼里通過 console.log(process.pid) 獲取進程 ID。這裡只是順便介紹一種,在知道監聽 TCP 埠的情況獲取進程的方式。

隨後,我們發起一個請求,在收到響應之前(有 5 秒等待時間),我們給應用發送 SIGINT 信號。

$ curl http://localhost:9420 &

$ kill -INT 70826
curl: (52) Empty reply from server
[1]+  Exit 52                 curl http://localhost:9420

可以看到,請求沒能正常收到響應。也就是說,預設情況下,Node 應用在接收到 SIGINT 信號時,會馬上把進程殺死,無視進程還沒處理完成的請求。所幸的是,我們可以手動監聽進程的 SIGINT 事件,像這樣:

process.on('SIGINT', () => {
  // do something here
});

如果我們在事件回調里什麼都不做,就意味著忽略該信號,進程該幹嘛幹嘛,像什麼事情都沒發生一樣。

那麼,如果我手動監聽 SIGKILL 會如何呢?對不起,SIGKILL 是不能被監聽的,官方文檔如是說:

'SIGKILL' cannot have a listener installed, it will unconditionally terminate Node.js on all platforms.

這是合情合理的,要知道 SIGKILL 是用於強殺進程的,你無法干預它的行為。

回到上面的問題,我們可以近似地理解為 Node 應用響應 SIGINT 事件的預設回調是這樣子的:

process.on('SIGINT', () => {
  process.exit(128 + 2/* signal number */);
});

我們可以列印 exit code 來驗證:

$ node server.js

$ echo $?
130

有了信號,我們就能主動通知進程何時離場了,下麵談一談進程如何平滑離場。

如何讓進程平滑離場

我們在上面示例基礎上,也就是在文件 server.js 中,補充如下代碼:

process.on('SIGINT', () => {
  server.close(err => {
    process.exit(err ? 1 : 0);
  });
});

這段代碼很簡單,我們改寫應用接收到 SIGINT 事件的預設行為,不再簡單粗暴直接殺死進程,而是在 server.close 方法回調中再調用 process.exit 方法,接著繼續試驗一下。

$ lsof -i TCP:9420
COMMAND   PID       USER   FD   TYPE             DEVICE SIZE/OFF NODE NAME
node    75842 myunlessor   13u  IPv6 0xd250033ec7c9362b      0t0  TCP *:9420 (LISTEN)

$ curl http://localhost:9420 &
[1] 75878

$ kill -2 75842

$ It works
[1]+  Done                    curl http://localhost:9420

可以看到,應用在退出前(即進程離場前),成功地響應了存量請求。

我們還可以驗證,進程離場前,確實不再接收增量請求:

$ curl http://127.0.0.1:9420
curl: (7) Failed to connect to 127.0.0.1 port 9420: Connection refused

這正是 server.close 所做的事,進程平滑離場就是這麼簡單,官方文檔是這麼描述這個 API 的:

Stops the server from accepting new connections and keeps existing connections. This function is asynchronous, the server is finally closed when all connections are ended and the server emits a 'close' event. The optional callback will be called once the 'close' event occurs. Unlike that event, it will be called with an Error as its only argument if the server was not open when it was closed.

結束語

進程平滑離場只是 Node 進程平滑重啟的一部分。生產環境中,新舊進程的接替涉及進程負載均衡、進程生命周期管理等方方面面的考慮。專業的工具做專業的事,PM2 就是 Node 進程管理很好的選擇。

此文已由騰訊雲+社區在各渠道發佈

獲取更多新鮮技術乾貨,可以關註我們騰訊雲技術社區-雲加社區官方號及知乎機構號


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1. 字元串: 2. 數值 js中,數值類型比較簡單,不區分整型浮點型等數字類型。就是這個數,帶不帶小數點,對js來說都是一樣進行識別。 <!DOCTYPE html><html><body> <script>var a = 1;var b = 2.3;var c = a + b;var name1 ...
  • HTML是什麼? 超文本標記語言(Hypertext Markup Language, HTML)是一種用於創建網頁的標記語言。 本質上是瀏覽器可識別的規則,我們按照規則寫網頁,瀏覽器根據規則渲染我們的網頁。對於不同的瀏覽器,對同一個標簽可能會有不同的解釋。(相容性問題) 網頁文件的擴展名:.htm ...
  • 一、前言 之前一直也沒有研究過webpack4是基於怎樣的規則去拆分模塊的,現在正好有時間打算好好瞭解一下,看了官方文檔也陸陸續續的看了看網上別人寫的文章,感覺大部分都是將官方文檔翻譯了一遍,很多問題都沒有解釋清楚,無奈只好自己寫demo去通過實際編譯結果來理解,經過一天多的不斷調試和百度,基本弄清 ...
  • v-bind:是Vue提供的用於綁定html屬性的指令。 html中常見的屬性有:id、class、src、title、style等,他們都是以 名稱/值對 的形式出現,如:id="first" 本篇的內容主要是介紹使用v-bind指令動態給這些屬性賦值。 話不多說,上代碼: 代碼很簡單,主要分為兩 ...
  • 一:基礎知識 1、JavaScript語言的歷史 l 早期名稱:livescript l 開發公司:網景公司(netscape) 2、JavaScript語言的基本特點 l 基於對象 l 事件驅動 l 解釋性語言 l 實時性 l 動態性 l 跨平臺 l 開發使用簡單 l 安全性 l 腳本語言 l 弱 ...
  • 上一篇我們講解了兩種方式,把Vue對象的數據展示在頁面上: 1、插值表達式 2、v-text指令 但是如果我們展示的數據包含元素標簽或者樣式,我們想展示標簽或樣式所定義的屬性作用,該怎麼進行渲染,比如展示內容為:<h1>這是一個h1元素內容</h1>。我們先用插值表達式和v-text嘗試一下。 運行 ...
  • 一.效果圖 二、. 三、源碼 <!doctype html> <html> <head> <meta charset="UTF-8"> <meta name="keywords" content="關鍵詞"> <meta name="description" content="描述"> <title ...
  • 如果有當前的變數(VUEX聲名的)會發生改變的(change),剛綁定的值需要在data () 內進行聲明 一個新的變數進行處理。 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...