作者:耿杰 春暉 志遠(yuǎn)
經(jīng)過近3年的建設(shè)打磨,美團(tuán)流水線引擎完成了服務(wù)端的基建統(tǒng)一,每日支撐近十萬次的流水線執(zhí)行量,系統(tǒng)成功率保持在99.99%以上。
一、背景
持續(xù)交付這個概念最早在2006年敏捷大會上被提出,經(jīng)過多年的發(fā)展,目前已成為很多技術(shù)團(tuán)隊提升研發(fā)效能的必經(jīng)之路。通過建設(shè)部署流水線,打通從代碼開發(fā)到功能交付的整個環(huán)節(jié),以自動化的方式完成構(gòu)建、測試、集成、發(fā)布等一系列行為,最終實現(xiàn)向用戶持續(xù)高效地交付價值。
流水線引擎作為支撐部署流水線的底座,它的好壞直接影響著部署流水線建設(shè)的水平。業(yè)界通常的做法是通過Jenkins、GitlabCI等開源工具(或公有云產(chǎn)品)進(jìn)行搭建,這是一條能幫助業(yè)務(wù)快速落地持續(xù)交付的道路,美團(tuán)早期也是采用搭建Jenkins的方式來快速支撐業(yè)務(wù)。
但隨著越來越多業(yè)務(wù)開始做持續(xù)交付的建設(shè),這種“短平快”方式的弊端逐漸顯現(xiàn)。比如,工具建設(shè)沒有統(tǒng)一的標(biāo)準(zhǔn),各業(yè)務(wù)都需要去了解整個工具鏈的細(xì)節(jié),建設(shè)成本高、水平參差不齊,很少有業(yè)務(wù)能搭建完整的部署流水線。同時,業(yè)務(wù)每天的構(gòu)建量都在快速增長,逐漸超過Jenkins等開源工具所能承受的極限,在交付高峰期任務(wù)嚴(yán)重排隊、服務(wù)不可用現(xiàn)象頻出,嚴(yán)重影響著業(yè)務(wù)交付的順暢度。
美團(tuán)在流水線引擎的建設(shè)層面大概經(jīng)歷了幾個階段。在2019年以前,主要圍繞Jenkins進(jìn)行優(yōu)化,2019年開始正式立項打造自研的流水線引擎,大致的歷程如下:
第一階段(2014-2015):搭建Jenkins統(tǒng)一集群,解決業(yè)務(wù)接入的通用問題(如單點登錄、代碼倉庫集成、消息通知、執(zhí)行機的動態(tài)擴縮等),降低業(yè)務(wù)的建設(shè)成本。
- 第二階段(2016-2018):拆分多個Jenkins集群,解決業(yè)務(wù)增長導(dǎo)致單集群性能瓶頸。最多時有十幾個集群,這些集群通常是按業(yè)務(wù)線維度劃分,并由業(yè)務(wù)自行建設(shè)。但隨著時間的推移,集群的拆分管理難度越來越大,Jenkins安全隱患頻出,對平臺方造成了很大的運維負(fù)擔(dān)。
- 第三階段(2019-至今):為了徹底解決引擎單機瓶頸和工具重復(fù)建設(shè)問題,我們開始自研分布式流水線引擎(美團(tuán)內(nèi)部項目名稱為Pipeline),并逐步收斂各業(yè)務(wù)依賴的底層基建。
經(jīng)過3年左右的建設(shè)打磨,流水線引擎完成了服務(wù)端的基建統(tǒng)一,涵蓋到店、到家、大眾點評、美團(tuán)優(yōu)選、美團(tuán)平臺、自動配送車、基礎(chǔ)研發(fā)平臺等幾乎所有的業(yè)務(wù),支持Java、C++、NodeJS、Golang等多種語言。在性能和穩(wěn)定性方面,引擎每日支撐近十萬次的流水線執(zhí)行量(作業(yè)調(diào)度峰值每小時達(dá)上萬次),系統(tǒng)成功率保持在99.99%以上(排除業(yè)務(wù)代碼自身原因和第三方工具的問題)。
下面我們主要介紹下我們在自研引擎建設(shè)上遇到的挑戰(zhàn)以及對應(yīng)的解決方案。
二、問題及思路
1、業(yè)務(wù)介紹
1)什么是流水線
我們可以把流水線的執(zhí)行看作是對代碼一步步加工,最終交付到線上的過程。根據(jù)業(yè)務(wù)定義的順序關(guān)系,依次執(zhí)行相應(yīng)的加工或質(zhì)量校驗行為(如構(gòu)建、代碼掃描、接口測試、部署工具等),整個執(zhí)行過程類似一個有向無環(huán)圖。
圖1 流水線概念
2)基本概念
- 組件:出于代碼復(fù)用和業(yè)務(wù)共享的考慮,我們將某一工具的操作行為封裝成一個組件,表示對于一項具體的加工或校驗行為。通過組件方式,業(yè)務(wù)可以便捷地使用已集成的質(zhì)量工具(如靜態(tài)代碼掃描、安全漏洞分析等),減少在同一工具上的重復(fù)開發(fā)成本;對于不滿足需求的場景,業(yè)務(wù)可以自定義一個新的組件。
- 組件作業(yè):表示組件的一次運行實例。
- 資源:為組件作業(yè)分配的一個可執(zhí)行環(huán)境。
- 流水線編排:表示流水線中不同組件執(zhí)行的先后順序。
- 引擎:負(fù)責(zé)調(diào)度所有的組件作業(yè),為其分配相應(yīng)的執(zhí)行資源,保證流水線執(zhí)行按預(yù)期完成。
2、主要挑戰(zhàn)
1)調(diào)度效率瓶頸
對調(diào)度時間相對敏感,流水線大部分是短時作業(yè)(作業(yè)持續(xù)數(shù)十秒到分鐘不等),如果調(diào)度時間過長,業(yè)務(wù)能明顯感知到流水線執(zhí)行變慢了。我們需要保證作業(yè)調(diào)度時間在一個可控的范圍內(nèi),避免出現(xiàn)調(diào)度瓶頸。
- 從業(yè)務(wù)場景考慮,調(diào)度邏輯存在一定的業(yè)務(wù)復(fù)雜性(如組件串并行判斷、優(yōu)先級搶占、降級跳過、復(fù)用上一次結(jié)果等),不僅僅是作業(yè)與資源的匹配計算,作業(yè)調(diào)度耗時存在一定的業(yè)務(wù)開銷。
- 引擎支撐公司每天近十萬次的執(zhí)行量,峰值量情況下,并發(fā)調(diào)度的作業(yè)量大,常見的開源工具(Jenkins/GitLab CI/Tekton等)都是采用單體調(diào)度模式,作業(yè)是串行調(diào)度的,容易出現(xiàn)調(diào)度瓶頸。
2)資源分配問題
對于作業(yè)系統(tǒng)來說,作業(yè)數(shù)通常都是大于資源數(shù)的(真實部署情況,資源不是無限的),作業(yè)積壓是系統(tǒng)設(shè)計時必須考慮的問題。如何在有限的資源下,盡可能提高作業(yè)的吞吐能力,同時降低在資源不足情況時造成對核心業(yè)務(wù)場景的影響。
- 如果只依靠動態(tài)擴容,容易出現(xiàn)資源不足時無法擴容、作業(yè)排隊等待的情況。特別是對于依賴流水線做研發(fā)卡控的業(yè)務(wù),這會直接阻塞業(yè)務(wù)的上線流程。
- 出于執(zhí)行耗時的考慮,大部分資源采用預(yù)部署的方式,縮短資源申請和應(yīng)用啟動的準(zhǔn)備時間。而對于預(yù)部署的資源,如何進(jìn)行有效劃分,既保證每類資源都有一定配額,同時也避免出現(xiàn)部分資源利用率過低,影響作業(yè)整體的吞吐能力。
- 不是所有工具的執(zhí)行資源都由引擎管理(如發(fā)布系統(tǒng),部署任務(wù)的資源管理是單獨的),在作業(yè)的資源分配上,還需要考慮不同的資源管理方式。
3)工具差異化問題
公司內(nèi)不同業(yè)務(wù)的差異化大,涉及的質(zhì)效類工具眾多,如何設(shè)計一個合適的插件化架構(gòu),滿足不同工具的接入需求。
- 不同工具實現(xiàn)形式差異化大,有些工具有獨立的平臺,可以通過接口方式進(jìn)行集成,有些僅僅是一段代碼片段,還需要提供相應(yīng)的運行環(huán)境。面對不同的接入形態(tài),引擎如何屏蔽不同工具帶來的差異,使業(yè)務(wù)在編排流水線時不用關(guān)注到工具的實現(xiàn)細(xì)節(jié)。
- 隨著業(yè)務(wù)場景的不斷豐富,組件執(zhí)行還會涉及人工交互(審批場景)、支持重試、異步處理、故障恢復(fù)等能力,這些能力的擴展如何盡可能減少對系統(tǒng)的沖擊,降低實現(xiàn)的復(fù)雜度。
3、解決思路
1)拆分調(diào)度決策與資源分配,解決調(diào)度效率瓶頸
從上述分析,一個作業(yè)的實際調(diào)度耗時 = 單個作業(yè)的調(diào)度耗時 * 待調(diào)度的作業(yè)數(shù)。因為單個作業(yè)的調(diào)度耗時會受具體的業(yè)務(wù)邏輯影響,不確定性大,優(yōu)化空間有限。而串行調(diào)度問題相對明確,在作業(yè)調(diào)度時間和數(shù)量不可控的情況下,是一個合適的優(yōu)化方向。
關(guān)于串行調(diào)度,業(yè)界常見的做法是按照業(yè)務(wù)線維度拆分多個集群,分?jǐn)偪偟恼{(diào)度壓力。但這種方式存在的問題是資源分配不具備靈活性,很容易出現(xiàn)資源的分配不均,在整體資源不足時,無法從全局上考慮高優(yōu)作業(yè)的資源分配。并且,多集群管理(新增集群/拆分現(xiàn)有集群)也是不小的運維負(fù)擔(dān)。
進(jìn)一步分析,串行調(diào)度主要是為了避免資源競爭問題,獲得相對最優(yōu)的資源。這對于流水線場景(作業(yè)量大于資源量且都是短時作業(yè)),資源最優(yōu)解不是強訴求。并且,資源量的并發(fā)度相對作業(yè)量更可控,根據(jù)作業(yè)執(zhí)行快慢不同,我們通過主動拉取作業(yè)的方式,控制拉取的數(shù)量和頻率,從而有效降低了資源競爭的情況。
最終,我們在設(shè)計上采取了調(diào)度決策與資源分配分離的模式:
- 調(diào)度決策:負(fù)責(zé)計算出可以調(diào)度的作業(yè),提交決策,等待合適的資源來執(zhí)行。該模塊具體水平擴展,分擔(dān)調(diào)度決策的壓力。
- 資源分配:負(fù)責(zé)維護(hù)作業(yè)與資源的關(guān)系,通過主動拉取作業(yè)的方式,資源可以向任意的實例拉取作業(yè),取消了原先串行分配資源的單點限制。
在這種模式下,作業(yè)調(diào)度、資源分配都具備水平擴展能力,擁有更高的性能和系統(tǒng)可用性。也利于作業(yè)調(diào)度的邏輯能夠獨立演進(jìn),便于開發(fā)、測試以及灰度上線。
2)引入資源池管理模式,實現(xiàn)資源的靈活分配
考慮到不是所有資源都由引擎管理,我們引入資源池的概念來屏蔽不同資源方式的差異,每個資源池代表一類資源的集合,不同資源池的資源管理方式可以是多樣化的。通過該方式,我們將資源分配的問題簡化為作業(yè)與資源池的匹配問題,根據(jù)作業(yè)的實際情況,合理設(shè)置不同的資源池大小,并配合監(jiān)控手段對資源池進(jìn)行動態(tài)調(diào)整。
在具體措施上,我們選擇“標(biāo)簽”的方式建立作業(yè)與資源池的匹配關(guān)系,通過從作業(yè)與資源兩個維度來滿足上述條件。
- 在作業(yè)端,作業(yè)基于標(biāo)簽屬性拆分到不同的作業(yè)隊列,并引入優(yōu)先級概念,保證每個隊列中作業(yè)按優(yōu)先級高低被拉取到,避免在積壓時,高優(yōu)作業(yè)排在后面無法被及時處理,阻塞業(yè)務(wù)研發(fā)流程。
- 在資源端,結(jié)合資源的實際場景,提供三種不同的資源池管理方式,以解決不同資源類型的配額和利用率問題。
a. 預(yù)置的公共資源,這部分資源會提前在資源池上擴容出來,主要應(yīng)對業(yè)務(wù)高頻使用的且對時間敏感的組件作業(yè)。在資源配額和利用率上,根據(jù)資源池的歷史情況和實時監(jiān)控,動態(tài)調(diào)整不同資源池的大小。
b. 按需使用的資源,主要針對公共資源環(huán)境不滿足的情況,業(yè)務(wù)需要自定義資源環(huán)境,考慮到這部分作業(yè)的體量不大,直接采用實時擴容的方式,相比預(yù)置資源的方式,可以獲得更好的資源利用率。
c. 外部平臺的資源,這些資源的管理平臺方比我們更有經(jīng)驗,平臺方通過控制向引擎拉取作業(yè)的頻率和數(shù)量,自行管理作業(yè)的吞吐情況。
3)引入組件的分層設(shè)計,滿足工具差異化需求
為了保持工具接入的自由度,引擎提供了作業(yè)維度最基本的操作接口(拉取作業(yè)、查詢作業(yè)狀態(tài)、上報作業(yè)結(jié)果),不同工具可以根據(jù)作業(yè)接口形式實現(xiàn)定制化的組件開發(fā)。
組件開發(fā)主要涉及①實現(xiàn)業(yè)務(wù)邏輯和②確定交付方式兩部分工作,而與引擎的系統(tǒng)交互相對是標(biāo)準(zhǔn)的。我們根據(jù)組件執(zhí)行過程進(jìn)行分層設(shè)計,拆分出業(yè)務(wù)邏輯、系統(tǒng)交互與執(zhí)行資源三層。在向引擎屏蔽工具實現(xiàn)細(xì)節(jié)的同時,可以更好地滿足多樣化的接入場景。
- 系統(tǒng)交互層,該層相對組件開發(fā)者是透明的,根據(jù)引擎提供的接口制定統(tǒng)一的流程交互標(biāo)準(zhǔn),以向引擎屏蔽不同組件的實現(xiàn)差異。
- 執(zhí)行資源層,主要解決工具運行方式的差異化,通過支持多種組件交付形式(如鏡像、插件安裝、獨立服務(wù))滿足工具與引擎的不同集成方式。
- 業(yè)務(wù)邏輯層,針對業(yè)務(wù)不同的開發(fā)場景,采用多種適配器的選擇,來滿足業(yè)務(wù)不同的開發(fā)訴求。
三、整體架構(gòu)
圖2 流水線架構(gòu)
- 觸發(fā)器:作為流水線的觸發(fā)入口,管理多種觸發(fā)源及觸發(fā)規(guī)則(Pull Request、Git Push、API 觸發(fā)、定時觸發(fā)等)。
- 任務(wù)中心:管理流水線構(gòu)建過程中的運行實例,提供流水線運行、中止、重試、組件作業(yè)結(jié)果上報等操作。
- 決策者:對所有等待調(diào)度的作業(yè)進(jìn)行決策,并將決策結(jié)果同步給任務(wù)中心,由任務(wù)中心進(jìn)行作業(yè)狀態(tài)的變更。
- Worker:負(fù)責(zé)向任務(wù)中心拉取可執(zhí)行的作業(yè),并為作業(yè)分配具體的執(zhí)行資源。
- 組件SDK:作為執(zhí)行組件業(yè)務(wù)邏輯的殼,負(fù)責(zé)真正調(diào)起組件,完成組件初始化與狀態(tài)同步的系統(tǒng)交互。
四、核心設(shè)計點
1、作業(yè)調(diào)度設(shè)計
1)調(diào)度過程
下面,我們以一個簡單的流水線調(diào)度示例(源碼檢出 - [并行:代碼掃描,構(gòu)建] - 部署),來介紹調(diào)度設(shè)計中各模塊的協(xié)作過程。
圖3 調(diào)度過程
大致邏輯如下:
①當(dāng)觸發(fā)流水線構(gòu)建后,系統(tǒng)會在任務(wù)中心創(chuàng)建該編排所要執(zhí)行的所有組件作業(yè)。并且將作業(yè)狀態(tài)的變化以事件方式通知決策者進(jìn)行決策。
②決策者接收決策事件,根據(jù)決策算法計算出可被調(diào)度的作業(yè),向任務(wù)中心提交作業(yè)的狀態(tài)變更請求。
③任務(wù)中心接收決策請求,完成作業(yè)狀態(tài)變更(作業(yè)狀態(tài)變更為已決策),同時加入相應(yīng)的等待隊列。
④Worker 通過長輪詢方式拉取到和自己匹配的等待隊列的作業(yè),開始執(zhí)行作業(yè),執(zhí)行完成后將結(jié)果上報給任務(wù)中心。
⑤任務(wù)中心根據(jù)Worker上報的作業(yè)執(zhí)行結(jié)果變更作業(yè)狀態(tài),同時向決策者發(fā)起下一輪決策。
⑥以此反復(fù),直至流水線下所有作業(yè)都已執(zhí)行完成或出現(xiàn)作業(yè)失敗的情況,對流水線進(jìn)行最終決策,結(jié)束本次執(zhí)行。
整個過程中,任務(wù)中心作為一個分布式存儲服務(wù),統(tǒng)一維護(hù)流水線和作業(yè)的狀態(tài)信息,以API方式與其他模塊進(jìn)行交互。而決策者和Worker通過監(jiān)聽作業(yè)狀態(tài)的變化執(zhí)行相應(yīng)的邏輯。
2)作業(yè)狀態(tài)流轉(zhuǎn)
下面是一個作業(yè)完整的狀態(tài)機,我們通過作業(yè)決策、拉取、ACK以及結(jié)果上報一系列事件,最終完成作業(yè)從初始狀態(tài)向完結(jié)狀態(tài)的流轉(zhuǎn)過程。
狀態(tài)機在接收某種狀態(tài)轉(zhuǎn)移的事件(Event)后,將當(dāng)前狀態(tài)轉(zhuǎn)移至下一個狀態(tài)(Transition),并執(zhí)行相應(yīng)的轉(zhuǎn)移動作(Action)。
圖4 狀態(tài)機
在實際場景中,由于調(diào)度過程涉及鏈路長、各環(huán)節(jié)穩(wěn)定性無法完全保證,容易產(chǎn)生因異常情況導(dǎo)致狀態(tài)不流轉(zhuǎn)的情況。為此,在設(shè)計上利用數(shù)據(jù)庫保證狀態(tài)變更的正確性,同時為非完結(jié)狀態(tài)作業(yè)設(shè)立相應(yīng)的補償機制,確保任一環(huán)節(jié)異常后作業(yè)可以恢復(fù)正確流轉(zhuǎn)。
我們重點從作業(yè)決策和作業(yè)拉取這兩個關(guān)鍵過程來看狀態(tài)流轉(zhuǎn)過程可能出現(xiàn)的問題,以及在設(shè)計上是如何解決的。
①作業(yè)決策過程:任務(wù)中心接收調(diào)度作業(yè)的決策,將可調(diào)度的作業(yè)從unstart變?yōu)閜ending狀態(tài),同時將作業(yè)加入等待隊列,等待被拉取。
圖5 狀態(tài)機-決策
- 未收到?jīng)Q策事件:由于決策者服務(wù)自身的問題或網(wǎng)絡(luò)原因,導(dǎo)致決策事件的請求失敗,作業(yè)長時間處于未調(diào)度狀態(tài)。
解決方案:引入定時監(jiān)測的機制,對于無過程狀態(tài)作業(yè)且處于未完結(jié)狀態(tài)的流水線進(jìn)行重新決策,避免決策服務(wù)短時間異常導(dǎo)致決策失敗。
- 重復(fù)決策:由于網(wǎng)絡(luò)延遲、消息重試現(xiàn)象可能出現(xiàn)多個決策者同時決策同一個作業(yè),產(chǎn)生作業(yè)轉(zhuǎn)移的并發(fā)問題。
解決方案:增加pending的狀態(tài)表示作業(yè)已被決策到,并通過數(shù)據(jù)庫樂觀鎖機制進(jìn)行狀態(tài)變更,保證僅有一個決策會真正生效。
- 狀態(tài)變更過程異常:由于存在異構(gòu)數(shù)據(jù)庫,狀態(tài)變更和加入隊列可能存在數(shù)據(jù)不一致,導(dǎo)致作業(yè)無法被正常調(diào)度。
解決方案:采用最終一致性的方案,允許調(diào)度的短暫延遲。采用先變更數(shù)據(jù)庫,再加入隊列的操作順序。利用補償機制,定時監(jiān)測隊列隊首的作業(yè)信息,若pending狀態(tài)下的作業(yè)有早于隊首作業(yè)的,進(jìn)行重新入隊操作。
②作業(yè)拉取過程:任務(wù)中心根據(jù)Worker拉取作業(yè)的事件請求,從等待隊列中獲取待調(diào)度作業(yè),將作業(yè)的狀態(tài)從pending變更為scheduled,并返回給Worker。
圖6 狀態(tài)機-ACK
- 作業(yè)丟失問題:這里存在兩種情況,①作業(yè)從隊列中移除,但在狀態(tài)將要變更時異常了;②作業(yè)從隊列中移除,也正確變更了狀態(tài)。但由于poll請求連接超時,未正常返回給Worker。
解決方案:前者通過作業(yè)決策環(huán)節(jié)中對pending狀態(tài)的作業(yè)補償機制,重新加入隊列;后者對于狀態(tài)已變更的情況,已調(diào)度的作業(yè)增加ACK機制,若超時未確認(rèn),狀態(tài)會流轉(zhuǎn)回pending狀態(tài),等待被重新拉取。
- 作業(yè)被多個Worker拉?。篧orker在接收到作業(yè)后,遇到長時間的GC,導(dǎo)致狀態(tài)流轉(zhuǎn)回pending狀態(tài),在Worker恢復(fù)后,可能出現(xiàn)作業(yè)已分配到另一個Worker上。
解決方案:通過數(shù)據(jù)庫樂觀鎖機制保證僅有一個Worker更新成功,并記錄作業(yè)與Worker的關(guān)系,便于對作業(yè)進(jìn)行中止以及Worker故障后的恢復(fù)操作。
3)決策過程
決策過程是從所有未啟動的作業(yè)中篩選出可以被調(diào)度的作業(yè),通過一定的順序?qū)⑵涮峤唤o任務(wù)中心,等待被資源拉取的過程。整個篩選過程可以分為串并行順序、條件過濾、優(yōu)先級設(shè)置三部分。
圖7 決策過程
- 串并行順序:相對于DAG中復(fù)雜的尋路場景,流水線場景比較明確,是將代碼逐步加工驗證,通過開發(fā)、測試、集成、上線等一系列階段的過程。階段間是嚴(yán)格串行的,階段內(nèi)出于執(zhí)行效率的考慮,會存在串并行執(zhí)行的情況。這里通過模型設(shè)計,將DAG的調(diào)度問題轉(zhuǎn)變成作業(yè)的先后次序問題,引入run order概念,為每個組件作業(yè)設(shè)置具體的執(zhí)行次序,根據(jù)當(dāng)前已執(zhí)行作業(yè)的次序,快速篩選出下一批次序僅大于當(dāng)前的作業(yè),若并行執(zhí)行,僅需將作業(yè)的次序設(shè)置成相同即可。
圖8 串并行決策
- 條件過濾:隨著業(yè)務(wù)場景擴展,不是所有的作業(yè)都需要調(diào)度資源,進(jìn)行真正的執(zhí)行。如某類耗時的組件,在代碼和組件參數(shù)都不變的情況下,可以直接復(fù)用上一次的執(zhí)行結(jié)果,或者在系統(tǒng)層面針對某類工具異常時進(jìn)行組件跳過的降級操作。針對這類情況,在作業(yè)真正提交給任務(wù)中心之前,會增加一層條件判斷(條件分為全局設(shè)置的系統(tǒng)條件以及用戶條件),這些條件以責(zé)任鏈形式進(jìn)行依次匹配過濾,根據(jù)匹配到的條件單獨向任務(wù)中心提交決策。
- 優(yōu)先級設(shè)置:從系統(tǒng)全局考慮,在作業(yè)出現(xiàn)積壓時,業(yè)務(wù)更關(guān)心核心場景下整條流水線是否能盡早執(zhí)行完成,而不是單個作業(yè)的排隊情況。所以,在優(yōu)先級設(shè)置上除了基于時間戳的相對公平策略外,引入流水線類型的權(quán)重值(如發(fā)布流水線>自測流水線;人工觸發(fā)>定時執(zhí)行),保證核心場景流水線相關(guān)作業(yè)能夠盡早被調(diào)度到。
2、資源池劃分設(shè)計
1)整體方案
我們采用多隊列的設(shè)計,結(jié)合標(biāo)簽建立作業(yè)隊列與資源池的匹配關(guān)系,以保障不同隊列資源的有效劃分,在出現(xiàn)隊列積壓、資源池故障、無可擴資源等情況時,最大限度地降低影響范圍,避免所有作業(yè)全局排隊等待的現(xiàn)象。
圖9 資源池架構(gòu)
2)模型關(guān)系
圖10 資源池模型對象
①作業(yè)隊列與標(biāo)簽的關(guān)系:隊列與標(biāo)簽采用1對1的關(guān)系,降低業(yè)務(wù)理解和運維成本。
- 當(dāng)隊列積壓時,能快速定位到某個標(biāo)簽沒資源了。
- 標(biāo)簽資源不足時,也能快速判斷影響的具體隊列情況。
②標(biāo)簽與資源池的關(guān)系:標(biāo)簽和資源池采用多對多的關(guān)系,主要從資源整體利用率和對核心隊列的資源可用性保障考慮。
- 對于一些作業(yè)量較少的隊列,單獨分配一個資源池會造成大部分時間資源是空閑狀態(tài),資源利用率低。我們通過給資源池打多標(biāo)簽的方式,既保證了隊列有一定的資源配額,同時也能處理其他標(biāo)簽的作業(yè),提高資源的利用率。
- 對于核心場景的隊列,通常標(biāo)簽資源會分配到多個資源池上,保證資源的一定冗余,同時也降低單個資源池整體故障帶來的影響。
3)標(biāo)簽設(shè)計
標(biāo)簽的目的是建立資源(池)與作業(yè)(隊列)的匹配關(guān)系。在設(shè)計上,為便于標(biāo)簽管理和后期維護(hù),我們采用二維標(biāo)簽的形式,通過組件和流水線兩個維度,共同決定一個作業(yè)所屬標(biāo)簽及對應(yīng)的資源。
- 第一維度:組件維度,對資源做通用劃分。結(jié)合組件的業(yè)務(wù)覆蓋情況、作業(yè)執(zhí)行量、對機器和環(huán)境的特殊要求(如SSD、Dev環(huán)境等),對需要獨立資源的組件進(jìn)行打標(biāo),劃分出不同的公共資源池(每個公共資源池執(zhí)行一類或多類組件作業(yè)),在引擎層面統(tǒng)一分配,保證所有作業(yè)都有可正常運行。
- 第二維度:流水線維度,根據(jù)業(yè)務(wù)場景進(jìn)行劃分。結(jié)合業(yè)務(wù)對資源隔離/作業(yè)積壓敏感度的訴求,按需進(jìn)行劃分。有些希望資源完全獨立的業(yè)務(wù),會從所有的公共資源池進(jìn)行切分;有些僅對部分核心場景下的資源需要保障,根據(jù)鏈路上涉及的組件,選擇性地從部分公共資源池進(jìn)行劃分,實現(xiàn)業(yè)務(wù)隔離和資源利用率的平衡。
注:每個維度都會設(shè)一個other的默認(rèn)值用來兜底,用于處理無資源劃分需求的場景。
圖11 標(biāo)簽設(shè)計
4)隊列拆分設(shè)計
根據(jù)作業(yè)所屬標(biāo)簽不同拆分出多個隊列,保證每個隊列的獨立性,降低作業(yè)積壓的影響范圍。整個拆分過程可以分為入隊和出隊兩部分:
- 入隊過程:通過計算作業(yè)在組件和流水線兩個維度的屬性值,來確定作業(yè)對應(yīng)的標(biāo)簽。結(jié)合模型關(guān)系中標(biāo)簽與隊列(1對1)的關(guān)系,為每個標(biāo)簽按需創(chuàng)建一個隊列,存儲該標(biāo)簽作業(yè),不同隊列間作業(yè)做排他處理,簡化出隊的實現(xiàn)復(fù)雜度。
- 出隊過程:隊列拆分后,因為標(biāo)簽和資源池(多對多)的關(guān)系,資源池的一次作業(yè)拉取請求往往會涉及多個隊列。出于拉取效率的考慮,采用輪詢的方式依次對單隊列進(jìn)行出隊操作,直到達(dá)到該次請求的作業(yè)數(shù)上限或所有可選隊列為空時返回結(jié)果。該方式可以避免同時對多個隊列加鎖,并且在前置環(huán)節(jié)會對多標(biāo)簽進(jìn)行隨機排序,降低多個請求同時操作一個隊列的競爭概率。
圖12 隊列拉取設(shè)計
3、組件分層設(shè)計
1)分層架構(gòu)
圖13 組件架構(gòu)設(shè)計
- 業(yè)務(wù)層:引入適配層,滿足組件開發(fā)中多樣化的需求場景,同時避免上層差異污染到下層。
- 系統(tǒng)交互層:設(shè)立統(tǒng)一的流程標(biāo)準(zhǔn),保證引擎和組件交互過程的一致性,便于統(tǒng)一處理非功能性的系統(tǒng)優(yōu)化。
- 執(zhí)行資源層:提供多種資源策略,向上層屏蔽不同資源類型的差異。
2)標(biāo)準(zhǔn)的交互流程設(shè)計
在系統(tǒng)交互層,組件與引擎交互的過程中,有兩個環(huán)節(jié)是確定的,①組件作業(yè)的狀態(tài)機流轉(zhuǎn),這涉及到組件執(zhí)行的整個生命周期管理,若允許存在不同的狀態(tài)流轉(zhuǎn)關(guān)系,整個管理過程會十分混亂;②引擎對外提供的接口范圍,從服務(wù)間解耦的角度,對外提供的接口主要是組件作業(yè)維度的接口操作,不應(yīng)該耦合任何組件內(nèi)部的實現(xiàn)細(xì)節(jié)。
結(jié)合作業(yè)狀態(tài)機 + 引擎提供的接口,確定了組件執(zhí)行基本的系統(tǒng)交互流程。利用模版模式,抽象出init()、run()、queryResult()、uploadArtifacts() 等必要方法供業(yè)務(wù)實現(xiàn),整個交互流程則由系統(tǒng)統(tǒng)一處理,業(yè)務(wù)無需關(guān)心。
圖14 組件標(biāo)準(zhǔn)流程設(shè)計
3)擴展基礎(chǔ)能力
組件執(zhí)行除了正常的執(zhí)行流程外,隨著業(yè)務(wù)場景的豐富,還會涉及組件中止、回調(diào)(人工審批場景)等操作,這些操作的引入勢必會改變原先的交互流程。為了不增加額外的交互復(fù)雜度,在拉取作業(yè)環(huán)節(jié),增加作業(yè)的事件類型(運行、中止、回調(diào)等事件),Worker根據(jù)拉取到的不同事件,執(zhí)行相應(yīng)的擴展邏輯。同時,引入新的擴展也不會影響到已有的交互流程。
圖15 組件擴展能力設(shè)計
基于上述擴展,我們可能更好地將一些通用能力下沉到Daemon Thread層。如結(jié)果查詢流程,通過守護(hù)線程的方式,取消了原先同步等待的查詢限制,這對于需要異步化處理的場景(如組件作業(yè)邏輯已執(zhí)行完,僅在等待外部平臺接口返回結(jié)果)可以提前釋放資源,提高資源執(zhí)行的利用率。并且,當(dāng)執(zhí)行資源故障重啟后,結(jié)果查詢線程會自動恢復(fù)待處理異步作業(yè)。這部分能力的支持在業(yè)務(wù)層是透明的,不改變整個交互流程。
4)引入適配器
業(yè)務(wù)雖可以通過必要方法完成自定義組件,但這些方法過于基礎(chǔ),業(yè)務(wù)在一些特定場景下實現(xiàn)成本較高。如對于組件支持Shell的腳本化調(diào)用,業(yè)務(wù)其實僅需提供可執(zhí)行的Shell即可,通用約定的方式,其他必要方法的實現(xiàn)都可以交由系統(tǒng)完成。
針對業(yè)務(wù)個性化的處理,采用適配器模式,通用引入不同Command(ShellCommand、xxCommand)來默認(rèn)實現(xiàn)特定場景下的必要方法,降低業(yè)務(wù)的開發(fā)成本。同時,保持系統(tǒng)側(cè)流程的一致性,通過動態(tài)注入Command的方式,防止對業(yè)務(wù)個性化處理的耦合。
圖16 組件適配器設(shè)計
5)效果
目前已支持Shell組件、服務(wù)組件、容器組件等多種接入方式,平臺上已提供數(shù)百個組件,組件開發(fā)方涉及數(shù)十個業(yè)務(wù)線。組件庫覆蓋源碼域、構(gòu)建域、測試域、部署域、人工審批域等多個環(huán)節(jié),打通了研發(fā)過程所涉及的各個基礎(chǔ)工具。
圖17 組件庫
五、后續(xù)規(guī)劃
- 借助Serverless等云原生技術(shù),探索更輕量、高效的資源管理方案,提供更精細(xì)化的資源策略,從資源的彈性、啟動加速、環(huán)境隔離三個方面為業(yè)務(wù)提供更優(yōu)的資源托管能力。
- 面向組件開發(fā)者,提供從開發(fā)、上線到運營的一站式開發(fā)管理平臺,降低組件開發(fā)、運營成本,使更多工具方、個人開發(fā)者能參與其中,共同打造豐富多樣的業(yè)務(wù)場景,形成良性的組件運營生態(tài)。?