深入解析 DolphinScheduler 任務排程、拆分與執行全流程

海豚调度發表於2024-10-10

Apache DolphinScheduler介紹

Apache DolphinScheduler 是一個分散式易擴充套件的視覺化DAG工作流任務排程開源系統。適用於企業級場景,提供了一個視覺化操作任務、工作流和全生命週期資料處理過程的解決方案。

Dag背景知識

摘錄了一下Dag的offical定義

A graph is formed by vertices and by edges connecting pairs of vertices, where the
vertices can be any kind of object that is connected in pairs by edges.
In the case of a directed graph, each edge has an orientation, from one vertex to another vertex. A path in a directed graph is a sequence of edges having the property that the ending vertex of each
edge in the sequence is the same as the starting vertex of the next edge in the sequence; a path forms a cycle if the starting vertex of its first edge equals the ending vertex of its last edge.

A directed acyclic graph is a directed graph that has no cycles.[1][2][3]

A vertex v of a directed graph is said to be reachable from another
vertex u when there exists a path that starts at u and ends at v.
As a special case, every vertex is considered to be reachable from itself (by a path with zero edges). If a vertex can reach itself via a nontrivial path (a path with one or more edges), then that path is a cycle, so another way to define directed acyclic graphs is that they are the graphs in which no vertex can reach itself via a nontrivial path.

在offical的定義中,有兩物件的集合,集合中的元素是

  1. vertex
    一個實體或者元素,可以是任何抽象的object
  2. edge
    一條有方向直線,包含兩個vertex,分別扮演起點和終點
  • Dag約束
  1. 在Dag中,一個edge(a,b)的終點可以作為另一個edge(b,c)的起點,這個鏈路中所有的vertex都是可到達的, c是從a可達的。
  2. 在Dag中允許vertex不存在於任何一個edge中,這個節點可以從自己到達自己(一個孤島,不和其他vertex有任何聯絡)
  3. 如果一個vertex可以從自己到達自己,但是中間經過了其他的vertex,那麼這就存在一個環circle
  4. 在Dag中沒有環

在DolphinScheduler中表示Dag的資料結構為

public class DAG<Node, NodeInfo, EdgeInfo> {

    private final ReadWriteLock lock = new ReentrantReadWriteLock();

    /**
     * node map, key is node, value is node information
     */
    private final Map<Node, NodeInfo> nodesMap;

    /**
     * edge map. key is node of origin;value is Map with key for destination node and value for edge
     */
    private final Map<Node, Map<Node, EdgeInfo>> edgesMap;

    /**
     * reversed edge set,key is node of destination, value is Map with key for origin node and value for edge
     */
    private final Map<Node, Map<Node, EdgeInfo>> reverseEdgesMap;
}

其中

  • Node表示任務的id
  • NodeInfo表示任務的詳細資訊
  • EdgeInfo包含任務id和依賴任務id

數倉建設任務和任務依賴

