主頁 > 軟體設計 > Auto-Job任務調度框架

Auto-Job任務調度框架

2023-01-05 07:35:21 軟體設計

Auto-Job 任務調度框架

Gitee | Github ## 一、背景

生活中,業務上我們會碰到很多有關作業調度的場景,如每周五十二點發放優惠券、或者每天凌晨進行快取預熱、亦或每月定期從第三方系統抽數等等,Spring和java目前也有原生的定時任務支持,但是其都存在一些弊病,如下:

  • 不支持集群,未避免任務重復執行的問題
  • 不支持生命周期的統一管理
  • 不支持分片任務:處理有序資料時,多機器分片執行任務處理不同資料
  • 不支持失敗重試:出現例外任務終結,不能根據執行狀態控制任務重新執行
  • 不能很好的和企業系統集成,如不能很好的和企業系統前端集成以及不能很好的嵌入到后端服務
  • 不支持動態調整:不重啟服務情況下不能修改任務引數
  • 無報警機制:任務失敗之后沒有報警通知(郵箱、短信)
  • 無良好的執行日志和調度日志跟蹤

基于原生定時任務的這些弊病,AutoJob就由此誕生,AutoJob為解決分布式作業調度提供了新的思路和解決方案,

二、特性

簡單: 簡單包括集成簡單、開發簡單和使用簡單,

集成簡單:框架能非常簡單的集成到Spring專案和非Spring專案,得益于AutoJob不依賴于Spring容器環境和MyBatis環境,你無需為了使用該框架還得搭建一套Spring應用,

開發簡單:AutoJob開發初衷就希望具有低代碼侵入性和快速開發的特點,如下在任意一個類中,你只需要在某個需要調度的任務上加上注解,該任務就會被框架進行動態調度:

	@AutoJob(attributes = "{'我愛你,心連心',12.5,12,true}", cronExpression = "5/7 * * * * ?")
    public void formatAttributes(String string, Double decimal, Integer num, Boolean flag) {
        //引數注入
        AutoJobLogHelper logger = new AutoJobLogHelper();//使用框架內置的日志類
        logger.setSlf4jProxy(log);//對Slf4j的log進行代理,日志輸出將會使用Slf4j輸出
        logger.info("string={}", string);
        logger.warn("decimal={}", decimal);
        logger.debug("num={}", num);
        logger.error("flag={}", flag);
        //使用mapper
        mapper.selectById(21312L);
        //...
    }

使用簡單:使用該框架你無需關注太多的配置,整個框架的啟動只需要一行代碼,如下:

//配置任務掃描包路徑
@AutoJobScan({"com.yourpackage"})
//處理器自動掃描
@AutoJobProcessorScan({"com.yourpackage"})
public class AutoJobMainApplication {
    public static void main(String[] args) {
    //框架啟動
    	new AutoJobBootstrap(AutoJobMainApplication.class)
                .build()
                .run();
        System.out.println("==================================>系統創建完成");
 	}

}

得益于良好的系統架構和編碼設計,你的應用啟動無需過多配置,只需要一行代碼

拓展: 框架原始碼采用多種合理設計模式設計,具有良好的可拓展性和可維護性,

動態: 框架提供API,支持任務的動態CURD操作,即時生效,

多資料庫支持: 提供多型別資料庫支持,目前支持MySQL和PostgreSQL,

任務依賴: 支持配置子任務,當父任務執行結束且執行成功后將會主動觸發一次子任務的執行,

一致性: 框架使用DB樂觀鎖實作任務的一致性,在集群模式下,調度器在調度任務前都會嘗試獲取鎖,獲取鎖成功后才會進行該任務的調度,

HA(開發中): 該框架支持去中心化的集群部署,集群節點通過RPC加密通信,集群節點之間會自動進行故障轉移和負載均衡,

彈性增縮容(開發中): 支持節點的動態上下線,同時節點支持開啟保護模式,防止惡劣的網路環境下節點脫離集群,

任務失敗重試: 支持任務失敗重試,并且可設定重試間隔,

完整的生命周期: 框架提供任務完整的生命周期事件,業務可捕捉并做對應的處理,

動態調度執行緒池: 框架使用自研的動態執行緒池,可靈活根據任務流量動態調整執行緒池核心執行緒和最大執行緒引數,節省系統執行緒資源,并且提供了默認的拒絕處理器,防止任務被missFire,

異步非阻塞的日志處理: 日志采用生產者消費者模型,基于自研的記憶體訊息佇列,任務方法作為日志的生產者,生產日志放入訊息佇列,框架啟動對應的日志消費執行緒進行日志處理,

實時日志: 日志將會實時的進行保存,便于跟蹤,

任務白名單: 提供任務白名單功能,只有在白名單中的任務才允許被注冊和調度,保證系統安全,

可拓展的日志存盤策略: 日志支持多種策略保存,如記憶體Cache、資料庫等,可根據專案需要靈活增加保存策略,如Redis、檔案等,

豐富的調度機制: 支持Cron like運算式,repeat-cycle調度、子任務觸發、延遲觸發等,得益于良好的編碼設計,用戶可非常簡單的新增自定義調度器,如下:

/**
 * 你的自定義調度器
 * @Author Huang Yongxiang
 * @Date 2022/08/18 14:56
 */
public class YourScheduler extends AbstractScheduler{
    public YourScheduler(AutoJobTaskExecutorPool executorPool, IAutoJobRegister register, AutoJobConfigHolder configHolder) {
        super(executorPool, register, configHolder);
    }
    
    //...調度邏輯
}

@AutoJobScan("com.example.autojob.job")
@AutoJobProcessorScan("com.example.autojob")
public class AutoJobMainApplication {
    public static void main(String[] args) {
        new AutoJobLauncherBuilder(AutoJobMainApplication.class)
                .withAutoScanProcessor()
            	//配置你的調度器
                .addScheduler(YourScheduler.class)
                .build()
                .run();
        System.out.println("==================================>系統創建完成");
    }
}

任務報警: 框架支持郵件報警,目前原生支持QQ郵箱、163郵箱、GMail等,同時也支持自定義的郵箱smtp服務器,
在這里插入圖片描述
目前系統提供:任務失敗報警、任務被拒報警、節點開啟保護模式報警、節點關閉保護模式報警,當然用戶也可非常簡單的進行郵件報警的拓展,

豐富的任務入參: 框架支持基礎的資料型別和物件型別的任務入參,如Boolean,String,Long,Integer,Double等型別,對于物件入參,框架默認使用JSON進行序列化入參,

良好的前端集成性: 框架提供相關API,用戶可以靈活開發Restful介面接入到企業專案,無需額外占用一個行程或機器來單獨運行調度中心,

記憶體任務: 框架提供DB任務和記憶體任務兩種型別,DB任務持久化到資料庫,宣告周期在資料庫內記錄,記憶體任務除了日志,整個生命周期都在記憶體中完成,相比DB任務具有無鎖、調度快速的特點,

腳本任務: 提供腳本任務的執行,如Python、Shell,SQL等,

動態分片(開發中): 集群模式下框架支持任務分片,多機運行,

全異步: 任務調度流程采用全異步實作,如異步調度、異步執行、異步日志等,有效對密集調度進行流量削峰,理論上支持任意時長任務的運行,

三、快速使用

1、專案匯入

