起運港:
目的港:

初步解決方案:利用 Node Require Cache“熱重載”應用程序代碼

 新聞     |      2020-04-18 11:28

幾個(ge) 月前,我們(men) 留意到,銀行集成服務部署緩慢正在影響團隊發布代碼的能力。工程師要花至少 30 分鍾才能通過多個(ge) 過渡環境和生產(chan) 環境構建、部署和監視變更,這將消耗大量寶貴的工程時間。隨著團隊越來越大,我們(men) 天天發布的代碼也越來越多,這一點變得越來越不可接受。

固然我們(men) 計劃實現長期改進,比如將基於(yu) Amazon ECS 服務的基礎設施遷移到 Kubernetes 上,但是,為(wei) 了在短期內(nei) 進步迭代速度,有必要快速解決(jue) 下這個(ge) 題目。因此,我們(men) 決(jue) 定實踐自定義(yi) 的“快速部署”機製。

我們(men) 的銀行集成服務由 4000 個(ge) Node.js 進程組成,這些進程運行在專(zhuan) 用的 Docker 收留器上,這些收留器托管並部署在 Amazon 的收留器編排服務 ECS 上。在分析了我們(men) 的部署過程之後,我們(men) 將增加的部署延遲回結到三個(ge) 不同的組件上:

啟動任務會(hui) 導致延遲。除了應用程序啟動時間之外,ECS 健康檢查也會(hui) 導致延遲,它決(jue) 定收留器何時預備好開始處理流量。控製這個(ge) 過程的三個(ge) 參數是 interval、retry 和 螺螄粉 startPeriod。假如沒有對健康檢查進行仔細調優(you) ,收留器可能會(hui) 卡在“啟動”狀態,即使它們(men) 已經預備好為(wei) 流量服務。封閉任務會(hui) 導致延遲。當我們(men) 運行 ECS 服務更新時,一個(ge) SIGTERM 信號被發送到所有正在運行的收留器。為(wei) 了處理這個(ge) 題目,我們(men) 在應用程序代碼中使用了一些邏輯,以便在完全封閉服務之前占用現有資源。我們(men) 啟動任務的速度限製了部署的並行性。盡管我們(men) 將 MaximumPercent 參數設置為(wei) 200%,但是 ECS start-taskAPI 調用的硬限製是每個(ge) 調用隻能執行 10 個(ge) 任務,米兰体育全站,而且速度有限。我們(men) 需要調用 400 次才能將所有收留器投進生產(chan) 。

我們(men) 考慮並試驗了一些不同的潛伏解決(jue) 方案,以逐步實現總體(ti) 目標:

減少生產(chan) 中運行的收留器總數。這當然是可行的,但它涉及到對服務架構進行重大修改,以使其能夠處理相同的請求吞吐量,在進行這樣的修改之前,還需要進行更多研究。通過修改健康檢查參數來調整 ECS 配置。我們(men) 嚐試通過減少 interval 和 startPeriod 的值來加強健康檢查,但是 ECS 在啟動時將健康的收留器錯誤地標記為(wei) 不健康,導致我們(men) 的服務永遠無法完全穩定在 100% 健康狀態。由於(yu) 根本題目(ECS 部署緩慢)依然存在,對這些參數進行迭代是一個(ge) 緩慢而費力的過程。在 ECS 集群中啟動更多實例,以便可以在部署期間同時啟動更多任務。這樣做可以減少部署時間,但不會(hui) 減少太多。從(cong) 長遠來看,這也不劃算。通過重構初始化和關(guan) 機邏輯優(you) 化服務重啟時間。隻需要做一些小小的修改,我們(men) 就能夠在每個(ge) 收留器中節省大約 5 秒的時間。

盡管這些更改將總體(ti) 部署時間減少了幾分鍾,但是我們(men) 仍然需要將時間進步至少一個(ge) 數目級,才能以為(wei) 題目已解決(jue) 。這將需要一個(ge) 根本不同的解決(jue) 方案。

Node require cache 是一個(ge) JavaScript 對象,它根據需要緩存模塊。這意味著多次執行 require(‘foo’) 或 import * as 螺螄粉 foo from 'foo’隻會(hui) 在第一次時請求 foo 模塊。神奇的是,刪除 require cache 中的條目(我們(men) 可以使用全局 require.cache 對象訪問)將迫使 Node 在下次導進模塊時從(cong) 磁盤重新讀取該模塊。

為(wei) 了繞過 ECS 部署過程,我們(men) 嚐試使用 Node 的 require cache 在運行時執行應用程序代碼的“熱重載”。一旦接收到外部觸發(我們(men) 將實在現為(wei) 銀行集成服務上的 gRPC 端點),應用程序將下載新代碼來替換現有的構建,清除 require cache,從(cong) 而強製重新導進所有相關(guan) 模塊。通過這種方法,我們(men) 能夠消除 ECS 部署中存在的大部分延遲,優(you) 化整個(ge) 部署過程。

