網易智慧企業 Node.js 實踐(2)| 平滑釋出和前端程式碼

網易雲信發表於2020-07-09

健康檢查

前文提到我們通過閘道器把流量轉發到 Node 應用,那閘道器是如何確定 Node 應用的可用性呢?

如果 Node 應用在釋出的過程中也把流量轉發過來,就會導致請求失敗,所以我們的閘道器會對 Node 應用做一個健康檢查,要首先確定 Node 應用是健康的,也就是可以對外服務的。具體來說就是閘道器會每隔30秒調一下 Node 應用的健康檢查的 HTTP 介面,如果介面返回的 code 是200,那就表示 Node 應用是可用的,使用者的請求在下次檢查之前都會轉發過來,如果返回其他 code,表示應用不可用,請求就不會轉發過來。過30秒再重複這個過程。 enter image description here 【示意圖】

這個方案實現起來非常簡單,只要再 Node 新增個能正常請求的 HTTP 介面即可,比如我們用的介面 /health/check它的 controller 內就 this.ctx.body = 'OK'就可以了。如果 Node 應用正常啟動,可以接受使用者請求,那麼這個介面返回 code 就會是200,如果這個介面不能正常訪問,返回的code不是200,那麼也意味著整個應用是不能訪問的。

那上面這個方案就沒有問題了嗎?肯定是有的,比如我們在釋出時候,首先要讓 Node 應用下線,如果恰好 Node 應用剛被健康檢查通過後就下線了,那麼就會導致後面30秒內轉發到 Node 應用的流量訪問失敗,所以我們有了升級方案-平滑釋出。

平滑釋出

平滑釋出就要跟釋出系統進行配合了,就是我們在釋出應用的時候釋出系統會自動呼叫 Node 應用的下線介面,釋出完成之後會呼叫 Node 應用的上線介面,這樣就可以通過一個全域性變數控制應用的狀態,而這個狀態是和應用的真實狀態沒有關係的。呼叫下線介面後,應用狀態置為下線,然後等待一段時間才真正讓應用下線,所以如果這時有流量進來應用依然可以正常服務。 enter image description here 【示意圖】

邏輯很簡單,但是實現的時候要考慮到 Egg.js 的多程式模型,Egg.js 一般根據伺服器的 CPU 核數來定啟動相應數量的 Worker 程式,這樣就可以完美利用多核資源。每個程式裡都跑的是同一份原始碼,這些程式同時監聽一個埠,所以當釋出系統呼叫下線介面時,只有其中一個程式會收到請求,如果只是把收到請求的這個程式的全域性變數置為下線的話,其它的程式在收到健康檢查的時候依然返回的是線上狀態,這樣就不對了,所以要使用程式間通訊,告訴所有程式下線。

基於這些分析我們實現了 Egg.js 外掛 pp-ndp ,另外由於 Egg.js 外掛中不允許有路由,所以我們通過中介軟體的形式實現,主要程式碼如下:

const { request } = ctx; const { path, hostname } = request; if (path === online) { app.messenger.sendToApp(ONLINE, ''); ctx.body = 'NDP: Nodejs Is Online'; } else if (path === offline) { app.messenger.sendToApp(OFFLINE, ''); ctx.body = 'NDP: Nodejs Is Offline'; } else if (path === check) { ctx.body = 'NDP: Nodejs Start Success'; } else if (path === status) { if (app[ISONLINE]) { ctx.body = 'NDP: Nodejs Is Online'; } else { ctx.status = 500; } } else { await next(); }

當然這個方案的前提是有多臺 Node 服務機器,並按分組進行釋出。如果只有一臺機器就沒必要這麼麻煩了,反正釋出一定會導致停服。

外掛 pp-ndp 為了滿足不同業務需求,online、offline、check、status 這四個 URL 是支援自定義配置的。

這個方案不僅解決了我們平滑釋出的問題,讓釋出不再那麼恐怖,而且還可以利用這個方案讓應用上線後能夠更好的服務,比如:可以在應用獲取配置之後再把應用置為上線狀態,或者可以在應用成功註冊或連線某服務之後再把應用置為上線狀態。讓應用保證最健康的狀態對外服務。

程式碼上 CDN 和 程式碼發現

看到 CDN 可能會奇怪, Node 應用為什麼要 CDN,其實是因為我們為了方便使用同構渲染,而把前端程式碼和 Node 程式碼放在了一個應用裡面,雖然這樣解決了服務端渲染程式碼訪問問題,但是客戶端程式碼還是走 CDN 比較合理。關於 webpack 使用 CDN 網上有很多文章可以參考,我主要介紹下如何發現前端程式碼的,包括程式碼上 CDN 和模版中插入前端程式碼 URL。

主要是使用 webpack-manifest-plugin 這個 webpack 外掛,它會生成一個檔案,比如我們用的 manifest.json,裡面包括前端程式碼資源名稱和對應路徑,類似:

{ "vendor.js": "/static/f5e0281b/js/vendor.chunk.js", "vendor.js.map": "/static/f5e0281b/js/vendor.chunk.js.map", "Page.css": "/static/f2065164/css/Page.chunk.css", "Page.js": "/static/f2065164/js/Page.chunk.js", "Page.js.map": "/static/f2065164/js/Page.chunk.js.map", }

只需要把這個檔案內列的檔案上傳到 CDN 即可,不需自己手動去打包目錄一個一個找。在上傳 CDN 的時候給每個檔案保持同樣路徑。使用我們實現的工具 pp-cdn 在釋出過程中的程式碼編譯完成之後進行上傳。在 Node 模版中引用程式碼時,使用我們開發的 Egg.js 外掛 pp-just,使用方式:

```html

```

外掛內部也是讀取 manifest.json 檔案,輸出加上 CDN 域名之後的 URL,比如上面的程式碼就轉變為:

```html

```

其實之所以這麼做,是為了利用前端程式碼多版本帶來的好處,我們是使用檔案 hash 作為檔案路徑的一部分作為多版本控制的,這樣每次釋出後編譯後會把新生成的檔案路徑寫入 manifest.json,然後通過上面講的方式就可以獲取到最新版本的程式碼。

當然目前 Node 和 前端程式碼在一起是不合理的,可能會導致不必要的釋出,後續應該會完全分離,但是使用manifest.json也可以作為我們後續程式碼分離後的方案之一。

總結

技術方案的選擇一般要結合團隊已有的技術方案和業務需求,本文介紹的平滑釋出方案在我們業務前期,確實解決了我們的釋出問題,讓釋出變得更安全。

但是隨著業務發展我們需要灰度環境,來更好的確保應用的健康狀態,提前發現應用中的問題。另外我們還需要知道我們的應用的執行狀態,所以在下一講內容中,我們會分享關於灰度釋出和應用監控相關的內容。

相關文章