該框架不依賴于Spring容器環境和MyBatis等持久層框架,你可以將其作為一個Maven模塊匯入到你的專案中,你可以去碼云上下載:https://gitee.com/hyxl-520/auto-job.git

專案分為兩個模塊:auto-job-framework和auto-job-spring,前者是框架的核心部分,后者是與Spring集成的使用,后續可能會基于Spring web開發相關控制臺,

2、專案配置

專案配置主要為框架配置和資料源配置,框架配置默認讀取類路徑下的auto-job.ymlauto-job.properties檔案,具體配置項內容見“所有配置”;資料源配置,框架默認使用Druid作為連接池,你只需要在druid.properties檔案中配置資料源就行了,當然你可以自定義資料源,具體方法在AutoJobBootstrap里,相關建表腳本可以在db目錄下找到,框架默認使用MySQL資料庫,理論上支持SQL標準的其他資料庫

3、任務開發

3.1、基于注解

開發一個基于注解的任務非常簡單,除了日志輸出使用框架內置的日志輔助類AutoJobLogHelper輸出外,其他你就只需要關心你的業務,當然,AutoJobLogHelper使用起來和slf4j幾乎沒有區別,它提供四種級別的日志輸出:debug、info、warn、error,而且你可以使用AutoJobLogHelper對你的slf4j進行代理,這樣這些任務執行中輸出的日志將會直接使用slf4j進行輸出,如下,是一個簡單演示:

 @AutoJob(attributes = "{'我愛你,心連心',12.5,12,true}", cronExpression = "5/7 * * * * ?", id = 2, alias = "引數測驗任務")
    public void formatAttributes(String string, Double decimal, Integer num, Boolean flag) {
        AutoJobLogHelper logger=new AutoJobLogHelper();
        //log是org.slf4j.Logger物件,這里對其進行代理
        logger.setSlf4jProxy(log);
        logger.info("string={}", string);
        logger.warn("decimal={}", decimal);
        logger.debug("num={}", num);
        logger.error("flag={}", flag);
    }

在你開發的任務上加上@AutoJob注解,配置一些東西,這個任務就開發完成了,@AutoJob是用來標識一個方法是一個AutoJob任務,當然還有其他注解,這里暫不做闡述,細心的同學會發現這個任務是有引數的,沒錯,AutoJob框架支持引數,更多引數的配置后文會詳細講解,

3.2、基于構建

手動創建任務相比注解來說更為靈活,框架提供了創建任務的構建者物件,如AutoJobMethodTaskBuilderAutoJobScriptTaskBuilder物件,前者用于構建方法型任務,后者用于構建腳本型任務,

MethodTask task = new AutoJobMethodTaskBuilder(Jobs.class, "hello") //方法型任務需要指定方法所在的類以及方法名
          .setTaskId(IdGenerator.getNextIdAsLong())
          .setTaskAlias("測驗任務") //任務別名
    	  .setParams("{'我愛你,心連心',12.5,12,true}") //任務引數,支持simple引數
          .setTaskType(AutoJobTask.TaskType.MEMORY_TASk)
          .setMethodObjectFactory(new DefaultMethodObjectFactory()) //方法運行物件工廠,用于創建方法運行的物件背景關系
          .addACronExpressionTrigger("* 5 7 * * * ?", -1) //添加一個cron-like觸發器
          .build();

AutoJobApplication
         .getInstance()
         .getMemoryTaskAPI() //獲取全域的記憶體任務的API
         .registerTask(new AutoJobMethodTaskAttributes(task)); //注冊任務

4、框架啟動

得益于良好的設計,該框架你可以在任何一個main方法啟動,如下是示列的一個啟動

import com.example.autojob.skeleton.annotation.AutoJobProcessorScan;
import com.example.autojob.skeleton.annotation.AutoJobScan;
import com.example.autojob.skeleton.framework.boot.AutoJobLauncherBuilder;

@AutoJobScan("com.example.autojob.job")
@AutoJobProcessorScan("com.example.autojob")
public class AutoJobMainApplication {
    public static void main(String[] args) {
        new AutoJobBootstrap(AutoJobSpringApplication.class)
                .withAutoScanProcessor()
                .build()
                .run();
        System.out.println("==================================>AutoJob應用已啟動完成");
    }
}

第5行是用于配置任務掃描的類路徑,支持子包掃描,不配置時會掃描整個專案,用時較長,

第6行是處理器掃描,處理器主要是在框架啟動前和框架啟動后進行一些處理,默認是掃描整個專案,注意該注解只有設定了withAutoScanProcessor才能生效,如代碼第10行,框架自己的處理器為自動加載,無需配置,

第9-12行是框架的啟動代碼,AutoJobBootstrap是應用引導構建程式,通過它你能增加很多自定義的配置,在第11行后,AutoJob應用即創建完成,第12行呼叫run方法啟動整個應用,

5、動態修改

框架本身不是一個Web應用,沒有提供對應修改的Rest介面,但是框架提供了很多操作任務的API,你可以在AutoJobAPIAutoJobLogAPI里找到,你可以你可以參考auto-job-spring模塊里提供的實體開發對應Rest介面,隨著版本更替,autojob將會在未來支持控制臺,

四、任務型別

按照功能分類

任務按照功能可以分為方法型任務和腳本型任務,

方法型任務對應Java中的一個方法,該方法可以有回傳值,允許有引數,引數的注入可以見“任務引數”,方法內部的日志輸出必須使用AutoJobLogHelper來輸出,否則日志可能無法保存,

腳本型任務對應一個磁盤上的腳本檔案或一段cmd命令,具體使用可見章節:“高級用法-腳本任務”,

按照調度方式分類

任務按照調度方式可以分為記憶體型任務和DB型任務,

記憶體型任務的生命周期都在記憶體中完成,具有調度迅速、無鎖、隨調隨動的特點,適合短周期、有限次、臨時性的任務,

DB型任務將會保存到資料庫,每一次調度都會更新資料庫相關狀態,DB型任務采用樂觀鎖,每次執行前都需要獲得鎖才能執行,具有長期性、易維護、易修改等特點,適合于定期資料同步、定時快取預熱等在長期內都會用到的任務,

五、任務引數

方法型任務

方法型任務支持兩種引數格式,一種是FULL型引數,一種是SIMPLE引數,具體區別可見如下示列:

void exampleMethod1(String str, Integer num, Double decimal, Boolean flag);

void exampleMethod2(String str, Integer num, Double decimal, Boolean flag, Long count, Param param);

class param{
    private int id;
    private String num;
    //...
}

如上方法:exampleMethod1,使用SIMPLE型引數:

MethodTask task = new AutoJobMethodTaskBuilder(Jobs.class, "hello") 
          .setTaskId(IdGenerator.getNextIdAsLong())
          .setTaskAlias("測驗任務")
    	  .setParams("{'我是字串引數',12,12.5,true}")
          .setTaskType(AutoJobTask.TaskType.MEMORY_TASk)
          .setMethodObjectFactory(new DefaultMethodObjectFactory()) 
    	  .build();
//{'我是字串引數',12,12.5,true}

使用FULL型引數

MethodTask task = new AutoJobMethodTaskBuilder(Jobs.class, "hello")
                .setTaskId(IdGenerator.getNextIdAsLong())
                .setTaskAlias("測驗任務")
                .setParams("[{\"values\":{\"value\":\"字串引數\"},\"type\":\"string\"},{\"values\":{\"value\":12},\"type\":\"integer\"},{\"values\":{\"value\":12.5},\"type\":\"decimal\"},{\"values\":{\"value\":false},\"type\":\"boolean\"}]")
                .setTaskType(AutoJobTask.TaskType.MEMORY_TASk)
                .setMethodObjectFactory(new DefaultMethodObjectFactory())
                .build();

