起運港:
目的港:
國際空運
國際海運
國際快遞

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

?新聞 ????|???? ?2020-04-18 11:28

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

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

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

啟動任務會導致延遲。除了應用程序啟動時間之外,ECS 健康檢查也會導致延遲,它決定收留器何時預備好開始處理流量??刂七@個過程的三個參數(shù)是 interval、retry 和 螺螄粉 startPeriod。假如沒有對健康檢查進行仔細調(diào)優(yōu),收留器可能會卡在“啟動”狀態(tài),即使它們已經(jīng)預備好為流量服務。封閉任務會導致延遲。當我們運行 ECS 服務更新時,一個 SIGTERM 信號被發(fā)送到所有正在運行的收留器。為了處理這個題目,我們在應用程序代碼中使用了一些邏輯,以便在完全封閉服務之前占用現(xiàn)有資源。我們啟動任務的速度限制了部署的并行性。盡管我們將 MaximumPercent 參數(shù)設置為 200%,但是 ECS start-taskAPI 調(diào)用的硬限制是每個調(diào)用只能執(zhí)行 10 個任務,千航國際,而且速度有限。我們需要調(diào)用 400 次才能將所有收留器投進生產(chǎn)。

我們考慮并試驗了一些不同的潛伏解決方案,以逐步實現(xiàn)總體目標:

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

盡管這些更改將總體部署時間減少了幾分鐘,但是我們?nèi)匀恍枰獙r間進步至少一個數(shù)目級,才能以為題目已解決。這將需要一個根本不同的解決方案。

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

為了繞過 ECS 部署過程,我們嘗試使用 Node 的 require cache 在運行時執(zhí)行應用程序代碼的“熱重載”。一旦接收到外部觸發(fā)(我們將實在現(xiàn)為銀行集成服務上的 gRPC 端點),應用程序?qū)⑾螺d新代碼來替換現(xiàn)有的構建,清除 require cache,從而強制重新導進所有相關模塊。通過這種方法,我們能夠消除 ECS 部署中存在的大部分延遲,優(yōu)化整個部署過程。

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

由于我們不愿意在銀行集成服務的可靠性上妥協(xié),這種復雜性需要重新考慮“熱重載”方法。

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

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

下面是終極解決方案:

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

下面這張圖簡單說明了這個過程。

結(jié)果

我們能夠在 3 周內(nèi)交付這個“快速部署”項目,并將 90% 生產(chǎn)收留器的部署時間從 30 多分鐘減少到 1.5 分鐘。

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

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

最后,我自己是一名從事了多年開發(fā)的JAVA老程序員,辭職目前在做自己的java私人定制課程,今年年初我花了一個月整理了一份最適合2019年學習的java學習干貨,可以送給每一位喜歡java的小伙伴,想要獲取的可以關注我的頭條號并在后臺私信我:java,即可免費獲取。

本文轉(zhuǎn)載至微信公眾號——InfoQ,如有侵權請聯(lián)系立刪!


袋裝螺螄粉 袋裝螺螄粉

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

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

在線咨詢-給我們留言