Egg.js 多机平滑重启实践
前提:
首先要聲明的是,我們的應(yīng)用都是在阿里云上多機部署的。當然這里不是安利文,而是給有相同問題的朋友一個實踐的參考。
背景:
我在公司處在一個側(cè)重 js 技術(shù)方向的團隊,后端項目也較多基于 node.js 開發(fā)。項目幾經(jīng)更迭也經(jīng)歷了 koa1 --> koa2 --> egg.js 的框架變更。
在早期項目依賴 koa 的時候,部署方案就是依賴 gitlab-ci + pm2 的方式做自動化部署和進程管理。pm2 可以管理進程的啟動和監(jiān)控,也可以在進程意外終止的時候重新拉起新的進程保證項目持續(xù)運作。但缺點也很明顯,首先 pm2 本身會需要資源,這個資源與項目進程的負載是成線性關(guān)系的,也就是當我們的并發(fā)量大的時候,進程需要更多的資源來處理請求,而 pm2 作為資源的分配者,也需要更多的資源來管理請求和進程的資源調(diào)度。甚至出現(xiàn) god deamon 進程占用了1個多g內(nèi)存😖。這本身來講是額外的資源消耗,并不是我們所希望的。另一方面我們也遇到了在高并發(fā)的情況下,pm2 并不能實現(xiàn) 100% 的平滑重啟。每當有新的代碼被部署的時候,還是會出現(xiàn)一定的請求失敗的情況。這與 pm2 本身有關(guān),相關(guān)的問題不是單一的,這里不一一展開🙍?♂?。
切換到 egg.js 之后,請求的調(diào)度任務(wù)由項目本身的 master 進程來管理,可以盡可能讓項目最大化的利用硬件資源。而且少了 pm2 作為媒介,不用去理會 pm2 造成的影響,可以更加關(guān)注項目本身的問題。
但是 egg.js 提供的啟動方案只有簡單的 start 和 stop。也就是當我要更新項目的時候,一定要關(guān)閉所有進程然后再啟動項目。這樣會造成服務(wù)的短暫不可用的情況,顯然不是我們希望看見的。
所以,我們通過各種嘗試來完善 egg.js 的重啟問題😁。
嘗試:編寫熱重啟腳本
在簡單了解 pm2 的重啟原理后,我們知道,pm2 先 fork 出一個新的進程,然后通過 ipc 通知一個進程關(guān)閉,當進程關(guān)閉后,pm2 再 fork 新的進程,這樣逐個重啟過去的,可以理解為串行。
通過 pm2 的這個方案,結(jié)合 egg-scripts 的源碼,我們修改出了一個可以逐個啟動進程的啟動腳本?egg-cluster-script?😄
起初在請求量低的時候,這個方案看似是可行的(因為錯誤少,沒發(fā)現(xiàn))。但是當我們把服務(wù)對接給公司其他業(yè)務(wù)方后,請求量激增,這個方案的問題就暴露出來了😓:
如果要深入到請求調(diào)度上的問題,這個改動的成本就相對較高了。最終,我們放棄了這個方案。轉(zhuǎn)而尋求通過外部手段的方式來達到平滑重啟的目的😖。
新的方向:SLB 的利用
首先我們前提中提到我司的服務(wù)都是部署在阿里云上的,基本的部署情況差不多如圖:
通常我們的服務(wù)是部署在多臺 ecs 上的,每臺 ecs 上部署多個進程的應(yīng)用。通過 SLB 做負載均衡,把請求根據(jù)權(quán)重適當?shù)姆峙浣o每個 ecs🤔。
在 SLB 中,定時的健康檢查判斷每個 ecs 上的服務(wù)是不是可用的,當不健康的檢查超出了給定的閾值,SLB 就會將 ecs 摘除,不會再將請求分發(fā)給這個 ecs,直到這臺 ecs 的健康檢查恢復正常。
通過這個健康檢查的原理,當 ecs 被摘除的時候,我們就可以任意去擺布這臺 ecs 上的進程了。
有了思路后,接下來就是指定實現(xiàn)的方案🧾:
2. 項目提供一個健康檢查的接口 /devops/health ,通常情況下我們采取 head 請求直接返回 狀態(tài)碼,當 app.running = true 的時候返回 204,否則返回 500;
const { Controller } = require('egg');module.exports = class DevopsController extends Controller {healthCheck() {const { ctx } = this;if(this.app.runnint === true) {ctx.body = null;}else {ctx.status = 500;ctx.body = '';}} }3. 編寫信號發(fā)送腳本改變 app 的健康狀態(tài);
// scripts/health-down.js // 這里的 findNodeProcess,appWorkerPath,titleTemplate 都可以從 egg-script 中找到 async function run () {const processList = await findNodeProcess(item => {const cmd = item.cmd;const title = 'your-app-name'return cmd.includes(appWorkerPath) && cmd.includes(util.format(titleTemplate, title));});for(const pro of processList) {const pid = pro.pid;process.kill(pid,'SIGINT');}// 健康狀態(tài)修改之后暫定 5s 讓 slb 摘除 ecs 后再進行進程處理await new Promise(resolve => setTimeout(resolve, 5000)); }run();4. 給 package.json 添加 scripts: "health:down": ''node scripts/health-down.js", 我們是使用 pm2-depoly 執(zhí)行的部署。所以在 ecosystem.config.js 中,應(yīng)用的 deploy 做修改;
// ecosystem.config.js module.exports = {deploy: {production: {user: 'your-deploy-user',host: [...'your-ecs-hosts'],ref: 'deploy-ref',repo: 'project-repo',ssh_options: ['StrictHostKeyChecking=no'],path: 'deploy-path-on-ecs','pre-deploy': 'git fetch && npm run health:down','post-deploy': 'npm install --production --no-save && npm stop && npm start'}} };5. 設(shè)置健康檢查策略,讓 slb 可以動態(tài)摘除/添加 ecs;
我這里健康檢查的頻率設(shè)置的相對頻繁,可以根據(jù)自己的需要修改這里的配置。大體的意思就是只要 2 次檢查不通過就會把 ecs 摘除,之后只要連續(xù)兩次檢查正常就會把 ecs 重新添加回來。
之后就是結(jié)合自己的 ci 來自動部署了。通過這個方式,我們的項目可以在任何時候?qū)崿F(xiàn)項目的平滑重啟,經(jīng)驗證即使在高峰時段也沒有出現(xiàn)異常。
當前已經(jīng)應(yīng)用的項目是日訪問量在 2億😺 左右的服務(wù),正在逐步推廣到其他服務(wù)中去。這個實踐也并非針對 eggjs 項目,應(yīng)該是具有相對通用性的方案,可以在任意語言和框架中采用😁。
結(jié)尾:
一定有人問為什么一開始不直接采用 SLB 的方案而要繞這么大個彎子。其實原因挺多的
轉(zhuǎn)自?https://zhuanlan.zhihu.com/p/84632879
總結(jié)
以上是生活随笔為你收集整理的Egg.js 多机平滑重启实践的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: CUBA平台使用感想 - 架构师角度
- 下一篇: 加密衍生品赛道异军突起 CBOEX如何做