/*
[
  {
    "values": {
      "value": "字串引數"
    },
    "type": "string"
  },
  {
    "values": {
      "value": 12
    },
    "type": "integer"
  },
  {
    "values": {
      "value": 12.5
    },
    "type": "decimal"
  },
  {
    "values": {
      "value": false
    },
    "type": "boolean"
  }
]
*/

我們可以發現SIMPLE引數十分簡單,"{a1,a2,a3,...}",引數運算式本身是一個字串,大引號包裹,引數順序按照從左到右依次匹配,SIMPLE引數支持四類引數

'字串引數',單引號包裹,對應型別String

12:整數型引數,對應型別:Integer包裝型別,如果數值超過整形范圍,則會自動匹配Long型別,

12.5:小數型引數,對應型別:Double包裝型別,

true|false:布爾型引數,對應型別:Boolean包裝型別,

FULL型引數相比就要復雜的多了,本身是一個JSON陣列字串,每一個JSON物件代表一個引數,每個物件有type和values兩個屬性,字面意思,型別和值,FULL型別除了支持SIMPLE型的四種型別引數外還支持物件型,物件型的引數使用JSON來進行序列化和反序列化,由于FULL型引數過于復雜,因此框架提供了AttributesBuilder物件,可以非常簡單的生成FULL型引數,以exampleMethod2為例:

Param param = new Param();
        param.setId(1);
        param.setNum("12");
System.out.println(new AttributesBuilder()
        .addParams(AttributesBuilder.AttributesType.STRING, "字串引數")
        .addParams(AttributesBuilder.AttributesType.INTEGER, 12)
        .addParams(AttributesBuilder.AttributesType.DECIMAL, 12.5)
        .addParams(AttributesBuilder.AttributesType.BOOLEAN, false)
        .addParams(Param.class, param)
        .getAttributesString());
/*
[
  {
    "values": {
      "value": "字串引數"
    },
    "type": "string"
  },
  {
    "values": {
      "value": 12
    },
    "type": "integer"
  },
  {
    "values": {
      "value": 12.5
    },
    "type": "decimal"
  },
  {
    "values": {
      "value": false
    },
    "type": "boolean"
  },
  {
    "values": {
      "id": 1,
      "num": "12"
    },
    "type": "com.example.autojob.job.Param"
  }
]
*/

一般來說,基于注解的任務開發我們更傾向于推薦使用SIMPLE型引數,簡單、明了;基于構建的任務開發我們更鐘意于FULL型引數,型別豐富,

腳本型任務

腳本型任務的引數是通過啟動命令給出的,如python /script.test.py -a 12 -b,其中-a 12-b就是兩個引數,因此腳本型任務只支持字串型引數,

七、任務運行物件工廠

任務運行物件工廠是方法型任務才有的屬性,因為方法型任務對應的是Java某個類中的方法,因此方法的執行可能依賴于物件實體的背景關系,特別是當該框架與Spring集成時很可能會使用Spring容器中的Bean,因此可以指定創建方法依賴的物件的工廠:IMethodObjectFactory,框架默認使用類的無參構造方法創建物件實體,當然你可以創建自定義的工廠:

public class SpringMethodObjectFactory implements IMethodObjectFactory {
    public Object createMethodObject(Class<?> methodClass) {
        // SpringUtil持有Spring的容器,獲取Spring容器中的Bean
        return SpringUtil.getBean(JobBean.class);
    }
}

那么怎么讓我們的任務運行物件工廠生效呢,見如下示列:

// 基于注解的任務開發只需要指定methodObjectFactory屬性即可,框架將會呼叫指定工廠的無參構造方法創建一個工廠實體
@AutoJob
            (
                    id = 1
                    , attributes = "{'hello autoJob'}"
                    , defaultStartTime = StartTime.NOW
                    , repeatTimes = -1, cycle = 5
                    , methodObjectFactory = SpringMethodObjectFactory.class
            )
public void hello(String str) {
    logHelper.info(str);
}

//基于構建的任務開發時將工廠實體配置進去即可
public static void main(String[] args) {
    MethodTask methodTask = new AutoJobMethodTaskBuilder(Jobs.class, "hello")
            .setMethodObjectFactory(new SpringMethodObjectFactory())
            .build();
    AutoJobApplication
         .getInstance()
         .getMemoryTaskAPI() //獲取全域的記憶體任務的API
         .registerTask(new AutoJobMethodTaskAttributes(task)); //注冊任務
}

八、任務日志

作為一款任務調度框架,詳細的日志一定是必不可少的,框架提供三種型別日志記錄:調度日志、執行日志、運行日志

調度日志

任務的每一次啟動到完成被任務是一次調度,調度日志詳細記錄了調度任務的基礎資訊、調度時間、運行狀態、執行時長、以及任務結果(任務結果對應方法型任務是回傳值,由JSON序列化,腳本型任務是腳本回傳值),調度日志對應資料庫表aj_scheduling_record,其ID關聯到本次調度中產生的運行日志和執行日志,

運行日志

運行日志為任務在運行期間內部輸出的日志,方法型任務為使用AutoJobLogHelper輸出的日志,腳本型任務為腳本或cmd命令在控制臺的輸出,運行日志對應資料庫表aj_job_logs

執行日志

執行日志記錄了某次調度任務的執行情況,如何時啟動、何時完成、是否運行成功、任務結果、任務例外等,執行日志對應庫表aj_run_logs

任務日志都是實時更新的,如果你使用的是框架的默認日志保存策略(資料庫存盤),你可以通過AutoJobLogDBAPI獲取到日志,運行日志和執行日志都系結了調度ID,通過調度ID即可找到本次調度所產生的運行日志和執行日志,

九、框架架構

在這里插入圖片描述
框架架構圖的左部分的組件是框架的核心組件,

任務容器模塊

任務容器模塊包含DB任務容器和記憶體任務容器,分別用于存放DB型的任務和記憶體型的任務,

調度模塊

調度模塊由調度器、任務調度佇列、注冊器、時間輪調度器以及時間輪構成,記憶體任務調度器AutoJobMemoryTaskScheduler和DB任務調度器AutoJobDBScheduler負責從任務容器調度出即將執行的任務(<=5秒)放到任務調度佇列快取AutoJobTaskQueue,時間輪調度器AutoJobTimeWheelScheduler通過注冊器AutoJobRegister調度任務調度佇列中的任務進入時間輪,準備執行,時間輪按秒滾動,將執行的任務提交進任務執行器池進行執行,運行成功調度器AutoJobRunSuccessScheduler執行運行成功后的相關操作,比如更新狀態、更新下次觸發時間等等,運行失敗調度器AutoJobRunErrorScheduler執行運行失敗后的相關操作,比如更新狀態、根據配置的重試策略更新觸發時間、故障轉移等等,

任務執行器池模塊

任務執行器池包含兩個動態執行緒池,分別為快池(fast-pool)和慢池(slow-pool),任務默認第一次執行提交進快池,第二次執行會根據上次執行時長決定是否降級處理,動態執行緒池是具有根據流量動態調節的執行緒池,具體的配置可以見“十、所有配置:執行器池配置”,