在 Plaiderdays (我們(men) 的內(nei) 部黑客馬拉鬆)期間,來自不同團隊的一組工程師聚在一起,為(wei) 我們(men) 所謂的“快速部署”實現了一個(ge) 端到真個(ge) 概念驗證。當我們(men) 一起想法構建一個(ge) 原型時,有一件事似乎出了題目:假如下載新構建的 Node 代碼也試圖使失效緩存,跨境鐵路 國際物流,那麽(me) 下載器代碼本身將如何重新加載就不清楚了。(有一種方法可以解決(jue) 這個(ge) 題目,就是使用 Node EventEmitter ,但是會(hui) 給代碼增加相當大的複雜性)。更重要的是,米兰体育全站,還存在運行未同步代碼版本的風險,這可能導致應用程序意外失敗。

由於(yu) 我們(men) 不願意在銀行集成服務的可靠性上妥協,這種複雜性需要重新考慮“熱重載”方法。

在過往,為(wei) 了在所有服務中運行一係列同一的初始化任務,我們(men) 編寫(xie) 了自己的進程封裝器,它的名稱非常貼切,叫做 Bootloader。Bootloader 的核心包含設置日誌管道、轉發信號和讀取 ECS 元數據的邏輯。每個(ge) 服務都是通過將應用程序可執行文件的路徑以及一係列標誌傳(chuan) 遞給 Bootloader 來啟動的,這些文件在執行初始化步驟之後會(hui) 作為(wei) 子進程執行。

我們(men) 沒有清除 Node 的 require cache,而是在下載預期的部署構建後,使用特殊的退出代碼來調用 process.exit 實現服務更新。我們(men) 還在 Bootloader 中實現了自定義(yi) 邏輯,以觸發使用此代碼退出的任何子進程的進程重載。與(yu) “熱重載”方法類似,這使我們(men) 能夠繞過 ECS 部署的本錢並快速引導新代碼,同時避免“熱重載”的陷阱。此外,Bootloader 層的這種“快速部署”邏輯答應我們(men) 將其推廣到在 Plaid 運行的任何其他服務。

下麵是終極解決(jue) 方案:

Jenkins 部署管道向銀行集成服務的所有實例發送 RPC 請求,指示它們(men) “快速部署”特定的提交散列。應用程序接收 gRPC 請求進行快速部署,並根據接收到的提交散列從(cong) Amazon S3 下載構建好的壓縮包。然後,它替換文件係統上的現有構建,並使用 Bootloader 識別的特殊退出代碼退出。Bootloader 看到應用程序使用這個(ge) 特殊的“Reload”退出代碼退出,然後重新啟動應用程序。服務運行新的代碼。

下麵這張圖簡單說明了這個(ge) 過程。

結果

我們(men) 能夠在 3 周內(nei) 交付這個(ge) “快速部署”項目,並將 90% 生產(chan) 收留器的部署時間從(cong) 30 多分鍾減少到 1.5 分鍾。

上圖顯示了我們(men) 為(wei) 銀行集成服務部署的收留器數目(按提交表示為(wei) 不同的顏色)。假如留意下黃線,就可以看到它在 12:15 左右增長趨於(yu) 平穩,這代表我們(men) 的收留器長尾仍然在占用資源。

這個(ge) 項目極大進步了 Plaid 集成工作的速度,答應我們(men) 更快地發布特性及進行 Bug 修複,並將浪費在上下文切換和監視儀(yi) 表板上的工程時間最小化。這也證實了我們(men) 的工程文化,即通過黑客馬拉鬆得來的想法實現具有實質性影響的項目。

最後,我自己是一名從(cong) 事了多年開發的JAVA老程序員,辭職目前在做自己的java私人定製課程,今年年初我花了一個(ge) 月整理了一份最適合2019年學習(xi) 的java學習(xi) 幹貨,可以送給每一位喜歡java的小夥(huo) 伴,想要獲取的可以關(guan) 注我的頭條號並在後台私信我:java,即可免費獲取。

本文轉載至微信公眾(zhong) 號——InfoQ,如有侵權請聯係立刪!


袋裝螺螄粉 袋裝螺螄粉

鄭重聲明:本文版權歸原作者所有,轉載文章僅(jin) 為(wei) 傳(chuan) 播更多信息之目的,如作者信息標記有誤,請第一時間聯係我們(men) 修改或刪除,多謝。

米兰体育全站
國際空運
國際海運
國際快遞
跨境鐵路
多式聯運
起始地 目的地 45+ 100 300 詳情
深圳 迪拜 30 25 20
廣州 南非 26 22 16
上海 巴西 37 28 23 詳情
寧波 歐洲 37 27 23 詳情
香港 南亞 30 27 25 詳情

給我們留言