如何使用Rust的gaffer實現優先順序的微批處理排程器 - njk

banq發表於2021-12-02

Surve Mobility是一個為共享出行服務提供商提供全方位服務的車隊運營,我們從客戶那裡接收任務,例如充電、清潔、補充耗材等。根據客戶和任務,這些任務會在整個過程中一一接收在一天的過程中,在每天的批次中,或者在極少數情況下,在每月的批次中。然後,我們的代理在城市中穿行,步行,乘坐客戶車輛和公共交通工具來完成這些任務。Surve 的應用程式將任務交付給代理,導航到任務,並提供對開門等操作的訪問許可權。
我們有一個路由演算法,它為每個活動代理計劃任務的旅行,最佳化旅行距離、任務優先順序和截止日期,並維護不變數,例如代理可以完成的任務,他們可以在一天中的什麼時間完成,充電點等資源是否可用。
很多時候我們可以允許巡視有點陳舊,但是在一些操作之後使用者正在等待檢視結果,在諸如更改優先順序和拒絕任務等情況下,使用者只能檢視陳舊資料,直到重新路由完成。
路由演算法可能需要一段時間才能執行,大約 10% 的時間需要超過 0.5 秒,並且會給我們的資料庫帶來相當大的負載。長事務和大量寫入和讀取的行意味著該演算法使用全域性鎖執行以避免序列化失敗。當任務和代理的數量很少並且演算法很簡單時,這很有效。然後我們成長了,我們有更多的任務和更多的代理,演算法變得更加複雜。這種全域性鎖定使應用程式在使用者等待的情況下變得太慢。
 

最佳化思路
這個問題有一些最佳化機會,這意味著透過改進排程我們可以獲得更可靠的效能,並且我們可以(在短期內)避免擔心最佳化路由演算法本身。

  • 分片

這個問題首先要注意的是,它可以很好地按業務區域(城市)進行分片。當代理人在漢堡開始工作時,其影響僅限於在漢堡的旅行。所以我們可以有效地同時執行多個改道,只要它們在不同的城市。分片問題意味著隨著我們為更多城市提供服務,應用程式效能不會下降。
  • (微)批處理

我們的路由演算法不是插入新任務的迭代演算法,它檢視所有任務並最佳化所有旅行以服務它們。所以我們只需要足夠頻繁地執行路由,以便正確檢視資料。如果我們對一個城市的狀態進行 100 次更改,我們只需要執行一次演算法即可使旅行正確。當我們收到大量數千個訂單時,這特別有用,或者如果幾個使用者在緩慢的重新路由執行時正在更改內容 - 他們只需要等到下一次重新路由結束。
  • 不同的優先順序

使用者等待:第一優先順序是針對諸如協調器已將任務劃分優先順序,或者代理標記需要跳過任務等情況,在這些情況下,協調器或代理在看到結果之前無法繼續他們的工作改道。它應該儘快執行。為了做到這一點,我們可以為此優先順序保留保留的計算容量,以便當需要在此級別重新路由時,通常可以立即安排它。
無效資料:第二優先順序是當有新資訊需要考慮,但沒有人特別等待時。這就像來自自動化外部系統的新任務,他們可能會在之後直接傳送更多工,所以我們可以等一下。
Timeout:最後一個只是獲取輸入中不會觸發上述任何其他更改的任何其他更改,它設定了資料陳舊程度的上限。

解決方案
我檢視了現有的排程程式,但沒有發現任何具有允許我們利用這些最佳化機會的功能的功能,因此我藉此機會構建了一個新的排程程式併發布了我的第一個 Rust crate,決定它需要這個集合特點:

  • 作業入隊:可以使用Clone + Send傳送者將作業從其他執行緒入隊。
  • 迴圈作業:如果作業未因某個最大超時而入隊,則可以自動將作業入隊。
  • Futures:將作業排入佇列的其他執行緒應該能夠await得到結果。
  • 優先順序:作業佇列優先執行,提交順序次之
  • 作業合併:任何具有相同效果的作業都應合併到佇列中以避免不必要的負載。這可能是最重要的功能,它為其他功能帶來了一些額外的挑戰:
    • 具有不同優先順序的合併作業必須合併到最高優先順序
    • 具有等待它們的期貨的合併作業需要使用合併作業的結果喚醒兩者,因此結果需要實現 `Clone` 以便可以將其提供給兩者。
  • 並行執行:多個工作執行緒正在處理作業
  • 併發排除:但有些作業需要獲取一些鎖,因此不能同時執行
  • 優先順序限制:為了讓容量為更高優先順序的作業做好準備,我們可以限制執行低優先順序作業的執行緒數。

結果是選擇gaffer,請檢視文件以獲取特定示例以及如何使用它。在這裡,我將更多地討論結合這些功能以及其他設計決策所面臨的一些挑戰。
.... 點選標題

相關文章