基於有向無環圖(DAG)的任務調度Demo

定時任務是軟件開發中經常遇到的問題。簡單的定時任務只需要在固定時間觸發它的執行就可以了。但是對於複雜的定時任務,可能是由多個任務組成一個任務組,它們之間存在依賴關係,一個任務執行的條件,必須是它的前置任務已經執行成功(或者沒有前置任務),它纔可以執行。例如下面這幅圖:

圖中任務的依賴關係爲:

任務1:依賴2,5

任務2:依賴3,4

任務3:無依賴

任務4:無依賴

任務5:無依賴

任務6:依賴2

這個任務關係圖其實就是“有向無環圖”(簡稱DAG)這種數據結構。圖是由一系列頂點和連接頂點的邊組成的數據結構。它分爲有向圖和無向圖。有向圖的邊是有方向的,即A->B這條邊和B->A是兩條不同的邊,而無向圖中,A->B和B->A是共用一條邊的。基於這種數據結構,我們可以用圖的頂點表示一個任務,而圖的邊表示任務之間的依賴關係,就可以基於有向無環圖來實現任務調度。

本文基於有向無環圖實現了一個簡單的任務調度系統的demo。

1.定義一個Executor接口

public interface Executor {
    boolean execute();
}

這個接口代表一個可執行的任務,execute代表任務的執行。

2.定義一個Executor接口的實現Task

public class Task implements Executor{
    private Long id;
    private String name;
    private int state;

    public Task(Long id, String name, int state) {
        this.id = id;
        this.name = name;
        this.state = state;
    }

    public boolean execute() {
        System.out.println("Task id: [" + id + "], " + "task name: [" + name +"] is running");
        state = 1;
        return true;
    }

    public boolean hasExecuted() {
        return state == 1;
    }
}

id:任務id

name:任務名

state:任務狀態,簡化爲0:未執行,1:已執行

hasExecuted返回任務是否已執行。

3.任務圖

public class Digraph {
    private Set<Task> tasks;
    private Map<Task, Set<Task>> map;

    public Digraph() {
        this.tasks = new HashSet<Task>();
        this.map = new HashMap<Task, Set<Task>>();
    }

    public void addEdge(Task task, Task prev) {
        if (!tasks.contains(task) || !tasks.contains(prev)) {
            throw new IllegalArgumentException();
        }
        Set<Task> prevs = map.get(task);
        if (prevs == null) {
            prevs = new HashSet<Task>();
            map.put(task, prevs);
        }
        if (prevs.contains(prev)) {
            throw new IllegalArgumentException();
        }
        prevs.add(prev);
    }

    public void addTask(Task task) {
        if (tasks.contains(task)) {
            throw new IllegalArgumentException();
        }
        tasks.add(task);
    }

    public void remove(Task task) {
        if (!tasks.contains(task)) {
            return;
        }
        if (map.containsKey(task)) {
            map.remove(task);
        }
        for (Set<Task> set : map.values()) {
            if (set.contains(task)) {
                set.remove(task);
            }
        }
    }

    public Set<Task> getTasks() {
        return tasks;
    }

    public void setTasks(Set<Task> tasks) {
        this.tasks = tasks;
    }

    public Map<Task, Set<Task>> getMap() {
        return map;
    }

    public void setMap(Map<Task, Set<Task>> map) {
        this.map = map;
    }
}

這個類使用了鄰接表來表示有向無環圖。tasks是頂點集合,也就是任務集合。map是任務依賴關係集合。key是一個任務,value是它的前置任務集合。一個任務執行的前提是它在map中沒有以它作爲key的entry,或者是它的前置任務集合中的任務都是已執行的狀態。

4.調度器

public class Scheduler {
    public void schedule(Digraph digraph) {
        while (true) {
            List<Task> todo = new ArrayList<Task>();
            for (Task task : digraph.getTasks()) {
                if (!task.hasExecuted()) {
                    Set<Task> prevs = digraph.getMap().get(task);
                    if (prevs != null && !prevs.isEmpty()) {
                        boolean toAdd = true;
                        for (Task task1 : prevs) {
                            if (!task1.hasExecuted()) {
                                toAdd = false;
                                break;
                            }
                        }
                        if (toAdd) {
                            todo.add(task);
                        }
                    } else {
                        todo.add(task);
                    }
                }
            }
            if (!todo.isEmpty()) {
                for (Task task : todo) {
                    if (!task.execute()) {
                        throw new RuntimeException();
                    }
                }
            } else {
                break;
            }
        }
    }

    public static void main(String[] args) {
        Digraph digraph = new Digraph();
        Task task1 = new Task(1L, "task1", 0);
        Task task2 = new Task(2L, "task2", 0);
        Task task3 = new Task(3L, "task3", 0);
        Task task4 = new Task(4L, "task4", 0);
        Task task5 = new Task(5L, "task5", 0);
        Task task6 = new Task(6L, "task6", 0);
        digraph.addTask(task1);
        digraph.addTask(task2);
        digraph.addTask(task3);
        digraph.addTask(task4);
        digraph.addTask(task5);
        digraph.addTask(task6);
        digraph.addEdge(task1, task2);
        digraph.addEdge(task1, task5);
        digraph.addEdge(task6, task2);
        digraph.addEdge(task2, task3);
        digraph.addEdge(task2, task4);
        Scheduler scheduler = new Scheduler();
        scheduler.schedule(digraph);
    }
}

調度器的實現比較簡單,就是遍歷任務集合,找出待執行的任務集合,放到一個List中,再串行執行(若考慮性能,可優化爲並行執行)。若List爲空,說明所有任務都已執行,則這一次任務調度結束。

5.運行結果:

任務執行順序正確。

6.總結:

本文基於有向無環圖實現了一個任務調度的demo。可據此進行擴展,實現一個任務調度系統。本文的實現比較簡單,沒有考慮任務失敗的情形,可對此進行擴展。對任務的提交,存儲等,也可進行擴展。

github地址:https://github.com/tswstarplanet/DagScheduler

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章