日志模塊

日志模塊和核心調度模塊是完全解耦的,運行日志由任務執行時產生并且發布到記憶體訊息佇列,日志模塊監聽訊息發布事件并且取出訊息放入訊息buffer,單獨由日志處理執行緒定期、定量保存日志,運行日志通過監聽任務事件來進行保存,日志模塊的設計都是異步化的,盡最大可能減小日志IO對調度的影響,

除了以上的核心組件外,框架還有部分功能拓展組件,

生命周期處理器

生命周期處理器也可以理解成生命周期鉤子,具體來說是一個任務的生命周期鉤子,具體看下面的生命周期事件圖
在這里插入圖片描述
要使用一個生命周期鉤子也十分簡單,下面來看一個示列:

//方式一(子事件處理器)
public class TaskBeforeRunHandle implements ITaskEventHandler<TaskBeforeRunEvent> {
    @Override
    public void doHandle(TaskBeforeRunEvent event) {
        System.out.println("任務:" + event
                .getTask()
                .getAlias() + "即將開始運行");
    }

    @Override
    public int getHandlerLevel() {
        return 0;
    }
}

以上示串列示一個在任務執行前在控制臺輸出:“任務:{任務別名}即將開始運行”,要實作一個事件處理器只需要實作ITaskEventHandler介面即可,泛型代表你需要處理的事件,當然還可以通過如下方式來實作同上面示列一樣的功能

//方式二(父事件處理器)
public class TaskBeforeRunHandle implements ITaskEventHandler<TaskEvent> {
    @Override
    public void doHandle(TaskEvent event) {
        if (event instanceof TaskBeforeRunEvent) {
            System.out.println("任務:" + event
                    .getTask()
                    .getAlias() + "即將開始運行");
        }
    }

    @Override
    public int getHandlerLevel() {
        //數字越大,級別越高
        return 0;
    }
}

TaskEvent是所有任務事件的父類,實作其父類事件的處理器時所有的任務相關事件都會執行該處理器,可以判斷事件型別來完成相關操作,當一個處理器需要處理多種事件型別時可以如上使用,每個事件處理器可以通過重寫getHandlerLevel方法指定級別,數字越大,級別越高,執行越會被優先執行,父事件處理器高級別>父事件處理器低級別>子事件處理器高級別>子事件處理器低級別,當然,只宣告處理器不將其添加到應用也不會生效的,下面介紹如何使得事件處理器生效,

public class TaskEventHandlerLoader implements IAutoJobLoader {
    @Override
    public void load() {
        //方式一(子事件處理器)
        TaskEventHandlerDelegate
                .getInstance()
                .addHandler(TaskBeforeRunEvent.class, new TaskBeforeRunHandle());
        
		//方式二(父事件處理器)
        TaskEventHandlerDelegate
                .getInstance()
                .addHandler(TaskEvent.class, new TaskBeforeRunHandle());
    }
}
//將啟動處理器添加進應用背景關系
public static void main(String[] args) {
        new AutoJobBootstrap(AutoJobMainApplication.class)
                .addProcessor(new TaskEventHandlerLoader()) //添加到背景關系
                .build()
                .run();
}

上面的代碼演示了如何添加處理器到背景關系,在AutoJob中,在框架啟動前和框架關閉前執行某些操作的處理器成為Processor,框架啟動前執行的處理器為IAutoJobLoader,框架關閉前執行的處理器為IAutoJobEnd,上面代碼中,通過啟動處理器將事件處理器添加到“事件委派者”:TaskEventHandlerDelegate,再在應用構建時手動將啟動處理器添加到應用背景關系中,當然如果你的Processor非常多,可以通過注解@AutoJobProcessorScan來自動掃描Processor,可以指定掃描的包,支持子包掃描,不指定時默認全專案掃描,掃描后通過呼叫Processor的無參構造方法創建實體后自動注入背景關系,如下示列:

@AutoJobProcessorScan("com.example.autojob")
public class AutoJobMainApplication {
    public static void main(String[] args) {
        new AutoJobBootstrap(AutoJobMainApplication.class) //指定入口類
                .withAutoScanProcessor() //手動開啟處理器自動掃描,默認是關閉的,以防全專案掃描耗時較長
                .build()
                .run();
        System.out.println("==================================>系統創建完成");
}

十、所有配置

框架提供了豐富的配置,這些配置默認是從auto-job.yml或者auto-job.properties檔案中加載,當然你可以從資料庫動態加載實作動態配置,全部配置如下:

# 動態任務調度框架配置
autoJob:
  context:
    schedulingQueue: 
      length: 100 # 調度佇列長度,調度佇列用于存放即將執行的任務
    memoryContainer: # 記憶體型任務容器,存放記憶體型任務
      length: 200 # 容器容量
      cleanStrategy: CLEAN_FINISHED # 清理策略,CLEAN_FINISHED-定期清理已經執行完成的任務 KEEP_FINISHED-保留執行完成的任務,會將其移入一個記憶體Cache,不會占用容器容量
  annotation:
    enable: true # 是否啟用注解掃描,掃描被@AutoJob @FactoryJob的方法并將其包裝成可執行任務物件
    defaultDelayTime: 30 # 在未給注解的任務配置調度資訊的情況下,默認的任務延遲執行時間:min
  database:
    type: mysql # 資料庫型別,目前支持,MySQL和PostgreSQL
  executor: # 執行器池,分為快池和慢池
    fastPool: # 快池相關配置,慢池相同
      update: # 執行器池支持根據流量動態調整執行緒數目
        enable: true # 是否開啟
        trafficUpdateCycle: 5 # 流量監控周期:秒
        adjustedThreshold: 0.05 # 如果流量變化相比最大執行緒數超過此比例(0-1),則進行調整
      coreThread: # 核心執行緒數
        initial: 5 # 初始值
        min: 5 # 允許變化到的最小值
        max: 50 # 允許變化到的最大值
        keepAliveTime: 60 # 當執行緒數大于當前核心執行緒數時,執行緒保持生存的時間:秒
      maxThread: # 最大執行緒數
        initial: 10
        min: 10
        max: 50
    slowPool: # 慢池
      update:
        enable: false
        trafficUpdateCycel: 5
        adjustedThreshold: 0.05
      coreThread:
        initial: 10
        min: 5
        max: 50
        keepAliveTime: 60
      maxThread:
        initial: 20
        min: 10
        max: 50
      relegation:
          threshold: 3 # 降級閾值,當任務的上次執行時長超過該閾值(分鐘)時,下次將會降級到slow pool運行
  register:
    filter: # 注冊過濾器用于防止某些不安全的任務被執行
      enable: true
      classPath: "**.job.**" # 只有在這些類路徑下的任務才允許被注冊和執行
  scheduler:
    finished:
      error:
        retry: # 失敗重試相關配置,該配置是全域的
          enable: true
          retryCount: 3
          interval: 1 # 兩次重試的間隔:min
  emailAlert: # 全域郵件報警相關配置
    enable: true
    auth:
      sender: "[email protected]" # 發送方,唯一
      receiver: "[email protected]" # 接收方,多個逗號分割
      token: "LXZYE214123CEWASU" # smtp密碼
      type: 163Mail # 郵件型別,目前支持:QQMail、163Mail、gMail(google)、outLookMail、customize(自定義)
      customize: # 自定義下的smtp服務器的相關配置
        smtpAddress:
        smtpPort:
    config: # 提供部分事件報警(開關)
      taskRunError: true # 任務運行出錯(優先使用任務私有郵件客戶端,不存在使用全域客戶端)
      taskRefuseHandle: true # 任務被拒絕執行(優先使用任務私有郵件客戶端,不存在使用全域客戶端)
      clusterOpenProtectedMode: true # 集群節點開啟保護模式(集群模式下有效)
      clusterCloseProtectedMode: true # 集群節點關閉保護模式(集群模式下有效)
  logging: # 日志的相關配置
    taskLog: # 任務內部通過呼叫logger輸出的日志
      memory: # 日志默認是資料庫保存,框架額外提供了記憶體Cache保存,記憶體Cache一般僅做測驗,該配置一般情況下無需更改
        enable: false
        length: 100
        defaultExpireTime: 3 # 分鐘
    runLog: # 任務的調度日志
      memory:
        enable: false
        length: 100
        defaultExpireTime: 3
  cluster: # 集群相關配置,目前版本暫無需考慮
    enable: false # 集群開關,目前版本開啟后會啟動PRC服務器
    port: 8080 # TCP埠
    auth: # RPC通信身份驗證
      enable: true # 是否啟動身份驗證
      publicKey: "autoJob!@#=123.?" # 通信加密公鑰,16位字串
      token: "hello" # token,相同token的兩個AutoJob應用才能通信
    client:
      nodeUrl: "localhost:8086"
      pool: # RPC會話池相關配置
        size: 10
        getTimeout: 3
        getDataTimeout: 10
        connectTimeout: 10
        keepAliveTimeout: 10
      allowMaxJetLag: 3
      nodeSync:
        cycle: 5
        offLineThreshold: 3
    config:
      annotations:
        enable: false
      protectedMode:
        enable: true
        threshold: 0.2

當然上面配置并不是都需要你配置,框架基本所有配置都設定了默認值,能保證常規場景下的調度,

十一、高級用法

1、腳本任務

框架支持腳本任務,原生支持:Python、Shell、PHP、NodeJs以及PowerShell,提供其他腳本型別拓展,腳本任務對應的物件為ScriptTask,腳本作為一個服務器上的腳本檔案保存在磁盤上,要構建一個腳本任務非常簡單,框架提供AutoJobScriptTaskBuilder來輔助構建一個完整的腳本任務,下面看幾個示列:

