Dolphinscheduler DAG核心原始碼剖析

海豚调度發表於2024-12-05

背景描述

file

注意 : 在 Dolphinscheduler 中,離線任務是有完整的宣告週期的,比如說停止、暫停、暫停恢復、重跑等等,都是以DAG(有向無環圖的形式進行任務組織)T+1離線任務的。

Dolphinscheduler DAG實現

org.apache.dolphinscheduler.common.graph.DAG

DAG三個重要的資料結構 :

// 頂點資訊
private final Map<Node, NodeInfo> nodesMap;

// 邊關聯資訊,作用是記錄頂點和邊的關係,可以找到葉子節點,也可以獲取下游節點
private final Map<Node, Map<Node, EdgeInfo>> edgesMap;

// 反向邊關聯資訊,作用是可以快速找到入度為0的節點(起始節點),也可以獲取上游節點
private final Map<Node, Map<Node, EdgeInfo>> reverseEdgesMap;

如下示例 :

DAG<String, String, String> graph = new DAG<>();
graph.addNode("A", "A");
graph.addNode("B", "B");
graph.addNode("C", "C");

// 新增一個B -> C的邊,當前A還飄著呢
graph.addEdge("B", "C");

// 如果新增A -> B,其實就是會從B開始一直到子節點,看有沒有可連線的線到A,如果有,說明這個A -> B的邊新增不得,因為會形成環,否則就可以新增
graph.addEdge("A", "B");

原始碼分析 :
org.apache.dolphinscheduler.common.graph.DAG#addEdge

public boolean addEdge(Node fromNode, Node toNode, EdgeInfo edge, boolean createNode) {
    lock.writeLock().lock();

    try {
        // TODO 是否可以新增該邊
        if (!isLegalAddEdge(fromNode, toNode, createNode)) {
            log.error("serious error: add edge({} -> {}) is invalid, cause cycle!", fromNode, toNode);
            return false;
        }

        // TODO 新增節點
        addNodeIfAbsent(fromNode, null);
        addNodeIfAbsent(toNode, null);

        // TODO 新增邊
        addEdge(fromNode, toNode, edge, edgesMap);
        addEdge(toNode, fromNode, edge, reverseEdgesMap);

        return true;
    } finally {
        lock.writeLock().unlock();
    }
}


private boolean isLegalAddEdge(Node fromNode, Node toNode, boolean createNode) {
    // TODO 如果fromNode和toNode兩個是同一個頂點,這個邊是不能新增的
    if (fromNode.equals(toNode)) {
        log.error("edge fromNode({}) can't equals toNode({})", fromNode, toNode);
        return false;
    }

    // TODO 這裡其實就是想說,不是建立節點,也就是說要求fromNode和toNode是需要存在的頂點
    if (!createNode) {
        if (!containsNode(fromNode) || !containsNode(toNode)) {
            log.error("edge fromNode({}) or toNode({}) is not in vertices map", fromNode, toNode);
            return false;
        }
    }

    // Whether an edge can be successfully added(fromNode -> toNode),need to determine whether the
    // DAG has cycle!
    // TODO 這裡獲取節點的數量
    int verticesCount = getNodesCount();

    Queue<Node> queue = new LinkedList<>();

    // TODO 將toNode放入到queue中
    queue.add(toNode);

    // if DAG doesn't find fromNode, it's not has cycle!
    // TODO 當queue不為空,這裡肯定就不為空了
    while (!queue.isEmpty() && (--verticesCount > 0)) {
        // TODO 獲取佇列裡面的元素
        Node key = queue.poll();

        for (Node subsequentNode : getSubsequentNodes(key)) {
            // TODO 其實這裡判斷的是比如說A -> B 有連線的DAG圖,傳入的是節點B,看B節點的邊是不是有A,如果有A說明已經有B -> A的關聯了,
            // TODO 就不能新增了。如果比如說B的下游節點,比如說 A -> B -> C,這樣的話,B的下游節點就是C,C是需要放入queue中的
            // TODO 核心思想其實就是要找到它要新增的目標節點的連線,是否有目標節點到源節點的連線存在(這樣來判斷是否存在環)
            if (subsequentNode.equals(fromNode)) {
                return false;
            }

            queue.add(subsequentNode);
        }
    }

    return true;
}

