溫瑞林,樊 春,馬銀萍,王政丹,向廣宇 ,付振新
(1.北京大學(xué)信息科學(xué)技術(shù)學(xué)院,北京 100871;2.北京大學(xué)計(jì)算中心,北京 100871;3.北京大學(xué)國家生物醫(yī)學(xué)成像科學(xué)中心,北京 100871;4.鵬城實(shí)驗(yàn)室,廣東 深圳 518055;5.北京大學(xué)軟件與微電子學(xué)院,北京 102600)
Slurm[1]是一個(gè)在生產(chǎn)環(huán)境中廣泛使用的任務(wù)調(diào)度系統(tǒng)。Slurm的運(yùn)行效率很高,但是當(dāng)對其進(jìn)行功能擴(kuò)展時(shí),由于Slurm代碼使用的是抽象能力較低的C語言且代碼規(guī)范不太嚴(yán)謹(jǐn)?shù)仍颍琒lurm的代碼耦合度高、維護(hù)時(shí)間成本較大,添加新功能的效率也較低。因此,本文將Slurm的現(xiàn)有架構(gòu)進(jìn)行重構(gòu)、解耦,使用具備較高抽象能力的C++進(jìn)行重新實(shí)現(xiàn),從而降低新功能的開發(fā)和維護(hù)成本,并基于此提出了一個(gè)新的任務(wù)調(diào)度系統(tǒng)SlurmX。
在SlurmX的設(shè)計(jì)過程中,本文參考了同樣使用C++語言編程,并且采用面向?qū)ο蠓椒▽?shí)現(xiàn)架構(gòu)和組件設(shè)計(jì)的高性能計(jì)算系統(tǒng)HTCondor[2]的一些設(shè)計(jì)思路,同時(shí)也遵循了Slurm的原有功能和程序邏輯。
本文將先對Slurm的功能和SlurmX的需求進(jìn)行分析;再對SlurmX進(jìn)行系統(tǒng)架構(gòu)設(shè)計(jì)和具體的內(nèi)部模塊設(shè)計(jì);最后對本文進(jìn)行了總結(jié),探討了未來改進(jìn)方向。
該資源調(diào)度系統(tǒng)要面向進(jìn)行高性能計(jì)算的用戶,假定用戶具備一定程度的計(jì)算機(jī)知識(shí),可以通過遠(yuǎn)程終端使用命令行操作該軟件。因此,該資源調(diào)度系統(tǒng)除資源使用情況概覽等部分場景外,僅需提供命令行操作接口。
本文提供如下幾種命令行接口:
(1)提交單個(gè)計(jì)算任務(wù)的交互式接口。通過該接口,用戶可以實(shí)時(shí)提交計(jì)算任務(wù),指定任務(wù)相關(guān)參數(shù)和計(jì)算資源需求,并且該任務(wù)以交互方式運(yùn)行,用戶可以實(shí)時(shí)觀察到該計(jì)算任務(wù)在遠(yuǎn)程執(zhí)行節(jié)點(diǎn)上的輸出。同時(shí),用戶也可以通過該接口對計(jì)算任務(wù)隨時(shí)執(zhí)行終止操作。
(2)提交批量計(jì)算任務(wù)的非交互式接口。通過該接口,用戶可以通過編寫一個(gè)帶有多個(gè)計(jì)算任務(wù)的相關(guān)參數(shù)、計(jì)算資源需求以及各個(gè)任務(wù)之間依賴關(guān)系的配置文件,批量提交計(jì)算任務(wù)。這些計(jì)算任務(wù)以非交互方式運(yùn)行,其數(shù)據(jù)會(huì)被轉(zhuǎn)存到相應(yīng)文件,用戶可以通過查看這些文件的內(nèi)容獲取這些計(jì)算任務(wù)的輸出內(nèi)容。
(3)查詢計(jì)算任務(wù)和節(jié)點(diǎn)狀態(tài)等信息的接口。由于存在第(2)類非交互式命令行接口,因此需要有另外一個(gè)提供計(jì)算任務(wù)隊(duì)列和執(zhí)行狀態(tài)查詢的命令行接口。同時(shí),注意到除了計(jì)算任務(wù)的查詢接口之外,還有許多其他信息需要查詢,如節(jié)點(diǎn)當(dāng)前工作狀態(tài)、資源使用情況和節(jié)點(diǎn)健康狀態(tài)等。為了防止命令行接口出現(xiàn)碎片化情況,本文對這些查詢接口統(tǒng)一進(jìn)行了設(shè)計(jì)和實(shí)現(xiàn)。圖1描述了SlurmX的用例圖。
Figure 1 User case diagram of SlurmX圖1 SlurmX用例圖
由于是在集群環(huán)境下,數(shù)量龐大的服務(wù)器集群中,某些節(jié)點(diǎn)在長時(shí)間工作之后不可避免地會(huì)出現(xiàn)不可用的情況。這些不可用的情況有可能是由于該節(jié)點(diǎn)內(nèi)部的硬件出現(xiàn)物理故障導(dǎo)致的,如磁盤出現(xiàn)壞道或網(wǎng)卡故障;也有可能是由于外部環(huán)境的影響導(dǎo)致的,如外部網(wǎng)絡(luò)故障。當(dāng)系統(tǒng)部分節(jié)點(diǎn)功能失效的情況出現(xiàn)時(shí),該集群調(diào)度系統(tǒng)應(yīng)當(dāng)可以持續(xù)正常工作,并對這些故障做出妥當(dāng)?shù)奶幚?;對于現(xiàn)有的正在非故障節(jié)點(diǎn)之上運(yùn)行的計(jì)算任務(wù),不應(yīng)該出現(xiàn)任何可見的干擾;對于新提交的計(jì)算任務(wù)請求,應(yīng)當(dāng)能正確調(diào)度到非故障節(jié)點(diǎn)上運(yùn)行;對于該資源調(diào)度系統(tǒng)的管理員,應(yīng)當(dāng)以合適的方式發(fā)出通知。
同時(shí),對于臨時(shí)性故障節(jié)點(diǎn),在可恢復(fù)的情況下,為了性能效率考慮,資源調(diào)度系統(tǒng)的控制器應(yīng)當(dāng)盡量對現(xiàn)存的任務(wù)保持繼續(xù)運(yùn)行狀態(tài),而不是重新啟動(dòng)。
下面描述命令行接口之外的具體功能,該資源調(diào)度系統(tǒng)在命令行接口的后端側(cè)需提供如下功能:
(1)該資源調(diào)度系統(tǒng)需具備嚴(yán)格的資源限制能力,即假設(shè)用戶不可信,可能會(huì)嘗試資源逃逸,該資源調(diào)度系統(tǒng)提供的資源限制能力應(yīng)該可以防止這種情況的發(fā)生。
(2)該資源調(diào)度系統(tǒng)需提供多種資源調(diào)度算法,使得大量的計(jì)算任務(wù)可以被批量提交和批量調(diào)度。本文希望通過這些調(diào)度算法使集群中的各種計(jì)算資源得到高利用率。
(3)該資源調(diào)度系統(tǒng)需提供對于通用硬件資源的分配能力,如GPU、InfiniBand網(wǎng)卡和專用硬件加速器等。除了通用的CPU、內(nèi)存資源之外,本文希望該資源調(diào)度系統(tǒng)能對那些專有硬件提供支持,對用戶提供統(tǒng)一的分配接口。同時(shí),本文還希望該資源調(diào)度系統(tǒng)具備一定的可擴(kuò)展性,對于未來新的硬件,可以通過編寫附加代碼或是提供動(dòng)態(tài)鏈接庫的方式對其提供支持。
由于SlurmX所需實(shí)現(xiàn)的功能十分繁雜,本文僅抽出其中3個(gè)最重要的組件來闡明SlurmX中的架構(gòu)設(shè)計(jì):
(1)SlurmXd。該組件運(yùn)行在計(jì)算節(jié)點(diǎn)之上,負(fù)責(zé)具體計(jì)算任務(wù)的生命周期管理、任務(wù)的I/O輸出轉(zhuǎn)發(fā)及資源隔離。其中,采用Linux提供的內(nèi)核基礎(chǔ)設(shè)施cgroups實(shí)現(xiàn)資源控制;采用LibEvent框架提供的事件循環(huán)實(shí)現(xiàn)各種信號(hào)、進(jìn)程I/O的處理。
(2)SlurmCtlXd。該組件運(yùn)行在集群的控制節(jié)點(diǎn)之上,負(fù)責(zé)集群節(jié)點(diǎn)生命周期的管理、任務(wù)隊(duì)列的調(diào)度及管理、節(jié)點(diǎn)資源分配和前端SrunX發(fā)來的計(jì)算任務(wù)相關(guān)請求處理。
(3)SrunX。該組件運(yùn)行在用戶節(jié)點(diǎn)之上,負(fù)責(zé)處理用戶計(jì)算任務(wù)需求輸入,并向SlurmCtlXd節(jié)點(diǎn)進(jìn)行資源請求,轉(zhuǎn)發(fā)運(yùn)行在SlurmXd節(jié)點(diǎn)上的軟件輸出。
各個(gè)組件使用遠(yuǎn)程過程調(diào)用RPC(Remote Process Call)在自定義的通信協(xié)議上進(jìn)行通信。圖2給出了SlurmX各組件之間交互關(guān)系的可視化形式。
Figure 2 Interaction among the components of SlurmX圖2 SlurmX各組件之間交互關(guān)系
3.2.1 SrunX與SlurmCtlXd之間的通信協(xié)議及交互流程
SrunX首先接受用戶任務(wù)信息輸入。其中,任務(wù)信息包括所需要分配的CPU核數(shù)、內(nèi)存大小、可執(zhí)行路徑和命令行參數(shù)。SrunX將其所需要分配的資源信息打包之后,發(fā)送至SlurmCtlXd。SlurmCtlXd返回分配結(jié)果,如果分配成功,回復(fù)中會(huì)包含對應(yīng)的SlurmXd節(jié)點(diǎn)標(biāo)識(shí)符和所分配資源對應(yīng)的Token(唯一標(biāo)識(shí)符);如果分配失敗,會(huì)返回失敗原因。
3.2.2 SrunX與SlurmXd之間的通信協(xié)議及交互流程
當(dāng)SrunX向SlurmCtlXd請求分配資源成功,取得所分配資源的Token后,SrunX會(huì)根據(jù)SlurmXd節(jié)點(diǎn)標(biāo)識(shí)符連接對應(yīng)的SlurmXd節(jié)點(diǎn),發(fā)送任務(wù)信息和所分配資源的Token。
SlurmXd在收到任務(wù)信息并檢驗(yàn)資源Token的合法性之后,開始嘗試運(yùn)行任務(wù)。如果運(yùn)行失敗,SlurmXd向SrunX返回任務(wù)失敗原因,SrunX向用戶顯示相關(guān)信息,結(jié)束運(yùn)行,同時(shí)SlurmXd也結(jié)束該任務(wù)的處理流程;如果任務(wù)運(yùn)行成功,SlurmXd開始持續(xù)向SrunX轉(zhuǎn)發(fā)任務(wù)的輸出信息,直到任務(wù)結(jié)束。當(dāng)任務(wù)運(yùn)行結(jié)束之后,SlurmXd會(huì)收集任務(wù)的結(jié)束信息,將其返回給SrunX,SrunX在接收到任務(wù)結(jié)束信息后,向用戶顯示相關(guān)信息,結(jié)束運(yùn)行。
由于轉(zhuǎn)發(fā)所運(yùn)行任務(wù)的輸出信息需要流式協(xié)議,因此在SrunX側(cè)和SlurmXd側(cè)都需要一個(gè)實(shí)現(xiàn)上述功能的狀態(tài)機(jī)。
3.2.3 SlurmXd與SlurmCtlXd之間的通信協(xié)議及交互流程
SlurmCtlXd在確認(rèn)SrunX請求的資源可分配之后,選擇指定的SlurmXd節(jié)點(diǎn)分配資源并生成資源Token后,將該Token下發(fā)至SlurmXd節(jié)點(diǎn)。
由于SlurmXd承擔(dān)了讓該資源Token失效的責(zé)任,因此SlurmXd需要監(jiān)控任務(wù)狀態(tài)。當(dāng)任務(wù)因?yàn)槿魏卧虿辉龠\(yùn)行后,SlurmXd需要通知SlurmCtlXd該Token已經(jīng)失效。
同時(shí),SlurmXd在啟動(dòng)之后,需要主動(dòng)向SlurmCtlXd發(fā)送注冊請求,注冊請求中包含SlurmXd節(jié)點(diǎn)的可分配資源等信息。SlurmCtlXd在收到來自SlurmXd的注冊請求之后,會(huì)嘗試與SlurmXd建立連接,并通過此鏈接的連接狀態(tài)檢測SlurmXd節(jié)點(diǎn)的健康狀態(tài)。
本節(jié)主要介紹SlurmXd和SlurmCtlXd組件內(nèi)部的詳細(xì)設(shè)計(jì)。由于SrunX本質(zhì)上是一個(gè)簡單的客戶端,故其內(nèi)部設(shè)計(jì)略過不表。
4.1.1 內(nèi)部各組件概覽
SlurmCtlXd內(nèi)部包含以下組件:
(1)XdNodeKeeper。該組件統(tǒng)一管理所有SlurmXd計(jì)算節(jié)點(diǎn)的連接情況。
(2)XdNodeMetaContainer。該組件統(tǒng)一維護(hù)所有SlurmXd計(jì)算節(jié)點(diǎn)的元數(shù)據(jù)信息(如任務(wù)列表、資源使用情況等),由于此類信息有著很高的查詢和更新頻率,在大規(guī)模任務(wù)調(diào)度時(shí),會(huì)由于強(qiáng)競爭導(dǎo)致運(yùn)行效率下降,因此本文專門抽象了一個(gè)類,對其進(jìn)行高并發(fā)和查詢方面的優(yōu)化。
(3)gRPC Server。在目前的實(shí)現(xiàn)中,本文采用了Google的gRPC[3]框架作為底層的通信框架,同時(shí)為了避免與gRPC框架過高的耦合度,本文實(shí)現(xiàn)了一個(gè)gRPC Server類,將gRPC框架細(xì)節(jié)封裝在該類中。
(4)GarbageCollector。由于C++不是一門帶有Garbage Collection(無效內(nèi)存資源回收)的語言,因此需要專門的類來處理運(yùn)行過程中臨時(shí)對象的釋放。這個(gè)類中含有一個(gè)固定線程,通過多線程并發(fā)回收的方式降低延遲。
SlurmCtlXd各組件之間的協(xié)作情況如圖3所示,XdNodeKeeper通過暴露的外部接口,在SlurmXd節(jié)點(diǎn)狀態(tài)更新時(shí),通知XdNodeMetaContainer修改節(jié)點(diǎn)數(shù)據(jù),gRPC Server代理了所有外部請求的處理,對于增加新SlurmXd節(jié)點(diǎn)的請求,gRPC Server將其轉(zhuǎn)發(fā)至XdNodeKeeper;對于其他請求中需要查詢SlurmXd節(jié)點(diǎn)數(shù)據(jù)的行為,則通過XdNodeMetaContainer查詢。XdNodeMetaContainer和XdNodeKeeper都通過GarbageCollector進(jìn)行垃圾回收。
Figure 3 Component diagram of SlurmCtlXd圖3 SlurmCtlXd內(nèi)部組件圖
4.1.2 XdNodeKeeper
作為資源調(diào)度系統(tǒng)的中心控制器,SlurmCtlXd最重要的功能之一便是監(jiān)控作為計(jì)算節(jié)點(diǎn)的SlurmXd節(jié)點(diǎn)的健康狀態(tài)。在SlurmCtlXd接收到SlurmXd節(jié)點(diǎn)注冊請求的那一刻開始,SlurmCtlXd便需要向作為通信底層的gRPC框架注冊狀態(tài)監(jiān)聽信息,通過底層的gRPC Channel的狀態(tài),監(jiān)測從發(fā)起鏈接到節(jié)點(diǎn)停止服務(wù)的SlurmXd全生命周期的狀態(tài)。
此部分實(shí)現(xiàn)繁雜,但是其邏輯上只包含了檢測SlurmX節(jié)點(diǎn)狀態(tài)的功能,因此本文參考HTCondor中的Master-Worker[4]模型,結(jié)合gRPC做出了如下設(shè)計(jì):
SlurmCtlXd作為中心控制器,其關(guān)心的信息只有節(jié)點(diǎn)是否存活,即ALIVE和DEAD這2種狀態(tài)。但是,由于節(jié)點(diǎn)有可能由于網(wǎng)絡(luò)波動(dòng)出現(xiàn)臨時(shí)性的鏈接中斷,考慮到節(jié)點(diǎn)注冊的高昂開銷,便引入了另外一個(gè)狀態(tài)TRANSIENT_FAILURE描述此種臨時(shí)性的鏈接中斷。
由于SlurmXd節(jié)點(diǎn)可能數(shù)量眾多(一般以百計(jì)算),本文需要對每一個(gè)SlurmXd節(jié)點(diǎn)進(jìn)行狀態(tài)監(jiān)控,但又不希望為每一個(gè)節(jié)點(diǎn)都分配一個(gè)線程來監(jiān)聽引起狀態(tài)機(jī)變化的事件,這樣不僅會(huì)引入額外的同步開銷,而且不同線程之間同步代碼邏輯的編寫也要花費(fèi)大量的時(shí)間。因此,本文采用了gRPC Channel提供的異步事件狀態(tài)監(jiān)聽API:NotifyOnStateChange,從而可以在單線程中完成此項(xiàng)工作。NotifyOnStateChange通過一個(gè)用戶提供名稱為data的可修改參數(shù)標(biāo)記所監(jiān)聽的事件,在該資源調(diào)度系統(tǒng)的場景中,使用針對SlurmXd節(jié)點(diǎn)的狀態(tài)設(shè)計(jì)狀態(tài)機(jī)模型來對SlurmXd節(jié)點(diǎn)的狀態(tài)變化進(jìn)行抽象,并在這個(gè)API中使用一個(gè)名稱為tag的變量來標(biāo)記每個(gè)SlurmXd節(jié)點(diǎn)對應(yīng)的狀態(tài)機(jī)。通過這個(gè)API,本文在一個(gè)監(jiān)聽異步事件的線程中,通過所觸發(fā)事件對應(yīng)的data參數(shù)來區(qū)分SlurmXd節(jié)點(diǎn)的身份。為了使代碼書寫具有良好的模塊性,根據(jù)本文場景,將SlurmXd狀態(tài)機(jī)分為2種類型,一種是正在建立連接的SlurmXd節(jié)點(diǎn),本文將此類節(jié)點(diǎn)賦予一個(gè)類型tag:InitXd;另一種是已經(jīng)建立連接的SlurmXd節(jié)點(diǎn),本文將此類節(jié)點(diǎn)賦予一個(gè)類型tag:EstabXd。同時(shí),還需要另外一個(gè)數(shù)據(jù)結(jié)構(gòu),用于記錄已經(jīng)建立連接的EstabXd節(jié)點(diǎn)的編號(hào)以及當(dāng)其連接臨時(shí)性失敗后進(jìn)入TRANSIENT_FAILURE狀態(tài)時(shí)的重試次數(shù)等信息。因此,本文向NotifyOnStateChange提供的data參數(shù)由2部分組成,第1部分是類型tag,第2部分是保存編號(hào)和重試次數(shù)等信息的數(shù)據(jù)結(jié)構(gòu)。
gRPC中用來對連接進(jìn)行抽象的組件Channel的狀態(tài)(如圖4所示)有為IDLE(等待連接)、CONNECTING(正在連接中)、READY(連接已建立,可以在這個(gè)Channel上發(fā)送RPC請求)和TRANSIENT_FAILURE(連接中斷,臨時(shí)性失敗)4個(gè)狀態(tài),但這并不符合上述3個(gè)狀態(tài)的需求。所以,本文需要針對上述3個(gè)狀態(tài),編寫底層的狀態(tài)機(jī)處理邏輯,利用回調(diào)函數(shù)的形式轉(zhuǎn)換成上層需要的SlurmXd的狀態(tài)機(jī)。
Figure 4 State machine in XdNodeKeeper圖4 XdNodeKeeper狀態(tài)機(jī)
因此,本文針對gRPC Channel狀態(tài)變化,設(shè)計(jì)了圖4所示的狀態(tài)機(jī),用來將該gRPC Channel的狀態(tài)機(jī)映射到SlurmXd節(jié)點(diǎn)的狀態(tài)機(jī),使用4個(gè)回調(diào)函數(shù)NodeIsUp()、NodeRecovered()、NodeIsTempDown()和NodeIsDown()來驅(qū)動(dòng)SlurmXd節(jié)點(diǎn)狀態(tài)機(jī)的狀態(tài)變化。在這4個(gè)回調(diào)函數(shù)被調(diào)用時(shí),會(huì)傳遞包括該SlurmXd節(jié)點(diǎn)編號(hào)在內(nèi)的相關(guān)信息給外部XdNodeKeeper的使用者,使其可以區(qū)分該狀態(tài)變化來自哪一個(gè)SlurmXd節(jié)點(diǎn)。
本文先給出該狀態(tài)機(jī)中各個(gè)圖形的含義。在圖5中描述了狀態(tài)及狀態(tài)轉(zhuǎn)移:狀態(tài)機(jī)當(dāng)前在狀態(tài)B,前一個(gè)狀態(tài)是A,由A狀態(tài)到B狀態(tài)的狀態(tài)遷移是由Cond事件被觸發(fā)導(dǎo)致的,該狀態(tài)機(jī)現(xiàn)在執(zhí)行Action動(dòng)作。
Figure 5 Notation of the state diagram圖5 狀態(tài)機(jī)中圖形的表示方法
在描述了狀態(tài)機(jī)的圖形含義后,再來闡述該SlurmXd狀態(tài)機(jī)具體的實(shí)現(xiàn)細(xì)節(jié)。當(dāng)XdNodeKeeper收到SlurmXd節(jié)點(diǎn)的注冊請求之后,XdNodeKeeper為該SlurmXd節(jié)點(diǎn)新建一個(gè)狀態(tài)機(jī)。
本文以圖4所示的左上角的實(shí)心黑圓作為起始狀態(tài),其中,在向NotifyOnStateChange提供的data數(shù)據(jù)中,將Tag類型設(shè)置為InitXd,重試次數(shù)retry_count置為0,然后進(jìn)入IDLE狀態(tài)。進(jìn)入IDLE狀態(tài)后,嘗試向SlurmXd節(jié)點(diǎn)發(fā)起連接,此時(shí)gRPC Channel進(jìn)入CONNECTING狀態(tài)。若SlurmXd節(jié)點(diǎn)因?yàn)槎丝趦H單向開放等原因連接失敗,則該狀態(tài)機(jī)進(jìn)入TRANSIENT_FAILURE狀態(tài),此時(shí)SlurmXd狀態(tài)機(jī)將retry_count加1,并嘗試重連,再次進(jìn)入CONNECTING狀態(tài),如果重復(fù)連接N次(N為用戶設(shè)置的次數(shù),默認(rèn)為3)還是失敗,則狀態(tài)機(jī)進(jìn)入結(jié)束狀態(tài),由于此時(shí)Tag類型為InitXd,向注冊請求返回失敗信息,并釋放該狀態(tài)機(jī)相關(guān)資源。若SlurmXd節(jié)點(diǎn)連接成功,則該SlurmXd進(jìn)入READY狀態(tài),此時(shí)為該SlurmXd節(jié)點(diǎn)分配節(jié)點(diǎn)編號(hào);將其Tag類型設(shè)置為EstabXd,表示該節(jié)點(diǎn)已建立連接;通知該SlurmXd節(jié)點(diǎn)的注冊者該節(jié)點(diǎn)已經(jīng)成功注冊,建立連接;通過NodeIsUp()回調(diào)函數(shù),向外部的XdNodeKeeper使用者(主要是下文的XdNodeMetaContainer)通知一個(gè)新的SlurmXd節(jié)點(diǎn)已經(jīng)成功連接;將retry_count重新設(shè)置為0。
由上文論述可知,一個(gè)Tag類型為EstabXd的SlurmXd節(jié)點(diǎn)(即已建立連接節(jié)點(diǎn))的起始狀態(tài)實(shí)際上是READY狀態(tài)。EstabXd狀態(tài)下的節(jié)點(diǎn)故障的處理邏輯和InitXd狀態(tài)下的處理邏輯類似,僅僅在回調(diào)通知函數(shù)上出現(xiàn)了一些變化,這是為了方便上層應(yīng)用做更詳細(xì)的處理工作。
通過對gRPC Channel的狀態(tài)進(jìn)行有針對性的狀態(tài)機(jī)設(shè)計(jì),XdNodeKeeper將底層使用gRPC進(jìn)行RPC調(diào)用和節(jié)點(diǎn)連接狀態(tài)檢測的繁雜實(shí)現(xiàn)細(xì)節(jié)成功封裝。如圖6所示,XdNodeKeeper對外只暴露了用來請求注冊新的SlurmXd節(jié)點(diǎn)的RegisterXdNode接口和設(shè)置前文所述的4個(gè)回調(diào)函數(shù)的接口,并通過這4個(gè)回調(diào)函數(shù)對外匯報(bào)節(jié)點(diǎn)變化狀態(tài)。通過極簡的接口暴露,XdNodeKeeper成功實(shí)現(xiàn)了與SlurmCtlXd其他組件的解耦,避免了在后期底層gRPC通信代碼發(fā)生更改時(shí)造成其他組件大規(guī)模的代碼改動(dòng)。
Figure 6 Relationship between XdNodeKeeper and gRPC圖6 XdNodeKeeper與gRPC的關(guān)系
4.1.3 XdNodeMetaContainer
SlurmCtlXd中需要記錄所有SlurmXd節(jié)點(diǎn)的信息,這些信息包括:當(dāng)前節(jié)點(diǎn)狀態(tài)、用來發(fā)起SlurmXd的RPC調(diào)用的gRPC stub以及該SlurmXd所有的資源信息、分配情況和該SlurmXd上所執(zhí)行的所有任務(wù)信息。
這些信息面臨著大量的查詢和修改,因此,本文抽象出一個(gè)類,用于統(tǒng)一維護(hù)和保護(hù)這些信息。在SlurmCtlXd中,當(dāng)SlurmXd節(jié)點(diǎn)狀態(tài)發(fā)生變化時(shí),希望可以在最短的時(shí)間內(nèi)對該節(jié)點(diǎn)狀態(tài)進(jìn)行更新,使新舊信息更新的窗口期盡量縮短,后續(xù)到來的任務(wù)請求可以最快地按照更新后的節(jié)點(diǎn)狀態(tài)進(jìn)行資源分配。
但是,由于節(jié)點(diǎn)信息繁雜,對這些節(jié)點(diǎn)信息進(jìn)行更改的時(shí)間代價(jià)較大,如果按照傳統(tǒng)的讀寫鎖算法,無論該讀寫鎖是否選擇讀寫者公平實(shí)現(xiàn),當(dāng)寫者取得讀寫鎖的獨(dú)占權(quán)后,所有讀者都必須等待寫者完成后才可以開始臨界區(qū)數(shù)據(jù)的讀取,這種高昂的開銷在一個(gè)高并發(fā)系統(tǒng)上是不可接受的。因此,本文需要確保SlurmXd節(jié)點(diǎn)數(shù)據(jù)的更新不會(huì)中斷大量的節(jié)點(diǎn)數(shù)據(jù)查詢。
同時(shí),當(dāng)任務(wù)數(shù)量上升至萬級別后,這些任務(wù)的事件處理會(huì)帶來短時(shí)間內(nèi)的大量的以萬甚至十萬計(jì)的SlurmXd節(jié)點(diǎn)數(shù)據(jù)查詢,考慮到現(xiàn)代CPU在Cache Line需要進(jìn)行同步時(shí)時(shí)延差不多在100 ns左右[5],十萬級別的對同一互斥鎖的競爭就會(huì)產(chǎn)生秒級的延遲,因此本文要保證大量并發(fā)讀安全的同時(shí)降低競爭開銷。常用的讀寫鎖往往采用2個(gè)互斥鎖加上幾個(gè)條件變量實(shí)現(xiàn),其開銷在臨界區(qū)小于毫秒級的時(shí)候,開銷并不比互斥鎖低,因此使用讀寫鎖在此場景下不但不會(huì)減小開銷,反而會(huì)增大開銷。
總結(jié)上述需求,本文用來維護(hù)SlurmXd節(jié)點(diǎn)相關(guān)數(shù)據(jù)的組件XdNodeMetaContainer需要滿足如下條件:
(1)在SlurmXd節(jié)點(diǎn)發(fā)生事件更新時(shí),該事件后續(xù)對于節(jié)點(diǎn)數(shù)據(jù)的查詢能以最快的速度看到更新,不會(huì)被SlurmXd節(jié)點(diǎn)數(shù)據(jù)的更新阻塞。
(2)高度并行化的節(jié)點(diǎn)數(shù)據(jù)讀取不會(huì)導(dǎo)致嚴(yán)重的同步性能開銷。
經(jīng)過對一些現(xiàn)有成熟技術(shù)的考察,如bRPC中的DoublyBufferedData[6],在XdNodeMetaContainer中,采用了雙緩沖加Thread-Local互斥量的方法。在該組件中,將SlurmXd的節(jié)點(diǎn)數(shù)據(jù)進(jìn)行“雙拷貝”,分為前臺(tái)數(shù)據(jù)和后臺(tái)數(shù)據(jù)。當(dāng)該組件進(jìn)行初始化時(shí),前臺(tái)數(shù)據(jù)和后臺(tái)數(shù)據(jù)都為空。當(dāng)一個(gè)更新請求到來時(shí),該組件先更新其后臺(tái)數(shù)據(jù)。此時(shí),前臺(tái)數(shù)據(jù)仍可以被并發(fā)的查詢請求正常訪問,不會(huì)被后臺(tái)的數(shù)據(jù)更新阻塞,即和后臺(tái)的數(shù)據(jù)更新相互獨(dú)立。
同時(shí),該組件會(huì)為所有產(chǎn)生數(shù)據(jù)讀取請求的進(jìn)程創(chuàng)建一個(gè)Thread-Local(線程本地的)互斥量,而前臺(tái)的所有針對該數(shù)據(jù)結(jié)構(gòu)的讀取請求都會(huì)對其所在線程的線程本地互斥量進(jìn)行加鎖。由于該互斥量位于線程本地存儲(chǔ)空間內(nèi)部,所以對該互斥量加鎖并不會(huì)產(chǎn)生任何競爭。對于一個(gè)不存在競爭,即不需要進(jìn)行CPU的不同核心間Cache Line同步的互斥量,其開銷在50個(gè)CPU 指令周期左右,即17 ns左右[5],因此,當(dāng)所有查詢線程共用一個(gè)互斥量時(shí),在高強(qiáng)度并發(fā)訪問條件下,每次加鎖有100 ns左右的開銷[5],采用每線程一個(gè)線程本地互斥量的方法,有效地降低了時(shí)延,提高了性能。
在后臺(tái)數(shù)據(jù)更新完畢之后,該組件使用C++的atomic庫封裝的CPU原子操作對前后臺(tái)數(shù)據(jù)進(jìn)行轉(zhuǎn)換。在該原子操作后,前臺(tái)數(shù)據(jù)為更新后的SlurmXd節(jié)點(diǎn)數(shù)據(jù),后臺(tái)數(shù)據(jù)為還未更新的正在被現(xiàn)有針對該數(shù)據(jù)結(jié)構(gòu)進(jìn)行查詢的請求所使用的舊SlurmXd節(jié)點(diǎn)數(shù)據(jù)。因此,所有除現(xiàn)有正在進(jìn)行的對該數(shù)據(jù)結(jié)構(gòu)進(jìn)行的查詢之外的后續(xù)新請求都會(huì)看到更新后的數(shù)據(jù)。唯一會(huì)產(chǎn)生阻塞等待的步驟,是在對后臺(tái)還未更新的舊數(shù)據(jù)進(jìn)行更新之前,需要等待還在使用舊數(shù)據(jù)的所有請求完成。當(dāng)所有使用舊數(shù)據(jù)的請求都結(jié)束之后,本文在此時(shí)對后臺(tái)的數(shù)據(jù)副本進(jìn)行更新操作。更新完成后,前臺(tái)數(shù)據(jù)和后臺(tái)數(shù)據(jù)即完成了最終的統(tǒng)一,此時(shí),一個(gè)對于該數(shù)據(jù)結(jié)構(gòu)的修改操作才算完成。
由于讀取操作不會(huì)產(chǎn)生互斥鎖的競爭,因此該數(shù)據(jù)結(jié)構(gòu)在對于讀取操作的多線程競爭保護(hù)上所花費(fèi)的開銷可以不計(jì)。對于修改操作,由于對后臺(tái)數(shù)據(jù)可以立即進(jìn)行修改,并且在修改之后后臺(tái)數(shù)據(jù)立刻與前臺(tái)數(shù)據(jù)互換,因此,修改操作對于所有并發(fā)的查詢讀取請求的同步要求只在前后臺(tái)數(shù)據(jù)切換后,需要所有進(jìn)行新的讀取請求的CPU核心的Cache Line對該切換后的數(shù)據(jù)進(jìn)行同步等待操作,無需中斷還在對未更新的新后臺(tái)數(shù)據(jù)進(jìn)行查詢的請求或者是長時(shí)間等待。該數(shù)據(jù)結(jié)構(gòu)設(shè)計(jì)的本質(zhì)是根據(jù)該數(shù)據(jù)結(jié)構(gòu)讀請求極多和寫請求極少的特點(diǎn),通過犧牲對數(shù)據(jù)結(jié)構(gòu)修改的性能,來有效提升對高并發(fā)讀取請求的性能。
4.1.4 GarbageCollector
從前述的2個(gè)組件XdNodeKeeper和XdNodeMetaContainer可以看到,這2個(gè)組件需要負(fù)責(zé)對SlurmXd狀態(tài)機(jī)和SlurmXd節(jié)點(diǎn)相關(guān)信息的分配和清理。為了保障用來記錄這些信息的內(nèi)存不會(huì)發(fā)生泄漏,在該資源調(diào)度系統(tǒng)的實(shí)現(xiàn)中,采用了C++標(biāo)準(zhǔn)庫中的共享指針shared_ptr[7]來對這些分配的資源進(jìn)行引用計(jì)數(shù)。當(dāng)XdNodeKeeper、XdNodeMetaContainer和所有使用這些資源的請求都對某個(gè)資源進(jìn)行了共享指針釋放之后,該資源的引用計(jì)數(shù)將下降為0,此時(shí)將釋放這些資源。但是,這些資源包含的信息十分繁雜,對于這些資源的分配和釋放清理都需要一定的時(shí)間開銷??紤]到所有SlurmXd節(jié)點(diǎn)的狀態(tài)機(jī)都在XdNodeKeeper的一個(gè)專有線程中進(jìn)行維護(hù),因此,本文不再處理該資源分配和釋放的工作,否則會(huì)影響到SlurmXd節(jié)點(diǎn)狀態(tài)信息更新的時(shí)效性。
通過GarbageCollector將所有資源清理的開銷全部從時(shí)延敏感的執(zhí)行路徑中轉(zhuǎn)移了,這樣提高了整體的響應(yīng)性能和任務(wù)吞吐量。
本節(jié)主要介紹SlurmXd中資源控制組件CgroupManager、任務(wù)管理組件TaskManager及設(shè)備抽象類Devices和設(shè)備資源分配器DevicesAllocator的實(shí)現(xiàn)。
4.2.1 CgroupManager
除了調(diào)度任務(wù)的執(zhí)行順序,資源調(diào)度系統(tǒng)最重要的一個(gè)作用是限制計(jì)算任務(wù)資源的使用,即任務(wù)使用的資源不能超過某一個(gè)系統(tǒng)或用戶限定的數(shù)值。
Linux 2.6.24引入了一個(gè)很重要的特性cgroups(control groups)[8]。cgroups是Linux內(nèi)核中的一個(gè)組件,通過該組件可以從操作系統(tǒng)的層面對應(yīng)用施加資源使用限制。
Linux的cgroups有2個(gè)版本:v1和v2。CgroupManager目前采用v1版本實(shí)現(xiàn),v1版本的cgroups的高層結(jié)構(gòu)如圖7所示。
Figure 7 Structure of cgroups圖7 cgroups架構(gòu)
cgroups為系統(tǒng)中許多可控制的資源,如CPU、Memory,提供控制器,控制器還可以派生出多個(gè)子控制器(在圖7中為不同的cgroups hierarchy)。在Linux中,每個(gè)進(jìn)程若是使用cgroups,在進(jìn)程控制塊中有一個(gè)數(shù)據(jù)結(jié)構(gòu)css_set[9]記錄該進(jìn)程服從于哪些子系統(tǒng)控制器的管理。其中,多個(gè)進(jìn)程可以和相同的css_set綁定,從而達(dá)成管理一個(gè)進(jìn)程組的目的。
由于cgroups具備很高的配置靈活性,本文需要自己制定一些統(tǒng)一規(guī)則,將底層靈活的組件配置固化成一套可供上層組件便捷使用的抽象接口,以方便高層組件實(shí)現(xiàn)各種邏輯。
Figure 8 Abstraction model of cgroups圖8 cgroups的抽象模型
因此,本文對于CgroupManager制定了如下的規(guī)則和限制:CgroupManager組件在初始化時(shí)會(huì)在所有指定需要使用的控制器系統(tǒng)(如CPU、Memory)的對應(yīng)根控制器下創(chuàng)建一個(gè)CgroupManager使用的專有層級,同時(shí)約定在所有子系統(tǒng)的該專有層級下子目錄結(jié)構(gòu)是相同的。因此,本文實(shí)際上利用cgroups抽象出了一個(gè)具備不同層級結(jié)構(gòu),但是每個(gè)層級控制器相同的一個(gè)控制器系統(tǒng)層級結(jié)構(gòu)。本文將這個(gè)抽象系統(tǒng)的根層級命名為CgroupManager Root Hierarchy??紤]該資源調(diào)度系統(tǒng)的實(shí)際場景,每個(gè)用戶會(huì)提交一個(gè)計(jì)算任務(wù),同時(shí)用戶或者系統(tǒng)會(huì)對該任務(wù)施加資源限制條件。注意到這些計(jì)算任務(wù)之間實(shí)際為平級關(guān)系,因此,CgroupManager Root Hierarchy下只會(huì)具備多個(gè)平級的子層級,即不會(huì)具備子子層級,本文將這些子層級的單個(gè)實(shí)體命名為CgroupManager Leaf Hierarchy。
由上文所述可知,CgroupManager Leaf Hierarchy實(shí)際上是單個(gè)計(jì)算任務(wù)通過cgroups實(shí)現(xiàn)的一系列資源限制。圖8給出了cgroups的抽象模型。
cgroups不僅提供了操作系統(tǒng)內(nèi)核層面對系統(tǒng)資源的使用限制,也提供了對資源使用情況的查詢能力。由于監(jiān)控任務(wù)的各種資源使用情況也是該資源調(diào)度系統(tǒng)的重要功能,CgroupManager將cgroups底層資源的使用情況查詢接口進(jìn)行了一定程度的封裝,對外提供了對資源的查詢功能。圖9描述了CgroupManager的類設(shè)計(jì)。通過該類,可以自由地對任務(wù)所使用的資源進(jìn)行限制,例如將CPU限制在3個(gè)核心或是在任務(wù)所使用內(nèi)存資源超過限制時(shí)由Linux的OOM Killer[10]將其終止。
Figure 9 CgroupManager class圖9 CgroupManager類
4.2.2 TaskManager
TaskManager是SlurmXd中承擔(dān)任務(wù)運(yùn)行、任務(wù)管理和任務(wù)I/O重定向等功能的組件,是SlurmXd的核心組件。下面主要介紹該組件的設(shè)計(jì)和原理:
從上文的描述可以看出,TaskManager在運(yùn)行時(shí)需要處理許多事件。這些事件包括但不限于:
(1)添加新任務(wù)的請求。
(2)轉(zhuǎn)發(fā)來自前端SrunX的中斷信號(hào)或輸入到正在SlurmXd節(jié)點(diǎn)上運(yùn)行的任務(wù)中。
(3)將正在運(yùn)行的任務(wù)產(chǎn)生的輸出轉(zhuǎn)發(fā)到前端SrunX。
(4)處理來自SlurmXd所在節(jié)點(diǎn)的結(jié)束信號(hào)SIGINT或SIGRTERM以及負(fù)責(zé)計(jì)算任務(wù)結(jié)束時(shí)的回收工作。
TaskManager在處理大量事件的同時(shí),還需特別注意保護(hù)在TaskManager中維護(hù)的所有任務(wù)信息在可能的多線程執(zhí)行環(huán)境中不會(huì)因?yàn)椴l(fā)修改和訪問導(dǎo)致數(shù)據(jù)損毀,這就要求本文通過互斥量對相關(guān)的數(shù)據(jù)結(jié)構(gòu)進(jìn)行保護(hù)。
總結(jié)上文,有2個(gè)需求:一是可以便捷地處理大量事件;二是在處理大量事件的同時(shí)要注意對相關(guān)數(shù)據(jù)結(jié)構(gòu)的并發(fā)保護(hù)。采用事件驅(qū)動(dòng)模型可以處理上述需求。在事件驅(qū)動(dòng)模型中,本文先將要監(jiān)聽的事件添加到一個(gè)監(jiān)聽列表,通過啟動(dòng)一個(gè)專有線程,不斷地對這些事件進(jìn)行輪詢,檢查事件是否已經(jīng)發(fā)生,如果某個(gè)事件發(fā)生,則執(zhí)行該事件對應(yīng)的處理邏輯。同時(shí),如果在某次輪詢中,多個(gè)事件同時(shí)發(fā)生,則在該線程中順序執(zhí)行這些事件的處理邏輯。采用事件驅(qū)動(dòng)模型的一個(gè)好處是,由于所有事件的處理邏輯實(shí)際上是串行執(zhí)行的,因此不會(huì)存在多線程執(zhí)行場景下需要實(shí)現(xiàn)很復(fù)雜同步邏輯的弊端。事件驅(qū)動(dòng)模型的主要弊端在于,只有一個(gè)工作線程卻需要處理所有事件邏輯。針對本文需求,使用了LibEvent庫作為底層的事件通知庫。圖10描述了LibEvent的內(nèi)部線程模型。
Figure 10 Internal threading model of LibEvent圖10 LibEvent內(nèi)部線程模型
明確了所使用的執(zhí)行模型后,接下來介紹TaskManager的具體執(zhí)行流程。在TaskManager初始化時(shí),先利用LibEvent提供的注冊事件API注冊如下所述的事件及其對應(yīng)的處理邏輯:
(1)SIGINT信號(hào)事件。該信號(hào)由用戶輸入,當(dāng)用戶在鍵盤上按下Ctrl+C或由其他軟件發(fā)送該信號(hào)時(shí),表明用戶希望SlurmXd關(guān)閉。此時(shí)將不再接收任何新的任務(wù)請求,并向所有正在執(zhí)行的任務(wù)下屬的進(jìn)程組發(fā)送SIGINT信號(hào),等待所有任務(wù)結(jié)束回收并執(zhí)行相應(yīng)的清理工作之后再退出。
(2)SIGCHLD信號(hào)事件。當(dāng)一個(gè)計(jì)算任務(wù)結(jié)束之后,由于SlurmXd進(jìn)程為計(jì)算任務(wù)中實(shí)際執(zhí)行進(jìn)程的父進(jìn)程,SlurmXd會(huì)收到一個(gè)SIGCHLD信號(hào),表明有計(jì)算任務(wù)已經(jīng)結(jié)束。SlurmXd收到信號(hào)之后會(huì)解析程序的返回值并統(tǒng)計(jì)程序相關(guān)信息后將其統(tǒng)一返回給用戶前端執(zhí)行程序SrunX,并由SrunX顯示這些信息,表明程序已經(jīng)結(jié)束。
(3)創(chuàng)建新任務(wù)請求事件。當(dāng)用戶通過SrunX發(fā)送任務(wù)信息和資源Token時(shí),gRPC框架中對應(yīng)的RPC請求處理邏輯會(huì)將該請求封裝成一個(gè)包含任務(wù)信息、輸出回調(diào)函數(shù)、任務(wù)結(jié)束回調(diào)函數(shù)和異步通知類的新任務(wù)請求結(jié)構(gòu)體。本文規(guī)定由SrunX提交的任務(wù)信息必須包含如下信息:
①任務(wù)名稱;
②任務(wù)可執(zhí)行路徑;
③任務(wù)參數(shù);
④cgroups資源限制信息。
同時(shí),為了達(dá)成轉(zhuǎn)發(fā)任務(wù)輸出的效果以及在任務(wù)結(jié)束時(shí)可以通知SrunX的目標(biāo),本文在新任務(wù)請求結(jié)構(gòu)體中添加了輸出回調(diào)函數(shù)和任務(wù)結(jié)束回調(diào)函數(shù)。這2個(gè)回調(diào)函數(shù)可以由調(diào)用TaskManager中添加新任務(wù)接口的調(diào)用方設(shè)置,這樣設(shè)計(jì)的目的是為了保持和任務(wù)管理無關(guān)的邏輯與TaskManager解耦,即將涉及gRPC部分的代碼實(shí)現(xiàn)排除在TaskManager的代碼實(shí)現(xiàn)之外,從而保持代碼的可維護(hù)性。同時(shí),SrunX也有可能以流的形式傳遞多種控制信息,因此本文在SrunX側(cè)和SlurmXd側(cè)都針對該邏輯實(shí)現(xiàn)了狀態(tài)機(jī)。
在實(shí)現(xiàn)中還有一個(gè)難點(diǎn)是使用TaskManager功能的類無法直接以同步方式調(diào)用LibEvent中的任務(wù)執(zhí)行邏輯,這部分邏輯只有當(dāng)事件觸發(fā)時(shí)才會(huì)被執(zhí)行,因此,為了在同步函數(shù)調(diào)用中能以異步方式獲取新任務(wù)的添加結(jié)果,TaskManager采用了C++標(biāo)準(zhǔn)庫中的std::future和std::promise[11]來提供異步通知新任務(wù)添加結(jié)果的功能,并通過跨線程單生產(chǎn)者單消費(fèi)者隊(duì)列進(jìn)行新任務(wù)添加請求的傳遞,通過Linux提供eventfd來完成對新任務(wù)請求事件在LibEvent中的觸發(fā)。
(4)任務(wù)輸出事件。注意到該事件并未在TaskManager初始化時(shí)進(jìn)行注冊,這是因?yàn)槊總€(gè)任務(wù)輸出事件必須和一個(gè)計(jì)算任務(wù)綁定,因此它是一種只有在生成新計(jì)算任務(wù)時(shí)才會(huì)注冊的動(dòng)態(tài)事件。當(dāng)計(jì)算任務(wù)有輸出產(chǎn)生時(shí),該事件處理邏輯會(huì)調(diào)用與該計(jì)算任務(wù)所綁定的輸出回調(diào)函數(shù),從而進(jìn)入上文中所述的邏輯,將輸出轉(zhuǎn)發(fā)至SrunX。
基于上文所述的內(nèi)容,SlurmXd中核心組件TaskManager的功能得以實(shí)現(xiàn)。
4.2.3 Devices類、DevicesAllocator類和專有設(shè)備的適配
該資源調(diào)度系統(tǒng)需要對各類資源進(jìn)行分配,這些資源包括所有機(jī)器通用的資源,如CPU、內(nèi)存和外置的專有硬件(如不同型號(hào)的顯卡和不同種類的網(wǎng)卡)。由于在不同的使用場景下設(shè)備種類是截然不同的,因此在實(shí)際應(yīng)用場景中,很可能會(huì)出現(xiàn)場景變更一次,硬件變更一次的情況。而顯然該資源調(diào)度系統(tǒng)的使用者希望該系統(tǒng)能適配多種類型的硬件,以避免每當(dāng)更換平臺(tái)時(shí)為適配新硬件對該資源調(diào)度系統(tǒng)的主體代碼進(jìn)行大規(guī)模更改。
因此,在進(jìn)行此部分功能的模塊設(shè)計(jì)時(shí),需要考慮上文所述的使用者需求。當(dāng)一個(gè)計(jì)算任務(wù)開始執(zhí)行的時(shí)候,分配給該任務(wù)的各種硬件資源需要進(jìn)行初始化。例如,通過上文設(shè)計(jì)的CgroupManager對CPU和內(nèi)存資源進(jìn)行限制,對通過PCIe接入的專有設(shè)備進(jìn)行權(quán)限設(shè)置。當(dāng)一個(gè)計(jì)算任務(wù)結(jié)束時(shí),可能有相關(guān)設(shè)備資源需要清理或者重置,例如對GPU的功耗解除限制。同時(shí),不同類型硬件的資源分配邏輯可能也不同,例如CPU或內(nèi)存屬于可以進(jìn)行細(xì)粒度分配的資源類型,而PCIe設(shè)備則不支持細(xì)粒度分配,因此二者需要根據(jù)不同的邏輯進(jìn)行分配。
因此,本文參考Adapter設(shè)計(jì)模式[12]將設(shè)備資源抽象為Devices類,該類提供的主要方法有Prepare和Cleanup。Devices類表示統(tǒng)一類型的一個(gè)資源或者多個(gè)資源歸屬于一個(gè)計(jì)算任務(wù)。當(dāng)其歸屬的計(jì)算任務(wù)執(zhí)行前,會(huì)調(diào)用該計(jì)算任務(wù)所擁有的Devices類的Prepare方法,硬件資源相關(guān)的初始化代碼在該方法中。當(dāng)其歸屬的計(jì)算任務(wù)結(jié)束后,會(huì)調(diào)用該計(jì)算任務(wù)所有擁有的Devices類的Cleanup方法,硬件資源相關(guān)的清理和釋放代碼在該方法中。
每一個(gè)Devices類對應(yīng)一個(gè)DevicesAllocator類,該DevicesAllocator類提供的主要方法有Init、AllocateDevices和FreeDevices,其中,Init提供某個(gè)Devices類對應(yīng)的專有硬件的初始化統(tǒng)計(jì);AllocateDevices負(fù)責(zé)分配指定數(shù)量和特定子類型的某類型設(shè)備,返回對應(yīng)的Devices類示例;FreeDevices負(fù)責(zé)釋放AllocateDevices所分配的資源。所有DevicesAllocator類的Init方法會(huì)在SlurmXd進(jìn)行初始化時(shí)被調(diào)用,AllocateDevices在創(chuàng)建單個(gè)新的計(jì)算任務(wù)請求資源時(shí)被調(diào)用,F(xiàn)reeDevices在計(jì)算任務(wù)結(jié)束后會(huì)被調(diào)用。
Figure 11 Devices class and DevicesAllocator class圖11 Devices類和DevicesAllocator類
本文基于Slurm任務(wù)調(diào)度系統(tǒng),通過引進(jìn)面向?qū)ο蟮脑O(shè)計(jì)方法,對Slurm中的部分重要功能進(jìn)行了抽象和模塊化,并對Slurm中原有的架構(gòu)進(jìn)行了重新設(shè)計(jì)。本文將基于上述工作設(shè)計(jì)及實(shí)現(xiàn)的任務(wù)調(diào)度系統(tǒng)命名為SlurmX。相對于Slurm原有的過程化實(shí)現(xiàn),SlurmX在保障高性能的情況下,有效降低了代碼的復(fù)雜度和耦合度,提升了模塊化程度,在工程方面降低了后續(xù)維護(hù)和添加自定義功能的開發(fā)成本。同時(shí),由于Slurm是一個(gè)已經(jīng)歷經(jīng)約二十年沉淀的項(xiàng)目,其功能繁復(fù)紛雜,SlurmX目前僅對Slurm中最為基礎(chǔ)和重要的功能進(jìn)行了重構(gòu),仍有許多功能還有待重構(gòu)和設(shè)計(jì)。目前,SlurmX整體系統(tǒng)還未定型,當(dāng)整體架構(gòu)定型、實(shí)現(xiàn)完善及詳細(xì)測試后將會(huì)開源,以方便感興趣的科研機(jī)構(gòu)進(jìn)行驗(yàn)證和推廣。