幾個(gè)月前,我們留意到,銀行集成服務(wù)部署緩慢正在影響團(tuán)隊(duì)發(fā)布代碼的能力。工程師要花至少 30 分鐘才能通過多個(gè)過渡環(huán)境和生產(chǎn)環(huán)境構(gòu)建、部署和監(jiān)視變更,這將消耗大量寶貴的工程時(shí)間。隨著團(tuán)隊(duì)越來越大,我們天天發(fā)布的代碼也越來越多,這一點(diǎn)變得越來越不可接受。
固然我們計(jì)劃實(shí)現(xiàn)長(zhǎng)期改進(jìn),比如將基于 Amazon ECS 服務(wù)的基礎(chǔ)設(shè)施遷移到 Kubernetes 上,但是,為了在短期內(nèi)進(jìn)步迭代速度,有必要快速解決下這個(gè)題目。因此,我們決定實(shí)踐自定義的“快速部署”機(jī)制。
我們的銀行集成服務(wù)由 4000 個(gè) Node.js 進(jìn)程組成,這些進(jìn)程運(yùn)行在專用的 Docker 收留器上,這些收留器托管并部署在 Amazon 的收留器編排服務(wù) ECS 上。在分析了我們的部署過程之后,我們將增加的部署延遲回結(jié)到三個(gè)不同的組件上:
啟動(dòng)任務(wù)會(huì)導(dǎo)致延遲。除了應(yīng)用程序啟動(dòng)時(shí)間之外,ECS 健康檢查也會(huì)導(dǎo)致延遲,它決定收留器何時(shí)預(yù)備好開始處理流量??刂七@個(gè)過程的三個(gè)參數(shù)是 interval、retry 和 螺螄粉 startPeriod。假如沒有對(duì)健康檢查進(jìn)行仔細(xì)調(diào)優(yōu),收留器可能會(huì)卡在“啟動(dòng)”狀態(tài),即使它們已經(jīng)預(yù)備好為流量服務(wù)。封閉任務(wù)會(huì)導(dǎo)致延遲。當(dāng)我們運(yùn)行 ECS 服務(wù)更新時(shí),一個(gè) SIGTERM 信號(hào)被發(fā)送到所有正在運(yùn)行的收留器。為了處理這個(gè)題目,我們?cè)趹?yīng)用程序代碼中使用了一些邏輯,以便在完全封閉服務(wù)之前占用現(xiàn)有資源。我們啟動(dòng)任務(wù)的速度限制了部署的并行性。盡管我們將 MaximumPercent 參數(shù)設(shè)置為 200%,但是 ECS start-taskAPI 調(diào)用的硬限制是每個(gè)調(diào)用只能執(zhí)行 10 個(gè)任務(wù),千航國(guó)際,而且速度有限。我們需要調(diào)用 400 次才能將所有收留器投進(jìn)生產(chǎn)。
我們考慮并試驗(yàn)了一些不同的潛伏解決方案,以逐步實(shí)現(xiàn)總體目標(biāo):
減少生產(chǎn)中運(yùn)行的收留器總數(shù)。這當(dāng)然是可行的,但它涉及到對(duì)服務(wù)架構(gòu)進(jìn)行重大修改,以使其能夠處理相同的請(qǐng)求吞吐量,在進(jìn)行這樣的修改之前,還需要進(jìn)行更多研究。通過修改健康檢查參數(shù)來調(diào)整 ECS 配置。我們嘗試通過減少 interval 和 startPeriod 的值來加強(qiáng)健康檢查,但是 ECS 在啟動(dòng)時(shí)將健康的收留器錯(cuò)誤地標(biāo)記為不健康,導(dǎo)致我們的服務(wù)永遠(yuǎn)無法完全穩(wěn)定在 100% 健康狀態(tài)。由于根本題目(ECS 部署緩慢)依然存在,對(duì)這些參數(shù)進(jìn)行迭代是一個(gè)緩慢而費(fèi)力的過程。在 ECS 集群中啟動(dòng)更多實(shí)例,以便可以在部署期間同時(shí)啟動(dòng)更多任務(wù)。這樣做可以減少部署時(shí)間,但不會(huì)減少太多。從長(zhǎng)遠(yuǎn)來看,這也不劃算。通過重構(gòu)初始化和關(guān)機(jī)邏輯優(yōu)化服務(wù)重啟時(shí)間。只需要做一些小小的修改,我們就能夠在每個(gè)收留器中節(jié)省大約 5 秒的時(shí)間。
盡管這些更改將總體部署時(shí)間減少了幾分鐘,但是我們?nèi)匀恍枰獙r(shí)間進(jìn)步至少一個(gè)數(shù)目級(jí),才能以為題目已解決。這將需要一個(gè)根本不同的解決方案。
Node require cache 是一個(gè) JavaScript 對(duì)象,它根據(jù)需要緩存模塊。這意味著多次執(zhí)行 require(‘foo’) 或 import * as 螺螄粉 foo from 'foo’只會(huì)在第一次時(shí)請(qǐng)求 foo 模塊。神奇的是,刪除 require cache 中的條目(我們可以使用全局 require.cache 對(duì)象訪問)將迫使 Node 在下次導(dǎo)進(jìn)模塊時(shí)從磁盤重新讀取該模塊。
為了繞過 ECS 部署過程,我們嘗試使用 Node 的 require cache 在運(yùn)行時(shí)執(zhí)行應(yīng)用程序代碼的“熱重載”。一旦接收到外部觸發(fā)(我們將實(shí)在現(xiàn)為銀行集成服務(wù)上的 gRPC 端點(diǎn)),應(yīng)用程序?qū)⑾螺d新代碼來替換現(xiàn)有的構(gòu)建,清除 require cache,從而強(qiáng)制重新導(dǎo)進(jìn)所有相關(guān)模塊。通過這種方法,我們能夠消除 ECS 部署中存在的大部分延遲,優(yōu)化整個(gè)部署過程。
在 Plaiderdays (我們的內(nèi)部黑客馬拉松)期間,來自不同團(tuán)隊(duì)的一組工程師聚在一起,為我們所謂的“快速部署”實(shí)現(xiàn)了一個(gè)端到真?zhèn)€概念驗(yàn)證。當(dāng)我們一起想法構(gòu)建一個(gè)原型時(shí),有一件事似乎出了題目:假如下載新構(gòu)建的 Node 代碼也試圖使失效緩存,跨境鐵路 國(guó)際物流,那么下載器代碼本身將如何重新加載就不清楚了。(有一種方法可以解決這個(gè)題目,就是使用 Node EventEmitter ,但是會(huì)給代碼增加相當(dāng)大的復(fù)雜性)。更重要的是,千航國(guó)際,還存在運(yùn)行未同步代碼版本的風(fēng)險(xiǎn),這可能導(dǎo)致應(yīng)用程序意外失敗。
由于我們不愿意在銀行集成服務(wù)的可靠性上妥協(xié),這種復(fù)雜性需要重新考慮“熱重載”方法。
在過往,為了在所有服務(wù)中運(yùn)行一系列同一的初始化任務(wù),我們編寫了自己的進(jìn)程封裝器,它的名稱非常貼切,叫做 Bootloader。Bootloader 的核心包含設(shè)置日志管道、轉(zhuǎn)發(fā)信號(hào)和讀取 ECS 元數(shù)據(jù)的邏輯。每個(gè)服務(wù)都是通過將應(yīng)用程序可執(zhí)行文件的路徑以及一系列標(biāo)志傳遞給 Bootloader 來啟動(dòng)的,這些文件在執(zhí)行初始化步驟之后會(huì)作為子進(jìn)程執(zhí)行。
我們沒有清除 Node 的 require cache,而是在下載預(yù)期的部署構(gòu)建后,使用特殊的退出代碼來調(diào)用 process.exit 實(shí)現(xiàn)服務(wù)更新。我們還在 Bootloader 中實(shí)現(xiàn)了自定義邏輯,以觸發(fā)使用此代碼退出的任何子進(jìn)程的進(jìn)程重載。與“熱重載”方法類似,這使我們能夠繞過 ECS 部署的本錢并快速引導(dǎo)新代碼,同時(shí)避免“熱重載”的陷阱。此外,Bootloader 層的這種“快速部署”邏輯答應(yīng)我們將其推廣到在 Plaid 運(yùn)行的任何其他服務(wù)。
下面是終極解決方案:
Jenkins 部署管道向銀行集成服務(wù)的所有實(shí)例發(fā)送 RPC 請(qǐng)求,指示它們“快速部署”特定的提交散列。應(yīng)用程序接收 gRPC 請(qǐng)求進(jìn)行快速部署,并根據(jù)接收到的提交散列從 Amazon S3 下載構(gòu)建好的壓縮包。然后,它替換文件系統(tǒng)上的現(xiàn)有構(gòu)建,并使用 Bootloader 識(shí)別的特殊退出代碼退出。Bootloader 看到應(yīng)用程序使用這個(gè)特殊的“Reload”退出代碼退出,然后重新啟動(dòng)應(yīng)用程序。服務(wù)運(yùn)行新的代碼。
下面這張圖簡(jiǎn)單說明了這個(gè)過程。
結(jié)果
我們能夠在 3 周內(nèi)交付這個(gè)“快速部署”項(xiàng)目,并將 90% 生產(chǎn)收留器的部署時(shí)間從 30 多分鐘減少到 1.5 分鐘。
上圖顯示了我們?yōu)殂y行集成服務(wù)部署的收留器數(shù)目(按提交表示為不同的顏色)。假如留意下黃線,就可以看到它在 12:15 左右增長(zhǎng)趨于平穩(wěn),這代表我們的收留器長(zhǎng)尾仍然在占用資源。
這個(gè)項(xiàng)目極大進(jìn)步了 Plaid 集成工作的速度,答應(yīng)我們更快地發(fā)布特性及進(jìn)行 Bug 修復(fù),并將浪費(fèi)在上下文切換和監(jiān)視儀表板上的工程時(shí)間最小化。這也證實(shí)了我們的工程文化,即通過黑客馬拉松得來的想法實(shí)現(xiàn)具有實(shí)質(zhì)性影響的項(xiàng)目。
最后,我自己是一名從事了多年開發(fā)的JAVA老程序員,辭職目前在做自己的java私人定制課程,今年年初我花了一個(gè)月整理了一份最適合2019年學(xué)習(xí)的java學(xué)習(xí)干貨,可以送給每一位喜歡java的小伙伴,想要獲取的可以關(guān)注我的頭條號(hào)并在后臺(tái)私信我:java,即可免費(fèi)獲取。
本文轉(zhuǎn)載至微信公眾號(hào)——InfoQ,如有侵權(quán)請(qǐng)聯(lián)系立刪!
鄭重聲明:本文版權(quán)歸原作者所有,轉(zhuǎn)載文章僅為傳播更多信息之目的,如作者信息標(biāo)記有誤,請(qǐng)第一時(shí)間聯(lián)系我們修改或刪除,多謝。
千航國(guó)際 |
國(guó)際空運(yùn) |
國(guó)際海運(yùn) |
國(guó)際快遞 |
跨境鐵路 |
多式聯(lián)運(yùn) |