Auto-Job任務調度框架

来源:https://www.cnblogs.com/hyxl-top/archive/2023/01/04/17024129.html
-Advertisement-
Play Games

AutoJob是一款輕量級任務調度框架,具有分散式、全非同步、易拓展、易集成等特點,提供多種任務調度模式和多種任務類型。配置豐富、拓展方便、使用簡單、代碼侵入性低。 ...


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 ₍˄·͈༝·͈˄*₎◞ ̑̑


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • vivo 實時計算平臺是 vivo 實時團隊基於 Apache Flink 計算引擎自研的覆蓋實時流數據接入、開發、部署、運維和運營全流程的一站式數據建設與治理平臺。 ...
  • 之前數據分層處理,最後把輕度聚合的結果保存到 ClickHouse 中,主要的目的就是提供即時的數據查詢、統計、分析服務。這些統計服務一般會用兩種形式展現,一種是為專業的數據分析人員的 BI 工具,一種是面向非專業人員的更加直觀的數據大屏。 以下主要是面向百度的 sugar 的數據大屏服務的介面開發 ...
  • 大數據 ODS&DWD&DIM-SQL分享 需求 思路一:等差數列 斷2天、3天,嵌套太多 1.1 開窗,按照 id 分組,同時按照 dt 排序,求 Rank -- linux 中空格不能用 tab 鍵 select id,dt,rank() over(partition by id order b ...
  • 當我在實現線上客服源碼彈窗效果JavaScript SDK時,對外公開的SDK代碼就是使用的自執行函數的形式。 使用自執行函數來實現 JavaScript SDK 有以下好處: 封裝代碼:自執行函數可以將你的 JavaScript 代碼封裝起來,從而避免在全局作用域中定義變數,防止變數名稱衝突。 提 ...
  • 我們有時候會不知道斷點打在什麼地方,比如想知道dom什麼時候被修改的,網路請求在哪裡 ,什麼情況才斷點。類似情況有很多,需要對應使用不同的打斷點方式才可以提高效率,本文演示已VSCode Debugger為主,其實跟chrome是大同小異,不過更加直觀方便。可以參考該文章 【前端調試】- 更好的調試 ...
  • 事件委托與事件對象 事件冒泡與事件捕獲 事件流:用於描述頁面接收事件的順序。以下是事件流的兩種不同方案: 事件冒泡:事件由最具體的元素逐級向上傳遞到最不具體的元素。 事件捕獲:事件由最不具體的元素逐級向下傳遞到最具體的元素。 以上的兩種事件流方案是截然相反的,分別由IE開發團隊和Netscape開發 ...
  • var request = new XMLHttpRequest(); //請求種類和地址和.......(屑阿狗忘了,但暫時沒用 request.open('GET', '這裡填寫介面地址', true); //返回格式,json是js對象的存儲 request.responseType = 'j ...
  • 抽象工廠模式 為什麼要用抽象工廠模式? * 舉個實際應用的例子,一個顯示器電路板廠商,旗下的顯示器電路板種類有非液晶的和液晶的;這個時候,廠商建造兩個工廠,工廠A負責生產非液晶顯示器電路板,工廠B負責生產液晶顯示器電路板;工廠一直就這樣運行著。有一天,總經理髮現,直接生產顯示器的其餘部分也挺掙錢,所 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...