系統設計:如何設計一個分散式作業排程器 ?- Rakshesh

banq發表於2022-05-27

工作排程是一個眾所周知的系統設計面試問題。下面是一些可能需要設計工作排程器的領域。
  • 設計一個付款處理的系統。(即每月/每週/每天的支付等)
  • 設計一個程式碼部署系統。(即程式碼流水線)

這個職位的目的是設計一個簡單但可擴充套件的作業排程系統。

問題說明
  • 設計一個作業排程器,在預定的時間間隔內執行作業


功能要求
  • 使用者可以安排或檢視工作。
  • 使用者可以列出所有已提交的作業和當前狀態。
  • 工作可以執行一次或重複執行。工作應在定義的計劃時間後的X閾值時間內執行。(讓x=15分鐘)
  • 單個作業的執行時間不超過X分鐘(讓X=5分鐘)。
  • 工作也可以有優先權。優先順序較高的作業應該比優先順序較低的作業先執行。
  • 工作的輸出應該儲存在檔案系統中

非功能要求
  • 高度可用 - 系統應始終可供使用者新增/檢視作業。
  • 高度可擴充套件性--系統應可擴充套件至數百萬個作業
  • 可靠性--系統必須至少執行一次作業,而且同一作業不能由不同的程式同時執行。
  • 永續性--在發生任何故障時,系統不應丟失作業資訊。
  • 延遲性 - 系統應該在作業被接受後立即確認使用者。使用者不需要等到作業完成。


系統介面
有三個API可以暴露給使用者

1. submitJob(api_key, user_id, job_schedule_time, job_type, priority, result_location)
這裡,job_type = ONCE或RECURRING,result_location可以是s3位置。
API在接受作業後可以返回http響應程式碼202

2. viewJob(api_key, user_id, job_id)
響應包括狀態為NOT_STARTED、STARTED或COMPLETED

3. listJobs(api_key, user_id, pagination_token)
使用者可以查詢所有提交的工作,並返回一個分頁的響應


使用者請求流程

  • 使用者通過連線負載均衡器(或API閘道器)來提交/獲取工作。
  • 請求將持續存在於資料庫中,並將確認函發回給使用者
  • 工作執行者服務將不斷從資料庫中輪詢到期的工作,並在佇列中保持輸入。
  • 工作執行者服務將執行實際的工作業務邏輯,並將最終結果更新到檔案系統中,並將狀態更新為完成。


資料庫設計
由於我們沒有嚴格要求交易支援或任何其他ACID屬性,並考慮到峰值QPS(2*1000=2000 QPS),我們可以使用SQL或NoSql資料庫。考慮到NoSql在規模、維護和成本方面的明顯優勢,我將使用DynamoDb的NoSql解決方案。

使用者查詢模式。

  • 給定userId,新增工作
  • 給定使用者身份,檢索所有工作編號

資料庫表結構:

Table: JOB

+------------------------------+--------+
|          Attribute           |  Type  |
+------------------------------+--------+
| user_id (partition key)      | uuid   |
| job_id (sort key)            | uuid   |
| actual_job_execution_time    | date   |
| job_status                   | string |
| job_type                     | string |
| job_interval                 | int    |
| result_location              | string |
| current_retries              | int    |
| max_retries                  | int    |
| scheduled_job_execution_time | date   |
| execution_status             | string |
+------------------------------+--------+


job_status。這是使用者將看到的工作狀態。它可能有:NOT_STARTED, STARTED, COMPLETED

execution_status。這是我們的服務將保持的實際執行狀態。它可能有。not_started, claimed, processing, success, retriable_failure, fatal_failure

除了使用者之外,我們的工作排程服務將輪詢資料庫以獲得到期的任務。我們有不同的方法來實現這一點

1. 基於X分鐘大小的桶視窗進行分割槽
我們可以建立索引,命名為 scheduledJob,以檢索最後X分鐘到期的工作。

Index: ScheduledJob
+----------------------------------------------+------+
|                  Attribute                   | Type |
+----------------------------------------------+------+
| scheduled_job_execution_time (partition key) | time |
| job_id (sort key)                            | uuid |
+----------------------------------------------+------+
Query (SQL equivalent):
SELECT * FROM ScheduledJob WHERE scheduled_job_execution_time == now() - X


2. 基於X分鐘大小的桶視窗加分片ID的分割槽
有可能在一個特定的時間視窗,已經收到了許多作業(比方說100K)。在這種情況下,上述查詢效能將非常緩慢。我們可以根據隨機的(比方說在1到Y之間)shard_id來進一步劃分資料庫。

Index: ScheduledJob

+----------------------------------------------+------+
|                  Attribute                   | Type |
+----------------------------------------------+------+
| scheduled_job_execution_time (partition key) | uuid |
| shard_id (partition key)                     | int  |
| job_id (sort key)                            | uuid |
+----------------------------------------------+------+
Query (SQL equivalent):
SELECT * FROM ScheduledJob WHERE scheduled_job_execution_time == now() - X and shard_id == Y





工作排程器是如何工作的?
作業排程流程:
每隔X分鐘,主節點會建立一個權威的UNIX時間戳,並給每個工作節點分配一個shard_id和scheduled_job_execution_time。
工作者節點將執行以下查詢,並將作業推送到Kafka佇列中進行執行。

Worker 1:
SELECT * FROM ScheduledJob WHERE scheduled_job_execution_time == now() - X and shard_id = 1
Worker 2:
SELECT * FROM ScheduledJob WHERE scheduled_job_execution_time == now() - X and shard_id = 2


容錯:
Master監控工作者的健康狀況,知道哪個工作者死亡,以及如何將查詢重新分配給一個新的工作者。
如果主節點死亡,我們可以分配其他工作節點作為主節點。
此外,我們還可以引入本地資料庫來跟蹤工人是否成功查詢了資料庫並將條目放入佇列中。

工作執行器是如何工作的?
作業執行器服務有多個消費者,從佇列中提取資料。消費者機器也有主程式和工作程式。主程式和工作程式都是基於 "拉 "的模式進行操作。主程式將從佇列中輪詢作業,工人程式將通過執行以下程式碼從主程式中持續輪詢作業

while True:
  w = get_next_work()
  do_work(w)


作業執行流程和容錯

  • 當一個作業從佇列中被取走時,消費者的主站會更新JOB db屬性execution_status=CLAIMED。
  • 當worker程式拿起工作時,它更新execution_status=PROCESSING,並不斷向本地資料庫傳送健康檢查。
  • 工作完成後,worker程式將把結果推送到s3,更新JOB資料庫的execution_status=COMPLETED或FATAL_FAILED,以及本地資料庫的狀態。
  • worker程式和主站都會在本地資料庫中更新健康檢查。


健康檢查器服務
健康檢查器服務定期執行(例如每隔 x 秒),並掃描最後一次從工人程式收到的健康檢查低於定義閾值的資料庫。在這種情況下,它認為該作業未能處理,並將條目推回佇列。

相關文章