背景描述
注意 : 在 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
本文由 白鯨開源 提供釋出支援!