在企業數倉建設中,普遍的做法是進行資料分層(引用https://juejin.cn/post/6969874734355841031)

file

在生產環境,由於分層的需要,業務邏輯分佈廣泛,資料儲存型別多樣,這就造成了數倉建設的任務多,任務之間依賴複雜,dag就成了最佳的任務依賴和排程的儲存結構。在Dag結構中每個節點表示一個具體的排程任務,任務之間的連線表示依賴關係,針對Dag結構化資料的遍歷過程,就是對數倉任務的執行過程。

一個簡單的數倉依賴任務關係(數倉建設中會有很多工依賴關係和更復雜的任務依賴關係)

file

DolphinScheduler系統角色拆分

Apache DolphinScheduler核心角色包括MasterServer和WorkerServer,這遵循模組化設計,master和worker專注於自己本身的角色和任務,模組遵循高內聚低耦合的設計,大大提高了系統的穩定性和可擴充套件性,同時也有利於並行開發,縮短系統的研發時間,提高系統的健壯性。

MasterServer主要負責 DAG 任務切分、任務提交監控,並同時監聽其它MasterServer和WorkerServer的健康狀態。 MasterServer服務啟動時向Zookeeper註冊臨時節點,透過監聽Zookeeper臨時節點變化來進行容錯處理。

WorkerServer主要負責任務的執行和提供日誌服務。 WorkerServer服務啟動時向Zookeeper註冊臨時節點,並維持心跳。

DolphinScheduler任務排程流程

參考官網,DolphinScheduler核心任務任務執行流程如下
file

鑑於任務排程的複雜性,一個大的流程可以劃分為小的流程,在主線流程之外還附加了支線流程,下面對執行排程流程拆分進行分析一下,這樣更容易理解。

file

Command分發流程

處理方式

非同步,分散式master server節點。

生產者

api-server將使用者的執行工作流http請求封裝成command資料,insert到t_ds_command表中
一個啟動工作流例項的command樣例

{
    "commandType": "START_PROCESS",
    "processDefinitionCode": 14285512555584,
    "executorId": 1,
    "commandParam": "{}",
    "taskDependType": "TASK_POST",
    "failureStrategy": "CONTINUE",
    "warningType": "NONE",
    "startTime": 1723444881372,
    "processInstancePriority": "MEDIUM",
    "updateTime": 1723444881372,
    "workerGroup": "default",
    "tenantCode": "default",
    "environmentCode": -1,
    "dryRun": 0,
    "processInstanceId": 0,
    "processDefinitionVersion": 1,
    "testFlag": 0
}

消費者

master server中的MasterSchedulerBootstrap loop程式, MasterSchedulerBootstrap使用zk分配到自己的slot,從t_ds_command表中select屬於slot的command列表處理
查詢語句

<select id="queryCommandPageBySlot" resultType="org.apache.dolphinscheduler.dao.entity.Command">
        select *
        from t_ds_command
        where id % #{masterCount} = #{thisMasterSlot}
        order by process_instance_priority, id asc
            limit #{limit}
</select>

MasterSchedulerBootstrap loop輪訓查到待處理的command任務,將command任務和master host生成ProcessInstance,將ProcessInstance物件插入到t_ds_process_instance表中,
同時生成包含執行所需要的上下文資訊的可執行任務workflowExecuteRunnable
workflowExecuteRunnablecache到本地cache processInstanceExecCacheManager,同時生產將ProcessInstanceWorkflowEventType.START_WORKFLOW生產到workflowEventQueue佇列中。

Dag遍歷執行任務

Master本地cache緩衝

cache實現ProcessInstanceExecCacheManagerImpl,提供如下核心功能

public interface ProcessInstanceExecCacheManager {

    /**
     * get WorkflowExecuteThread by process instance id
     *
     * @param processInstanceId processInstanceId
     * @return WorkflowExecuteThread
     */
    WorkflowExecuteRunnable getByProcessInstanceId(int processInstanceId);

    /**
     * judge the process instance does it exist
     *
     * @param processInstanceId processInstanceId
     * @return true - if process instance id exists in cache
     */
    boolean contains(int processInstanceId);

    /**
     * remove cache by process instance id
     *
     * @param processInstanceId processInstanceId
     */
    void removeByProcessInstanceId(int processInstanceId);

    /**
     * cache
     *
     * @param processInstanceId     processInstanceId
     * @param workflowExecuteThread if it is null, will not be cached
     */
    void cache(int processInstanceId, @NonNull WorkflowExecuteRunnable workflowExecuteThread);

    /**
     * get all WorkflowExecuteThread from cache
     *
     * @return all WorkflowExecuteThread in cache
     */
    Collection<WorkflowExecuteRunnable> getAll();

    void clearCache();
}

生產者

MasterSchedulerBootstrap loop將command transform to可以執行的任務,任務物件中包含了要處理的所有上下文資訊

消費者

EventExecuteService根據dag資訊,拿到第一批沒有任何依賴的TaskInstance新增到待執行任務佇列standByTaskInstancePriorityQueue中, standByTaskInstancePriorityQueue按照優先順序先後順序執行,處理任務狀態,將待執行任務提交到globalTaskDispatchWaitingQueue佇列中。

可執行任務Dispatch

Master進城內優先順序佇列

到了globalTaskDispatchWaitingQueue中,已經是可執行任務的最小單元了

生產者

EventExecuteService根據parent node,對Dag進行廣度優先遍歷,提交任務到globalTaskDispatchWaitingQueue佇列中。

消費者

消費者為GlobalTaskDispatchWaitingQueueLooperGlobalTaskDispatchWaitingQueueLooper消費待dispatch的任務,根據任務型別執行任務排程,對任務的排程是走的rpc介面,目前來看根據任務型別分為兩種:

  1. MasterTaskDispatcher
  2. WorkerTaskDispatcher

對於WorkerTaskDispatcher來說,rpc server收到rpc request之後提交任務到了workerTaskExecutorThreadPool執行。所以這是一個非同步處理任務的過程,不至於讓master server hang在這個地方。對於任務的執行進度,會在關鍵節點進行回撥通知。

任務執行狀態回撥通知

Worker被dispatch任務,非同步提交到執行緒池中之行,在任務非同步執行的節點,呼叫rpc介面通知master任務的狀態。

生產者

Worker非同步執行節點,對於任務執行狀態回撥包括四個

  1. TaskExecutionStatus.FAILURE 執行丟擲異常,執行失敗
  2. TaskExecutionStatus.RUNNING_EXECUTION 開始執行
  3. TaskExecutionStatus.KILL 被殺死
  4. TaskExecutionStatus.SUCCESS 執行成功

備註:在官方的事件流程中Ack的方向搞錯了,Ack不是worker通知給master,而是master通知workerer,我的這個事件狀態的處理結束了。

經過校正一下,比較概括性的總結,整體的流程大致如下圖

file

消費者

master節點ITaskInstanceExecutionEventListener服務,服務接受rpc請求,並將任務新增到TaskEventService eventQueue佇列中。

任務狀態處理

緩衝佇列

master節點TaskEventService eventQueue佇列。

生產者

這個生產者可能會很多

  1. api-server使用者行為
  2. master節點任務排程
  3. work節點任務執行
  4. master任務執行

消費者

為master節點的TaskInstanceListenerImpl服務,TaskInstanceListenerImplTaskEvent transform to TaskExecuteRunnable,並且提交到執行緒池執行taskExecuteThreadMap待執行,線上程池中修改任務的執行狀態。

本文由 白鯨開源 提供釋出支援!

相關文章