Dolphinscheduler DagHelper解說

DAG類是一個基礎通用的DAG工具類,而DagHelper是任務定義、任務定義直接的關係組裝成DAG的一個業務工具類。

org.apache.dolphinscheduler.server.master.graph.WorkflowGraphFactory#createWorkflowGraph

public IWorkflowGraph createWorkflowGraph(ProcessInstance workflowInstance) throws Exception {

    // TODO 這裡其實就是獲取的流程例項對應的任務數和之間的關係
    List<ProcessTaskRelation> processTaskRelations = processService.findRelationByCode(
            workflowInstance.getProcessDefinitionCode(),
            workflowInstance.getProcessDefinitionVersion());

    // TODO 獲取對應的任務定義log
    List<TaskDefinitionLog> taskDefinitionLogs = taskDefinitionLogDao.queryTaskDefineLogList(processTaskRelations);

    // TODO 獲取TaskNode
    List<TaskNode> taskNodeList = processService.transformTask(processTaskRelations, taskDefinitionLogs);

    // generate process to get DAG info
    // TODO 這裡其實解析的是是否自己手動指定的啟動節點列表,預設不會
    List<Long> recoveryTaskNodeCodeList = getRecoveryTaskNodeCodeList(workflowInstance.getCommandParam());

    // TODO 如果 預設startNodeNameList為空
    List<Long> startNodeNameList = parseStartNodeName(workflowInstance.getCommandParam());

    // TODO 構建ProcessDag物件例項
    ProcessDag processDag = DagHelper.generateFlowDag(
            taskNodeList,
            startNodeNameList,
            recoveryTaskNodeCodeList,
            workflowInstance.getTaskDependType());

    if (processDag == null) {
        log.error("ProcessDag is null");
        throw new IllegalArgumentException("Create WorkflowGraph failed, ProcessDag is null");
    }

    // TODO 生成DAG
    DAG<Long, TaskNode, TaskNodeRelation> dagGraph = DagHelper.buildDagGraph(processDag);
    log.debug("Build dag success, dag: {}", dagGraph);

    // TODO 使用WorkflowGraph來封裝任務節點列表和dagGraph
    return new WorkflowGraph(taskNodeList, dagGraph);
}

org.apache.dolphinscheduler.service.utils.DagHelper#generateFlowDag

public static ProcessDag generateFlowDag(
                                             List<TaskNode> totalTaskNodeList,
                                             List<Long> startNodeNameList,
                                             List<Long> recoveryNodeCodeList,
                                             TaskDependType depNodeType) throws Exception {

    // TODO 其實就是拿到所有的節點
    List<TaskNode> destTaskNodeList =
            generateFlowNodeListByStartNode(
                    totalTaskNodeList, startNodeNameList, recoveryNodeCodeList, depNodeType);

    if (destTaskNodeList.isEmpty()) {
        return null;
    }

    // TODO 獲取任務節點之前的關係
    List<TaskNodeRelation> taskNodeRelations = generateRelationListByFlowNodes(destTaskNodeList);

    // TODO 其實就是例項化一個ProcessDag
    ProcessDag processDag = new ProcessDag();
    // TODO 設定DAG的邊
    processDag.setEdges(taskNodeRelations);
    // TODO 設定DAG的頂點
    processDag.setNodes(destTaskNodeList);
    return processDag;
}

設定了destTaskNodeList和taskNodeRelations

org.apache.dolphinscheduler.service.utils.DagHelper#buildDagGraph

public static DAG<Long, TaskNode, TaskNodeRelation> buildDagGraph(ProcessDag processDag) {

    DAG<Long, TaskNode, TaskNodeRelation> dag = new DAG<>();

    // TODO 新增頂點
    if (CollectionUtils.isNotEmpty(processDag.getNodes())) {
        for (TaskNode node : processDag.getNodes()) {
            dag.addNode(node.getCode(), node);
        }
    }

    // TODO 新增邊
    if (CollectionUtils.isNotEmpty(processDag.getEdges())) {
        for (TaskNodeRelation edge : processDag.getEdges()) {
            dag.addEdge(edge.getStartNode(), edge.getEndNode());
        }
    }
    return dag;
}

轉載自 Journey

原文連結:https://segmentfault.com/a/1190000045117764

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

相關文章