正確理解Yarn容量排程中的capacity引數

語言: CN / TW / HK

我正在參與掘金創作者訓練營第4期

【初步結論】

容量排程器中,配得最多的應該就是capacitymaximum-capacity了,一個是當前佇列的資源容量,一個是佇列可使用的最大容量。多個佇列的容量之和為100。

maximum-capacity這個引數還好理解,即佇列可使用資源的上限。

假如有多個佇列,每個佇列都將maximum-capacity的值設定成與capacity一樣,意味著每個佇列只能使用固定大小的資源,不能超額使用其他佇列空閒資源,這樣,也就可能出現資源浪費或利用率低的情況。

因此,通常該值會設定成比capacity大。例如都設定為100,也就是每個佇列最大都可以使用叢集的全部資源。

但既然最大都可以使用叢集的全部資源,那麼capacity引數的作用和意義到底是什麼,該引數又是如何限制使用者資源使用的。

查看了官方文件,網上也看了不少文章,始終覺得沒有講透capacity這個引數的意義,索性直接擼原始碼。

結合原始碼,並對照日誌,確認了幾個關鍵點後,對自己的結論很是自信,立馬郵件同步給組內的小夥伴。

佇列的capacity引數是單個使用者在該佇列中所能使用資源的上限。

由於允許多個不同的使用者向同一個佇列提交任務,因此多個使用者的不同任務的資源疊加起來可以超過capacity,但是不能超過maximum-capacity。

【反駁】

然而,沒過多久,就收到了同事的答覆郵件,並附帶如下圖示:佇列配置10%的資源,使用者提交了一個任務,使用的資源遠超10%!

0.jpg

【迴應】

收到郵件,瞬間覺得臉已被打腫,但是之前研究相關原始碼,確定應該是會限制的啊,難道是哪個細節沒注意到,程式碼走了其他分支?

帶著疑問再次走讀相關程式碼,並進行一系列測試,發現該現象是可以解釋的,之前給出的結論也仍舊還是成立的。

當前叢集的總資源為12GB,佇列容量設定為10%,因此該佇列上,單個使用者理論上資源使用的上限為:

12 * 1024 * 0.1 = 1228.8MB

注:該佇列的父佇列為root,如果父佇列不是root,則需要繼續乘父佇列的容量百分比。

由於配置的叢集資源分配最小單位為1024MB,因此需要向上取整,即2048MB。也就是單個使用者使用的資源上限為2048MB。

當spark任務的driver啟動時(申請的資源為2048MB),當前佇列中,該使用者已使用的資源為0,未超過上限,因此可以為其分配資源,即driver可以成功啟動。

driver啟動後繼續申請啟動兩個executor,每個executor申請分配2048MB。yarn排程時,發現該使用者當前已使用資源為2048MB(為driver分配的資源),仍舊未超過上限,因此繼續為一個executor分配了資源。但輪到第二個executor時,該使用者當前已使用的資源變為了4096MB,超過了上限,因此沒有為該executor分配資源。

也就是說:雖然佇列容量配置的是10%,但並不是嚴格按照10%來限制,即允許超額使用。只要使用者當前已使用資源沒有超過上限,就可以繼續分配(即便分配後會超過上限);但一旦當前已使用的資源超過了上限時,則不能再繼續分配資源。

為了驗證上面的結論,再進行如下測試:佇列的容量仍舊配置為10%,同時將AM資源使用限制調高(maximum-am-resource-percent),防止因AM資源受限出現干擾。

先提交一個spark任務,情況和上面的情況一樣,再次提交一個任務時,第二個任務始終處於ACCEPT狀態,spark任務的driver都沒有進行資源分配。

1.jpg

同時,從介面上可以看到任務的診斷資訊為:超過使用者資源使用上限。

2.jpg

在這個基礎之上,切換使用者,再提交一個spark任務,發現任務可以正常執行,如下圖所示:

3.jpg

將該佇列的AM資源使用限制調回到原來的值,再來進行測試,第二個任務同樣處於ACCEPT狀態,但介面上看到的資訊則不同,提示為:超過使用者AM最大使用資源。

4.jpg

到這裡,也就驗證了之前的結論是正確的了。

【再次質疑】

將上面的測試過程,相關截圖,以及結論總結進行了彙總,然後郵件進行了回覆,以為可以告一段落了。但是,過了一會,再次收到了郵件,回覆如下:

將佇列的容量設定為5%,那麼理論上該佇列單個使用者的最大使用資源為:

12 * 1024 * 0.05 = 614.4MB 向上取整為1024MB,按你的結論,提交的任務應該都無法分配資源,處於ACCEPT狀態才對,然而還是進行了資源的分配,如下圖所示: 5.jpg

【GG】

看到郵件後,心情很平淡,因為之前的研究過程中,已經發現了這個問題,直接貼一段程式碼說明:

if (!Resources.lessThanOrEqual(resourceCalculator, lastClusterResource,amIfStarted, amLimit)) { if (getNumActiveApplications() < 1 || (Resources.lessThanOrEqual(resourceCalculator, lastClusterResource, queueUsage.getAMUsed(partitionName),Resources.none()))) { LOG.warn("maximum-am-resource-percent is insufficient to start a" + " single application in queue, it is likely set too low." + " skipping enforcement to allow at least one application" + " to start"); } else { application.updateAMContainerDiagnostics(AMState.INACTIVATED, CSAMContainerLaunchDiagnosticsConstants.QUEUE_AM_RESOURCE_LIMIT_EXCEED); if (LOG.isDebugEnabled()) { LOG.debug("Not activating application " + applicationId + " as amIfStarted: " + amIfStarted + " exceeds amLimit: " + amLimit); } continue; } }

也就是說,只要當前佇列中沒有任務在執行,提交任務時,即便是超過了使用者可使用資源的上限,仍舊會進行資源的分配,保證有一個任務可以執行

另外,從上面的圖中,還可以看出一點,該任務只分配了2048MB,也就是driver的資源,而driver申請啟動的executor均未分配到任何資源,因為當前已使用資源已經超過了上限。

【總結】

佇列的capacity引數是作用於單個使用者的資源使用上限,真正排程分配時只要使用者已使用資源未超過上限,就可以繼續分配(分配後可以超過上限)。

當然決定使用者資源使用上限的還有其他引數,例如user-limit-factor,minimum-user-limit-percent等,後續文章再單獨說明。

另外,整個討論過程下來,體會到原始碼是不會說謊的,看原始碼的同時還是要多動手測試驗證,才能真正做到正確理解。