深入瞭解Python的Dask分散式排程程式 - selectfrom

banq發表於2022-04-15

Dask 是一個強大的 Python 庫,可讓您使用一個程式碼將資料工程從一臺機器擴充套件到多臺機器,並具有 Python 的可擴充套件性。這種分散式電源的核心是 Dask 分散式排程程式。

從本質上講,Dask排程器將工作交給某個工作者worker。如果該工作者worker看起來已經飽和,並且在一段時間後無法啟動任務,Dask Scheduler就會將該工作拿走,並將其交給另一個工作者。
這方面的程式碼可以在dask.distributed>stealing.py中找到,你可以在GitHub.上進行跟蹤。

WorkStealing類保持對幾個work-stealing重要狀態的跟蹤:
  • stealable_all:跟蹤計劃中的任務,並且是 "可偷的"。有一個 "級別 "的概念,預先設定了15個級別的可steal狀態,這有助於Scheduler決定需要重新分配工作的緊迫性。
  • stealable:記錄我們可以steal工作的工人的情況。
  • key_stealable:跟蹤當前被分配的工人,級別配對。
  • saturated: 一個已經飽和的工人列表。


Dask分散式排程器是如何重新平衡任務負載的?
dask.distributed > stealing.py > WorkStealing > balance()

還記得飽和saturated列表嗎?我們取前10名飽和工人,並在其中找到那些分配的任務多於他們目前能處理的工人,其邏輯如下:
combined_occupancy(ws) > 0.2 and len(ws.processing) > ws.nthreads

Occupancy是一個雙數,用於跟蹤任務的中位執行時間,在某種意義上是一個啟發式的數值,用於預測要花多長時間。nThreads是我們通常的執行緒數。隨著工作者積累了更多的工作,飽和/閒置列表會保持最新,並且可以增加/減少長度。
我們現在引入級別和成本乘數的概念,以決定任務竊取優先權。我們有15個級別的任務,以及相應的成本乘數。
從現在開始(為了準確起見,總是參考原始碼),成本乘數看起來是這樣的,按照級別增加的順序。

[1, 1.03125, 1.0625, 1.125, 1.25, 1.5, 2, 3, 5, 9, 17, 33, 65, 129, 257]

對於每個級別,我們都會瀏覽所有可竊取的任務,並做一個成本效益分析。
dask.distributed > stealing.py > WorkStealing > balance > maybe_move_task

目前的決策演算法如下:
如果我們偷了任務,成本={閒置工人的佔用率+(成本乘數*任務所需的估計時間)}。
如果我們沒有偷竊,成本={飽和工人的佔用率-(任務所需的估計時間/2}。
occ_idl + cost_multiplier * duration <= occ_sat - duration / 2

我現在不確定為什麼要這樣定義,但我要猜測,有一些選擇是基於對時間的學習而做出的。

最後,我們轉向實際的偷竊任務 讓我們來看看重新分配任務的機制。
async def move_task_confirm

我們將偷竊方法稱為我們想要偷竊的任務。該方法在in_flight任務佇列中尋找該任務。然後檢查 "stimulus ID",這似乎是在檢查偷竊請求發出時任務的狀態。如果請求是陳舊的,終止。如果不是,繼續。
self.in_flight_occupancy[thief] -= d["thief_duration"]
self.in_flight_occupancy[victor]+= d["victor_duration"]

然後我們更新我們偷來並給了任務的工人的佔用率(預期平均任務完成時間保持在兩倍)。
ts.state != “processing” : abort steal
elif state in _WORKER_STATE_CONFIRM : only then we still


然後我們檢查工作者的狀態。如果受害者已經在執行,則中止偷竊。如果受害者仍未啟動該任務,則偷竊。

這似乎是work-stealer與Spark和Hadoop類系統的關鍵區別之一。

Spark和Hadoop有能力推測地安排同一任務的多次執行,以應對可能的 "滯後 "任務,而Dask目前沒有這樣做。


self.remove_key_from_stealable(ts)
ts.processing_on = thief
duration = victim.processing.pop(ts)
victim.occupancy -= duration
thief.processing[ts] = d[“thief_duration”]
thief.occupancy += d[“thief_duration”]
self.scheduler.total_occupancy += d[“thief_duration”]
self.put_key_in_stealable(ts)


我們從'可竊取的'任務鍵中抹去任務,因為我們正在制定竊取。更新任務以指向新的'盜賊'工作者,更新佔用把可竊取的任務放回盜賊,因為該工作者把任務傳送給了工作者。

self.scheduler.check_idle_saturated(thief)
self.scheduler.check_idle_saturated(victim)

最後,我們檢查並更新兩個工作者的空閒飽和狀態。空閒飽和狀態是由排程器本身保持的,此時它做一些數學運算來計算出兩個工作者的飽和狀態。
至此,我對Dask排程器的工作竊取機制的理解結束了。我在這裡的主要收穫是。
搶工被集中地整合到Dask分散式排程器中。雖然作者注意到偷工減料並不是其效能的核心組成部分,但我對Dask原始論文中的概念的實現相當好奇。目前,Dask並不支援投機性排程。我們能夠在沒有投機性排程的情況下達到Spark的許多效能基準,這一事實令人印象深刻。
Dask分散式是一個相當強大的任務分配機制,但它具有明顯的可讀性,而且決策啟發式對程式設計師是透明的。這一點我非常欣賞。


 

相關文章