		ScriptTask task = new AutoJobScriptTaskBuilder()
                .setTaskId(IdGenerator.getNextIdAsLong()) //設定任務ID,任務ID作為區分任務的鍵,不指定時將會隨機分配
                .setTaskAlias("測驗腳本任務1") //任務別名
                .setTaskType(AutoJobTask.TaskType.MEMORY_TASk) //任務型別,有記憶體型任務和DB型任務,記憶體型任務的所有生命周期都在記憶體完成,除了日志外不會保留到資料庫
                .setBelongTo(1L) //保留拓展欄位,用于說明該任務所屬
                .addACronExpressionTrigger("* 15 7 * * * ?", -1) //添加一個cron-like觸發器,兩個引數分別是:cron-like運算式、重復次數,不指定觸發器時將會在默認延遲后執行一次,-1表示該任務為永久執行,如果只需執行n次,重復次數為n-1
                .createNewWithContent(ScriptType.PYTHON, "print('hello auto-job')"); // 使用腳本型別和腳本內容構建一個腳本任務物件

        ScriptTask task1 = new AutoJobScriptTaskBuilder()
                .setTaskId(IdGenerator.getNextIdAsLong())
                .setTaskAlias("測驗腳本任務2")
                .setTaskType(AutoJobTask.TaskType.MEMORY_TASk)
                .setBelongTo(1L)
                .addASimpleTrigger(SystemClock.now(), 3, 10, TimeUnit.SECONDS) //添加一個簡單觸發器,四個引數分別是:啟動時間、重復次數、周期、周期時間單位,該觸發器表示立即執行,并且重復執行三次,總共執行四次,周期為10秒
                .createNew("python", "/script", "test", "py"); // 使用給定路徑的腳本檔案創建一個腳本任務,四個引數分別是:啟動命令、腳本路徑、腳本檔案名、腳本后綴,該方法能夠創建除框架原生腳本型別以外的腳本任務

        ScriptTask task2 = new AutoJobScriptTaskBuilder()
                .setTaskId(IdGenerator.getNextIdAsLong())
                .setTaskAlias("測驗腳本任務3")
                .setTaskType(AutoJobTask.TaskType.MEMORY_TASk)
                .setBelongTo(1L)
                .addAChildTaskTrigger()  // 添加一個子任務觸發器,該任務不會自動觸發,只有當有任務主動關聯該任務作為其子任務且父任務完成一次調度時才會觸發該任務
                .createNewWithCmd("ping www.baidu.com"); // 創建一個cmd腳本任務

        ScriptTask task3 = new AutoJobScriptTaskBuilder()
                .setTaskId(IdGenerator.getNextIdAsLong())
                .setTaskAlias("測驗腳本任務4")
                .setTaskType(AutoJobTask.TaskType.MEMORY_TASk)
                .setBelongTo(1L)
                .addADelayTrigger(3, TimeUnit.MINUTES) // 添加一個延遲觸發器,任務將在給定延遲后自動觸發一次,默認使用該型別觸發器,延遲時間可以在框架配置中配置
                .createNewWithExistScriptFile(ScriptType.PYTHON, "/script", "test"); // 使用已存在的腳本創建一個腳本任務,三個引數分別是:腳本型別、腳本路徑、腳本檔案名

以上示列除了演示了如何創建一個腳本任務,也介紹了觸發器,框架提供了四種觸發器,分別是cron-like觸發器、simple觸發器、父-子任務觸發器、延遲觸發器,具體觸發器的介紹上面代碼注釋基本講解了這里就不作冗述,

2、自定義調度器

調度器的概念在第九節:框架架構里已經說明,那么怎么來自定義一個自己的調度器呢,下面做一個簡單示列:

/**
 * 你的自定義調度器
 *
 * @Author Huang Yongxiang
 * @Date 2022/08/18 14:56
 */
public class YourScheduler extends AbstractScheduler{
    //調度器默認構造方法
    public YourScheduler(AutoJobTaskExecutorPool executorPool, IAutoJobRegister register, AutoJobConfigHolder configHolder) {
        super(executorPool, register, configHolder);
    }
    
