180723-Quick-Task 動態腳本支持框架之結構設計篇

logo

文章連接:https://liuyueyi.github.io/hexblog/2018/07/23/180723-Quick-Task-動態腳本支持框架之結構設計篇/java

Quick-Task 動態腳本支持框架之結構設計篇

相關博文:git

前面兩篇博文,主要是總體介紹和如何使用;接下來開始進入正題,逐步剖析,這個項目是怎麼一步一步搭建起來的;本篇博文則主要介紹基本骨架的設計,圍繞項目的核心點,實現一個基礎的原型系統github

I. 結構分析

總體設計圖以下:redis

腳本框架.png

對於上面的圖,得有一個基本的認知,最好是能在腦海中構想出整個框架運行的方式,在正式開始以前,先簡單的過一下這張結構圖api

抓要點緩存

1. 任務執行單元

即圖中的每一個task就表示一個基本的任務,有下面幾個要求框架

  • 統一的繼承關係(面向對象的設計理念,執行同一個角色的類由某個抽象的接口繼承而來)
  • 任務的執行之間是沒有關係的(即任務在獨立的線程中調度執行)

2. 任務隊列

在圖中表現很明顯了,在內存中會保存一個當前全部執行的任務隊列(或者其餘的容器)ide

這個的目的是什麼?工具

  • 任務腳本更新時,須要卸載舊的任務(所以能夠從隊列中找到舊的任務,並停掉)
  • 任務腳本刪除時,須要卸載舊的任務

3. 任務管理者

雖然圖中並無明確的說有這麼個東西,但也好理解,咱們的系統設計目標就是支持多任務的執行和熱加載,那麼確定有個任務管理的角色,來處理這些事情學習

其要作的事情就一個任務熱加載

  • 包括動態腳本更新,刪除,新增的事件監聽
  • 實現卸載內存中舊的任務並加載執行新的任務

4. 插件系統

這個與核心功能關係不大,能夠先不care,簡單說一下就是爲task提供更好的使用的公共類

這裏不詳細展開,後面再說

II. 設計實現

有了上面的簡單認知以後,開始進入正題,編碼環節,省略掉建立工程等步驟,第一步就是設計Task的API

1. ITask設計

抽象公共的任務接口,從任務的標識區分,和業務調度執行,很容易寫出下面的實現

public interface ITask {
    /**
     * 默認將task的類名做爲惟一標識
     *
     * @return
     */
    default String name() {
        return this.getClass().getName();
    }

    /**
     * 開始執行任務
     */
    void run();

    /**
     * 任務中斷
     */
    default void interrupt() {}
}

前面兩個好理解,中斷這個接口的目的何在?主要是出於任務結束時的收尾操做,特別是在使用到流等操做時,有這麼個回調就比較好了

2. TaskDecorate

任務裝飾類,爲何有這麼個東西?出於什麼考慮的?

從上面能夠知道,全部的任務最終都是在獨立的線程中調度執行,那麼咱們本身實現的Task確定都是會封裝到線程中的,在Java中能夠怎麼起一個線程執行呢?

一個順其天然的想法就是包裝一下ITask接口,讓它集成自Thread,而後就能夠簡單的直接將任務丟到線程池中便可

@Slf4j
public class ScriptTaskDecorate extends Thread {
    private ITask task;

    public ScriptTaskDecorate(ITask task) {
        this.task = task;
        setName(task.name());
    }

    @Override
    public void run() {
        try {
            task.run();
        } catch (Exception e) {
            log.error("script task run error! task: {}", task.name());
        }
    }

    @Override
    public void interrupt() {
        task.interrupt();
    }
}

說明:

上面這個並非必須的,你也徹底能夠本身在線程池調度Task任務時,進行硬編碼風格的封裝調用,徹底沒有問題(只是代碼將不太好看而已)

3. TaskContainer

上面兩個是具體的任務相關定義接口,接下來就是維護這些任務的容器了,最簡單的就是用一個Map來保存,uuid到task的映射關係,而後再須要卸載/更新任務時,停掉舊的,添加新的任務,對應的實現也比較簡單

public class TaskContainer {
    /**
     * key: com.git.hui.task.api.ITask#name()
     */
    private static Map<String, ScriptTaskDecorate> taskCache = new ConcurrentHashMap<>();

    /**
     * key: absolute script path
     *
     * for task to delete
     */
    private static Map<String, ScriptTaskDecorate> pathCache = new ConcurrentHashMap<>();

    public static void registerTask(String path, ScriptTaskDecorate task) {
        ScriptTaskDecorate origin = taskCache.get(task.getName());
        if (origin != null) {
            origin.interrupt();
        }
        taskCache.put(task.getName(), task);
        pathCache.put(path, task);
        AsynTaskManager.addTask(task);
    }

    public static void removeTask(String path) {
        ScriptTaskDecorate task = pathCache.get(path);
        if (task != null) {
            task.interrupt();
            taskCache.remove(task.getName());
            pathCache.remove(path);
        }
    }
}

說明

爲何有兩個map,一個惟一標識name爲key,一個是task的全路徑爲key?

  • 刪除任務時,是直接刪除文件,因此須要維護一個pathCache
  • 維護name的映射,主要是基於任務的惟一標識出發的,後續可能借此作一些擴展(好比任務和任務之間的關聯等)

4. 任務註冊

前面介紹了任務的定義和裝載任務的容器,接下來能夠想到的就是如何發現任務並註冊了,這一塊這裏不要詳細展開,後面另起一篇詳解;主要說一下思路

在設計之初,就決定任務採用Groovy腳原本實現熱加載,因此有兩個很容易想到的功能點

  • 監聽Groovy腳本的變更(新增,更新,刪除),對應的類爲 TaskChangeWatcher
  • 加載Groovy腳本到內存,並執行,對應的類爲 GroovyCompile

5. 執行流程

有了上面四個是否能夠搭建一個原型框架呢?

答案是能夠的,整個框架的運行過程

  • 程序啓動,註冊Groovy腳本變更監聽器
  • 加載groovy腳本,註冊到TaskContainer
  • 將groovy腳本丟到線程池中調度執行
  • 執行完畢後,清除和回收現場

6. 其餘

固然其餘一些輔助的工具類無關緊要了,固然從使用的角度出發,有不少東西仍是頗有必要的,如

  • 通用的日誌輸出組件(特別是日誌輸出,收集,檢索,經典的ELK場景)
  • 報警相關組件
  • 監控相關
  • redis緩存工具類
  • dao工具類
  • mq消費工具類
  • http工具類
  • 其餘

III. 其餘

0. 相關

博文:

項目:

1. 一灰灰Bloghttps://liuyueyi.github.io/hexblog

一灰灰的我的博客,記錄全部學習和工做中的博文,歡迎你們前去逛逛

2. 聲明

盡信書則不如,已上內容,純屬一家之言,因我的能力有限,不免有疏漏和錯誤之處,如發現bug或者有更好的建議,歡迎批評指正,不吝感激

3. 掃描關注

blogInfoV2.png

相關文章
相關標籤/搜索