    //...調度邏輯
}

@AutoJobScan("com.example.autojob.job")
@AutoJobProcessorScan("com.example.autojob")
public class AutoJobMainApplication {
    public static void main(String[] args) {
        new AutoJobLauncherBuilder(AutoJobMainApplication.class)
                .withAutoScanProcessor()
            	//配置你的調度器,如果你的調度器支持默認構造方法可以只指定型別
                .addScheduler(YourScheduler.class)
            	//.addScheduler(new YourScheduler()) 如果不支持默認構造方法就需要添加一個實體
                .build()
                .run();
        System.out.println("==================================>系統創建完成");
    }
}

可能你希望框架只通過你的調度器來進行調度,而不再需要記憶體任務調度器或DB任務調度器,你可以在應用啟動時選擇性關閉:

@AutoJobScan("com.example.autojob.job")
@AutoJobProcessorScan("com.example.autojob")
public class AutoJobMainApplication {
    public static void main(String[] args) {
        new AutoJobBootstrap(AutoJobMainApplication.class)
                .withAutoScanProcessor()
                .closeDBTaskScheduler() // 關閉DB任務調度器
                .closeMemoryTaskScheduler() // 關閉記憶體任務調度器
                .build()
                .run();
        System.out.println("==================================>系統創建完成");
    }
}

注意!!!如果你沒有指定自己的調度器而關閉了框架原生的記憶體任務調度器或DB任務調度器,則框架會喪失該型別任務的調度功能,如果都關閉了則框架不再具有任何任務的調度功能,

3、自定義郵件報警

AutoJob中的郵件報警也是事件驅動的,框架發布相關報警事件->對應處理器創建郵件物件->發送,因此要實作自定義的郵件報警,只需要實作:自定義的報警事件、何時發布事件、報警事件處理器(模板的創建),

所有的報警事件都繼承于AlertEvent,下面我們看一下框架的任務運行錯誤報警的實作方式:

//定義報警事件
@Getter
@Setter
public class TaskRunErrorAlertEvent extends AlertEvent {
    public TaskRunErrorAlertEvent(String title, String content, AutoJobTask errorTask) {
        super(title, AlertEventLevel.WARN, content);
        this.errorTask = errorTask;
    }
    private AutoJobTask errorTask;
    private String stackTrace;
}

//報警事件的郵件模板創建
public static AlertMail newRunErrorAlertMail(TaskRunErrorAlertEvent event) {
        AlertMailBuilder builder = AlertMailBuilder.newInstance();
        AutoJobTask errorTask = event.getErrorTask();
        return builder
            	.setMailClient(errorTask.getMailClient())
                .setTitle(event.getTitle())
                .setLevel(AlertEventLevel.WARN)
                .addContentTitle(String.format("任務:\"%d:%s\"執行失敗", errorTask.getId(), errorTask.getAlias()), 1)
                .addBr()
                .addBold("報警時間:" + DateUtils.formatDateTime(event.getPublishTime()))
                .addBr()
                .addBold(String.format("報警機器:%s:%s", event
                        .getNode()
                        .getHost(), event
                        .getNode()
                        .getPort()))
                .addBr()
                .addBold("任務路徑:" + errorTask.getReference())
                .addBr()
                .addParagraph("堆疊資訊如下:")
                .addParagraph(event
                        .getStackTrace()
                        .replace("\n", "</br>"))
                .addError("請及時處理")
                .getAlertMail();
}

//事件處理器
@Slf4j
public class TaskRunErrorAlertEventHandler implements IAlertEventHandler<TaskRunErrorAlertEvent> {
    @Override
    public void doHandle(TaskRunErrorAlertEvent event) {
        AutoJobConfig config = AutoJobApplication.getInstance().getConfigHolder().getAutoJobConfig();
        if (!config.getTaskRunErrorAlert()) {
            return;
        }
        AlertMail alertMail = AlertMailFactory.newRunErrorAlertMail(event);
        if (alertMail != null) {
            if (alertMail.send()) {
                log.info("發送報警郵件成功");
            } else {
                log.error("發送報警郵件失敗");
            }
        }
    }
}

//事件處理器添加進背景關系
public class AlertEventHandlerLoader implements IAutoJobLoader {
    @Override
    public void load() {
        TaskEventHandlerDelegate
                .getInstance()
                .addHandler(TaskRunErrorEvent.class, new TaskRunErrorEventHandler());
    }
}

//事件發布
public class TaskRunErrorEventHandler implements ITaskEventHandler<TaskRunErrorEvent> {
    @Override
    public void doHandle(TaskRunErrorEvent event) {
        AlertEventHandlerDelegate
                .getInstance()
                .doHandle(AlertEventFactory.newTaskRunErrorAlertEvent(event.getTask(), event.getErrorStack()));
    }
}

上面的代碼大家需要關注幾個地方:AlertMailBuilder是一個郵件模板構建類,可以構建一個郵件物件;報警事件處理器和任務事件處理器一樣需要通過Processor添加進背景關系,

4、自定義日志存盤

框架默認日志的存盤位置是資料庫,你可以自己定義相關的存盤策略和存盤策略委派者,來實作日志在其他地方的存盤,下面來簡單演示:

//定義日志存盤策略

/**
 * 運行日志檔案保存策略
 *
 * @Date 2022/11/21 9:15
 */
public class AutoJobLogFileStrategy implements IAutoJobLogSaveStrategy<AutoJobLog> {
    @Override
    public void doHandle(String taskPath, List<AutoJobLog> logList) {
        try {
            FileWriter fileWriter = new FileWriter(new File(taskPath));
            //...
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
/**
 * 執行日志檔案保存策略
 *
 * @Date 2022/11/21 9:15
 */
public class AutoJobRunLogFileStrategy implements IAutoJobLogSaveStrategy<AutoJobRunLog> {
    @Override
    public void doHandle(String taskPath, List<AutoJobRunLog> logList) {
        try {
            FileWriter fileWriter = new FileWriter(new File(taskPath));
            //...
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}


//設定策略委派
public class AutoJobLogFileDelegate implements ILogSaveStrategyDelegate<AutoJobLog> {
    @Override
    public IAutoJobLogSaveStrategy<AutoJobLog> doDelegate(AutoJobConfigHolder configHolder, Class<AutoJobLog> type) {
        //默認使用File保存策略
        return new AutoJobLogFileStrategy();
    }
}

public class AutoJobRunLogFileDelegate implements ILogSaveStrategyDelegate<AutoJobRunLog> {
    @Override
    public IAutoJobLogSaveStrategy<AutoJobRunLog> doDelegate(AutoJobConfigHolder configHolder, Class<AutoJobRunLog> type) {
        //默認使用File保存策略
        return new AutoJobRunLogFileStrategy();
    }
}

以上代碼定義好了檔案的存盤策略,那么如何使得我們的策略生效呢,這就需要我們再創建任務時把我們的策略委派給添加進背景關系

public static void main(String[] args) {
        new AutoJobBootstrap(AutoJobMainApplication.class)
                .setLogSaveStrategyDelegate(new AutoJobLogFileDelegate()) //設定運行日志存盤策略委派者
                .setRunLogSaveStrategyDelegate(new AutoJobRunLogFileDelegate()) //設定執行日志存盤策略委派者
                .build()
                .run();
}

將我們的日志存盤策略委派者設定進去后,原有的存盤策略就會被覆寫,當然如果你的委派者邏輯里面回傳了AutoJobLogDBStrategy等原生的保存策略除外,

5、自定義任務過濾器

任務過濾器是一個過濾器鏈,在任務注冊進任務調度佇列前執行,主要功能是用于過濾某些不安全的任務的執行,框架提供了基于類路徑過濾的任務過濾器ClassPathFilter,但是白名單只能在組態檔配置,因此很可能你希望實作一個動態的白名單配置,比如從資料庫比對等等,這時你就需要繼承AbstractRegisterFilter,如下示列:

//package com.example.spring.job
public class TestFilter extends AbstractRegisterFilter {
    @Override
    public void doHandle(AutoJobTask task) {
       if(/*...*/){
            //當該任務不允許注冊時直接設定成不允許注冊
            task.setIsAllowRegister(false);
        }
    }
}

@SpringBootApplication
@AutoJobScan("com.example.spring.job")
@AutoJobRegisterPreProcessorScan("com.example.spring") //指定掃描包
public class AutoJobSpringApplication {
    public static void main(String[] args) {
        SpringApplication.run(AutoJobSpringApplication.class, args);
        System.out.println("==================================>Spring應用已啟動完成");
        new AutoJobBootstrap(AutoJobSpringApplication.class)
                .withAutoScanProcessor()
                .build()
                .run();
        System.out.println("==================================>AutoJob應用已啟動完成");
    }

}

在創建應用時還需要在入口類上配置@AutoJobRegisterPreProcessorScan,指定注冊前置處理器的掃描包路徑,否則該過濾器不會被掃描到,

注意:子任務不會被該類過濾器處理,

6、注解任務開發的高級應用

在第三章節-第三小節-基于注解中,簡單演示了注解@AutoJob的用法,AutoJob框架還提供了其他注解,如@FactoryAutoJob@Conditional等,下面一一講解,

@AutoJob注解

@Autojob注解是框架中使用最多的一個注解,將其標注在一個方法上,配置好調度資訊,該方法就會在應用啟動時將其包裝成一個方法型任務放到對應的任務容器,可以參考下下面的示列,

@Slf4j
public class Jobs {
    private static final AutoJobLogHelper logHelper = new AutoJobLogHelper();

    static {
        logHelper.setSlf4jProxy(log);
    }

    //立即啟動,重復無限次,周期為5秒,使用自定義方法運行物件工廠,引數為"hello autoJob"
    @AutoJob(id = 1, attributes = "{'hello autoJob'}", defaultStartTime = StartTime.NOW, repeatTimes = -1, cycle = 5, methodObjectFactory = SpringMethodObjectFactory.class)
    public void hello(String str) {
        logHelper.info(str);
    }

    //2022-11-21 12:00:00啟動,重復3次,總共執行4次,周期為10秒,作為DB任務調度,最長允許運行時長5秒
   @AutoJob(id = 2, startTime = "2022-11-21 12:00:00", repeatTimes = 3, cycle = 10, asType = AutoJobTask.TaskType.DB_TASK, maximumExecutionTime = 5000)
    public void longTask() {
        logHelper.info("long task start");
        SyncHelper.sleepQuietly(10, TimeUnit.SECONDS);
        logHelper.info("long task end");
    }

    //作為子任務調度
    @AutoJob(id = 3, schedulingStrategy = SchedulingStrategy.AS_CHILD_TASK)
    public void childTask() {
        logHelper.info("child task start");
        SyncHelper.sleepQuietly(3, TimeUnit.SECONDS);
        logHelper.info("child task end");
    }

    //按照cron like運算式調度,重復無限次,子任務為3
    @AutoJob(id = 4, alias = "獲取隨機字串", cronExpression = "* * 0/5 17 * * ?", repeatTimes = -1, childTasksId = "3")
    public String getRandomString() {
        return StringUtils.getRandomStr(16);
    }
    
    //僅保存到資料庫
    @AutoJob(id = 4, schedulingStrategy = SchedulingStrategy.ONLY_SAVE)
    public void error() {
        String str = null;
        str.length();
    }	
}

@FactoryAutoJob注解

由于@AutoJob的配置都是固定的,可能你希望能夠動態配置任務的某些屬性,因此@FactoryAutoJob就為了解決此類場景而出現的,當然你也可以使用基于構建的方式開發任務來實作動態,下面來看一個示列:

@FactoryAutoJob(RandomStringMethodFactory.class)
public String getRandomString() {
    return StringUtils.getRandomStr(16);
}

public class RandomStringMethodFactory implements IMethodTaskFactory {
    @Override
    public MethodTask newTask(AutoJobConfigHolder configHolder, Method method) {
        return new AutoJobMethodTaskBuilder(method.getDeclaringClass(), method.getName())
                .setTaskId(IdGenerator.getNextIdAsLong())
            	//...
                .build();
    }
}

如上示列,getRandomString的包裝將由RandomStringMethodFactory來進行,

@Conditional注解

相信經常使用Spring的小可耐們對此注解應該熟悉,在Spring中,該注解用于實作條件注入,即符合條件時該Bean才會注入到容器,在AutoJob中,功能類似,只有符合該注解指定條件的方法才能被包裝成一個任務,

7、使用內置RPC框架

AutoJob的目標是一款分布式的任務調度框架,因此內部開發了通信框架:RPC, 這里只做簡單介紹,后期會基于該RPC開發分布式的AutoJob,每一個AutoJob都有服務端和客戶端,服務端的開啟可以通過在組態檔里cluster.enable=true開啟,要使用RPC框架首先需要開發服務提供類,如框架自帶的API:

@AutoJobRPCService("MemoryTaskAPI") //通過該注解宣告該類是一個RPC服務提供方
@Slf4j
public class MemoryTaskAPI implements AutoJobAPI {
    //...細節省略
    @Override
    @RPCMethod("count") //宣告該方法對外提供的方法名
    //服務方法回傳值和引數都得是包裝型別
    public Integer count() {
        //...
    }
}

其他AutoJob節點如何呼叫該服務呢,也非常簡單,如下示列:

@AutoJobRPCClient("MemoryTaskAPI") //宣告該介面是一個RPC客戶端
public class MemoryTaskAPIClient{
    //方法名同服務對外提供方法名相同
    Integer count();
}

RPCClientProxy<MemoryTaskAPIClient> proxy = new RPCClientProxy<>("localhost", 7777, MemoryTaskAPIClient.class); //創建介面代理
MemoryTaskAPIClient client = proxy.clientProxy(); //獲取代理實體
System.out.println(client.count()); //像本地方法一樣使用

內嵌RPC框架基于netty開發,使用JSON進行序列化和反序列化,基礎資料型別僅支持包裝型別,即如int需要使用Integer,集合支持Map和List,支持泛型,目前RPC僅供學習使用,

8、使用基于時間動態調整的執行緒池封裝

框架的執行池AutoJobTaskExecutorPool是任務執行的地方,其包含一個快池和一個慢池,分別用于執行運行時間短和運行時間長的任務,框架任務執行原生使用的是兩個基于流量動態更新的執行緒池FlowThreadPoolExecutorHelper,為了更加適應業務需求,提供基于時間動態調整的執行緒池TimerThreadPoolExecutorPool

TimerThreadPoolExecutorHelper.TimerEntry entry = new TimerThreadPoolExecutorHelper.TimerEntry("0 0 7 * * ?", 10, 20, 60, TimeUnit.SECONDS);//配置調整項,<0的項不作調整
		//添加一個觸發監聽器
        entry.setTriggerListener((cronExpression, threadPoolExecutor) -> {
            System.out.println("日間執行緒池調整");
        });
        TimerThreadPoolExecutorHelper fastPool = TimerThreadPoolExecutorHelper
                .builder()
                .setInitialCoreTreadCount(3)
                .setInitialMaximizeTreadCount(5)
                .setTaskQueueCapacity(100)
                .addTimerEntry("0 0 22 * * ?", 0, 1, -1, null)
                .addTimerEntry(entry)
                .build();
        new AutoJobBootstrap(AutoJobSpringApplication.class)
                .withAutoScanProcessor()
            	//自定義執行池
                .setExecutorPool(new AutoJobTaskExecutorPool(null, fastPool, FlowThreadPoolExecutorHelper
                        .builder()
                        .build()))
                .build()
                .run();
        System.out.println("==================================>AutoJob應用已啟動完成");

lExecutorPool`,

TimerThreadPoolExecutorHelper.TimerEntry entry = new TimerThreadPoolExecutorHelper.TimerEntry("0 0 7 * * ?", 10, 20, 60, TimeUnit.SECONDS);//配置調整項,<0的項不作調整
		//添加一個觸發監聽器
        entry.setTriggerListener((cronExpression, threadPoolExecutor) -> {
            System.out.println("日間執行緒池調整");
        });
        TimerThreadPoolExecutorHelper fastPool = TimerThreadPoolExecutorHelper
                .builder()
                .setInitialCoreTreadCount(3)
                .setInitialMaximizeTreadCount(5)
                .setTaskQueueCapacity(100)
                .addTimerEntry("0 0 22 * * ?", 0, 1, -1, null)
                .addTimerEntry(entry)
                .build();
        new AutoJobBootstrap(AutoJobSpringApplication.class)
                .withAutoScanProcessor()
            	//自定義執行池
                .setExecutorPool(new AutoJobTaskExecutorPool(null, fastPool, FlowThreadPoolExecutorHelper
                        .builder()
                        .build()))
                .build()
                .run();
        System.out.println("==================================>AutoJob應用已啟動完成");

如上示列,快池使用基于時間動態調整的執行緒池封裝,其會在每天早上七點將執行緒池擴容到核心10執行緒,最大20執行緒,核心空閑時長更新為60秒,在每晚十點將執行緒池縮容到核心0執行緒,最大1執行緒并且添加了一個觸發監聽器;慢池使用基于流量調整執行緒池封裝,

如果對你有幫助,謝謝點贊、收藏、Star ??·??·??*?? ??

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/541230.html

標籤:其他

上一篇:Jupyter Notebook入門指南

下一篇:工廠模式C++實作 (內附簡單原始碼實作)

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 面試突擊第一季,第二季,第三季

    第一季必考 https://www.bilibili.com/video/BV1FE411y79Y?from=search&seid=15921726601957489746 第二季分布式 https://www.bilibili.com/video/BV13f4y127ee/?spm_id_fro ......

    uj5u.com 2020-09-10 05:35:24 more
  • 第三單元作業總結

    1.前言 這應該是本學期最后一次寫作業總結了吧。總體來說,對作業的節奏也差不多掌握了,作業做起來的效率也更高了。雖然和之前的作業一樣,作業中都要用到新的知識,但是相比之前,更加懂得了如何利用工具以及資料。雖然之間卡過殼,但總體而言,這幾次作業還算完成的比較好。 2.作業程序總結 相比前兩個單元,此單 ......

    uj5u.com 2020-09-10 05:35:41 more
  • 北航OO(2020)第四單元博客作業暨課程總結博客

    北航OO(2020)第四單元博客作業暨課程總結博客 本單元作業的架構設計 在本單元中,由于UML圖具有比較清晰的樹形結構,因此我對其中需要進行查詢操作的元素進行了包裝,在樹的父節點中存盤所有孩子的參考。考慮到性能問題,我采用了快取機制,一次查詢后盡可能快取已經遍歷過的資訊,以減少遍歷次數。 本單元我 ......

    uj5u.com 2020-09-10 05:35:48 more
  • BUAA_OO_第四單元

    一、UML決議器設計 ? 先看下題目:第四單元實作一個基于JDK 8帶有效性檢查的UML(Unified Modeling Language)類圖,順序圖,狀態圖分析器 MyUmlInteraction,實際上我們要建立一個有向圖模型,UML中的物件(元素)可能與同級元素連接,也可與低級元素相連形成 ......

    uj5u.com 2020-09-10 05:35:54 more
  • 6.1邏輯運算子

    邏輯運算子 1. && 短路與 運算式1 && 運算式2 01.運算式1為true并且運算式2也為true 整體回傳為true 02.運算式1為false,將不會執行運算式2 整體回傳為false 03.只要有一個運算式為false 整體回傳為false 2. || 短路或 運算式1 || 運算式2 ......

    uj5u.com 2020-09-10 05:35:56 more
  • BUAAOO 第四單元 & 課程總結

    1. 第四單元:StarUml檔案決議 本單元采用了圖模型決議UML。 UML檔案可以抽象為圖、子圖、邊的邏輯結構。 在實作中,圖的節點包括類、介面、屬性,子圖包括狀態圖、順序圖等。 采用了三次遍歷UML元素的方法建圖,第一遍遍歷建點,第二、三次遍歷設定屬性、連邊,實作圖物件的初始化。這里借鑒了一些 ......

    uj5u.com 2020-09-10 05:36:06 more
  • 談談我對C# 多型的理解

    面向物件三要素:封裝、繼承、多型。 封裝和繼承,這兩個比較好理解,但要理解多型的話,可就稍微有點難度了。今天,我們就來講講多型的理解。 我們應該經常會看到面試題目:請談談對多型的理解。 其實呢,多型非常簡單,就一句話:呼叫同一種方法產生了不同的結果。 具體實作方式有三種。 一、多載 多載很簡單。 p ......

    uj5u.com 2020-09-10 05:36:09 more
  • Python 資料驅動工具:DDT

    背景 python 的unittest 沒有自帶資料驅動功能。 所以如果使用unittest,同時又想使用資料驅動,那么就可以使用DDT來完成。 DDT是 “Data-Driven Tests”的縮寫。 資料:http://ddt.readthedocs.io/en/latest/ 使用方法 dd. ......

    uj5u.com 2020-09-10 05:36:13 more
  • Python里面的xlrd模塊詳解

    那我就一下面積個問題對xlrd模塊進行學習一下: 1.什么是xlrd模塊? 2.為什么使用xlrd模塊? 3.怎樣使用xlrd模塊? 1.什么是xlrd模塊? ?python操作excel主要用到xlrd和xlwt這兩個庫,即xlrd是讀excel,xlwt是寫excel的庫。 今天就先來說一下xl ......

    uj5u.com 2020-09-10 05:36:28 more
  • 當我們創建HashMap時,底層到底做了什么?

    jdk1.7中的底層實作程序(底層基于陣列+鏈表) 在我們new HashMap()時,底層創建了默認長度為16的一維陣列Entry[ ] table。當我們呼叫map.put(key1,value1)方法向HashMap里添加資料的時候: 首先,呼叫key1所在類的hashCode()計算key1 ......

    uj5u.com 2020-09-10 05:36:38 more
最新发布
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:20:47 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:20:25 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:20:17 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:20:10 more
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:19:44 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:19:07 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:18:57 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:18:49 more
  • 05單件模式

    #經典的單件模式 public class Singleton { private static Singleton uniqueInstance; //一個靜態變數持有Singleton類的唯一實體。 // 其他有用的實體變數寫在這里 //構造器宣告為私有,只有Singleton可以實體化這個類! ......

    uj5u.com 2023-04-19 08:42:51 more
  • 【架構與設計】常見微服務分層架構的區別和落地實踐

    軟體工程的方方面面都遵循一個最基本的道理:沒有銀彈,架構分層模型更是如此,每一種都有各自優缺點,所以請根據不同的業務場景,并遵循簡單、可演進這兩個重要的架構原則選擇合適的架構分層模型即可。 ......

    uj5u.com 2023-04-19 08:42:41 more