天天看點

定時任務架構選型Quartz/Xxl-Job

作者:一隻程式猿粑粑
以前公司平台中內建了定時任務功能,但平台内部實作比較簡單,使用方式有些受限,比如說無法跟蹤定時任務執行狀态,無法自動解決叢集狀态下的任務争搶問題,是以考慮更新一下任務實作方式,搜集一番後,Quartz和Xxl-Job都能滿足現在的需求;

以下就對兩種定時任務架構進行簡單說明。

Quartz

定時任務架構選型Quartz/Xxl-Job

傳送門

github位址:https://github.com/quartz-scheduler/quartz

Maven坐标

<!-- Quartz Core -->
<dependency>
  <groupId>org.quartz-scheduler</groupId>
  <artifactId>quartz</artifactId>
  <version>2.3.0</version>
</dependency>           

可以檢視jar包的依賴情況如下:

定時任務架構選型Quartz/Xxl-Job

如果不是maven項目,單獨下載下傳jar包使用的情況,不要漏掉jar包;可通過官網下載下傳的方式擷取到所有的jar包;

官網不知道怎麼回事兒,超級慢;

我一般的做法是,本地建立一個單獨的maven項目,加入相關maven依賴,然後通過下面的maven指令擷取到所有pom.xml檔案中的jar包;然後再複制到自己需要放的地方;

mvn dependency:copy-dependencies -DoutputDirectory=lib

配置檔案

最終編譯後的位置:WEB-INF/classes/quartz.properties

下面是一個最基本的配置項内容:

org.quartz.scheduler.instanceName = MyScheduler
org.quartz.threadPool.threadCount = 3
org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore           

其中

此配置建立的排程器有以下特點:

  • org.quartz.scheduler.instanceName -這個排程程式的名稱将是“MyScheduler”。
  • org.quartz.threadPool.threadCount- 線程池中有3個線程,這意味着最多可以同時運作3個任務。
  • org.quartz.jobStore.class - Quartz的所有資料,比如任務和觸發器的細節,都儲存在記憶體中(而不是資料庫中)。即使你有一個資料庫,并且想要在Quartz上使用它,我建議你先讓Quartz和RamJobStore一起使用,然後再用資料庫打開一個全新的次元。

任務資訊

任務資訊處理類實作了org.quartz.Job 接口;如下

public class HelloJob implements Job {
    @Override
    public void execute(JobExecutionContext jobExecutionContext) 
        throws JobExecutionException {
        System.out.println("hello @ " 
                           + LocalDateTime
                           .now()
                           .format(DateTimeFormatter
                                   .ofPattern("yyyyMMddHHmmss")));
    }
}           

任務排程

一旦使用StdSchedulerFactory.getDefaultScheduler()獲得一個排程器,您的應用程式将不會終止,直到您調用schedul. shutdown(),因為将有活動線程。

注意代碼示例中的靜态導入 ;

這些将在下面的代碼示例中發揮作用。

更詳細地配置檔案說明在這兒:

https://github.com/quartz-scheduler/quartz/blob/master/docs/configuration.adoc

import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.impl.StdSchedulerFactory;

import static org.quartz.JobBuilder.*;
import static org.quartz.TriggerBuilder.*;
import static org.quartz.SimpleScheduleBuilder.*;

public class QuartzTest {

    public static void main(String[] args) throws InterruptedException {

        try {
            // Grab the Scheduler instance from the Factory
            Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

            // and start it off
            scheduler.start();

            // define the job and tie it to our HelloJob class
            JobDetail job = newJob(HelloJob.class)
                    .withIdentity("job1", "group1")
                    .build();

            // Trigger the job to run now, and then repeat every 40 seconds
            Trigger trigger = newTrigger()
                    .withIdentity("trigger1", "group1")
                    .startNow()
                    .withSchedule(simpleSchedule()
                            .withIntervalInSeconds(10)
                            .repeatForever())
                    .build();

            // Tell quartz to schedule the job using our trigger
            scheduler.scheduleJob(job, trigger);

            Thread.sleep(60 * 60 * 1000);

            scheduler.shutdown();

        } catch (SchedulerException se) {
            se.printStackTrace();
        }
    }
}           

叢集下的任務排程

資料庫表

表檔案在jar包的org.quartz.impl.jdbcjobstore,可根據資料庫類型選擇不同的資料庫檔案;

定時任務架構選型Quartz/Xxl-Job

quartz也提供了資料庫方面的任務配置及叢集下的任務處理;

#==============================================================
#Configure Main Scheduler Properties
#org.quartz.scheduler.instanceName【相同業務部署保持該配置一緻】
#==============================================================
org.quartz.scheduler.instanceName = quartzScheduler
org.quartz.scheduler.instanceId = AUTO

#==============================================================
#Configure JobStore
#org.quartz.jobStore.tablePrefix:表名字首
#org.quartz.jobStore.dataSource:對應org.quartz.dataSource.xxx下面的配置資訊
#==============================================================
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.tablePrefix = QRTZ_
org.quartz.jobStore.isClustered = true
org.quartz.jobStore.clusterCheckinInterval = 10000
org.quartz.jobStore.dataSource = myDS

#==============================================================
#Configure DataSource
#可以通過配置詳細資料源的方式,也可以通過配置連接配接池的方式
#org.quartz.dataSource.qzDS.jndiURL = java:/comp/env/jdbc/mydb
#我是用的weblogic中間件直接配置weblogic連接配接池的jndi即可,例如我的jndi名稱為jdbc/isp
#是以我的配置如下:
#org.quartz.dataSource.qzDS.jndiURL=jdbc/isp
#
#org.quartz.dataSource.myDS.driver為資料庫驅動名稱,
#根據項目中實際需要修改為自己的資料庫驅動名稱即可
#==============================================================
org.quartz.dataSource.myDS.driver = com.ibm.db2.jcc.DB2Driver
org.quartz.dataSource.myDS.URL = 
org.quartz.dataSource.myDS.user = 
org.quartz.dataSource.myDS.password = 
org.quartz.dataSource.myDS.maxConnections = 30

#==============================================================
#Configure ThreadPool
#==============================================================
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount = 5
org.quartz.threadPool.threadPriority = 5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true           

crontab任務建立

// 建構job資訊
JobDetail jobDetail = JobBuilder.newJob(jobClass)
        .withIdentity(jobName, jobGroup)
        .withDescription(description)
        .build();
// 表達式排程建構器
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);
scheduleBuilder.withMisfireHandlingInstructionDoNothing();
// 按新的cronExpression表達式建構一個新的trigger
CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(jobName, jobGroup).withSchedule(scheduleBuilder)
        .build();
if (!params.isEmpty()) {
    // 放入參數,運作時的方法可以擷取
    jobDetail.getJobDataMap().putAll(params);
}
Date date = scheduler.scheduleJob(jobDetail, trigger);           

Xxl-job

說明

XXL-JOB是一個分布式任務排程平台,其核心設計目标是開發迅速、學習簡單、輕量級、易擴充。

官網:https://www.xuxueli.com/xxl-job/

gitee傳送門:https://gitee.com/xuxueli0323/xxl-job/tree/master

maven坐标

<!-- https://mvnrepository.com/artifact/com.xuxueli/xxl-job-core -->
<dependency>
    <groupId>com.xuxueli</groupId>
    <artifactId>xxl-job-core</artifactId>
    <version>2.3.0</version>
</dependency>           

快速入門

git clone https://gitee.com/xuxueli0323/xxl-job.git

獲得到目錄結構

定時任務架構選型Quartz/Xxl-Job

初始化資料庫

/xxl-job/doc/db/tables_xxl_job.sql

編譯源碼

xxl-job-admin:排程中心

xxl-job-core:公共依賴

xxl-job-executor-samples:執行器Sample示例(選擇合适的版本執行器,可直接使用,也可以參考其并将現有項目改造成執行器)

:xxl-job-executor-sample-springboot:Springboot版本,通過Springboot管理執行器,推薦這種方式;

:xxl-job-executor-sample-frameless:無架構版本;

配置部署排程中心

排程中心項目:xxl-job-admin

作用:統一管理任務排程平台上的排程任務,負責觸發排程執行,并且提供任務管理平台。

排程中心配置檔案位址:

/xxl-job/xxl-job-admin/src/main/resources/application.properties【修改資料庫配置】

/xxl-job/xxl-job-admin/src/main/resources/logback.xml

完成上述修改後,然後運作XxlJobAdminApplication

定時任務架構選型Quartz/Xxl-Job

運作成功後通過浏覽器打開:http://localhost:8080/xxl-job-admin/,使用者名及密碼:admin/123456

定時任務架構選型Quartz/Xxl-Job

至此“排程中心”項目已經部署成功。

配置部署執行器項目

執行器項目:xxl-job-executor-sample-springboot (提供多種版本執行器供選擇,現以 springboot 版本為例,可直接使用,也可以參考其并将現有項目改造成執行器)

作用:負責接收“排程中心”的排程并執行;可以直接部署執行器,也可以将執行器內建到現有業務項目中。

執行器配置

配置檔案位址:

/xxl-job/xxl-job-executor-samples/xxl-job-executor-sample-springboot/src/main/resources/

application.properties

修改:xxl.job.executor.logpath為本地路徑

logback.xml

修改日志路徑為本地路徑

建立任務

定時任務架構選型Quartz/Xxl-Job

觸發執行

請點選任務右側 “執行” 按鈕,可手動觸發一次任務執行(通常情況下,通過配置Cron表達式進行任務排程觸發)。

檢視日志

請點選任務右側 “日志” 按鈕,可前往任務日志界面檢視任務日志。

在任務日志界面中,可檢視該任務的曆史排程記錄以及每一次排程的任務排程資訊、執行參數和執行資訊。運作中的任務點選右側的“執行日志”按鈕,可進入日志控制台檢視實時執行日志。

磁盤上的日志檔案路徑在xxl.job.executor.logpath

定時任務架構選型Quartz/Xxl-Job

更多

基礎配置:
- 執行器:任務的綁定的執行器,任務觸發排程時将會自動發現注冊成功的執行器, 實作任務自動發現功能; 另一方面也可以友善的進行任務分組。每個任務必須綁定一個執行器, 可在 "執行器管理" 進行設定;
- 任務描述:任務的描述資訊,便于任務管理;
- 負責人:任務的負責人;
- 報警郵件:任務排程失敗時郵件通知的郵箱位址,支援配置多郵箱位址,配置多個郵箱位址時用逗号分隔;

觸發配置:
- 排程類型:
    無:該類型不會主動觸發排程;
    CRON:該類型将會通過CRON,觸發任務排程;
    固定速度:該類型将會以固定速度,觸發任務排程;按照固定的間隔時間,周期性觸發;
    固定延遲:該類型将會以固定延遲,觸發任務排程;按照固定的延遲時間,從上次排程結束後開始計算延遲時間,到達延遲時間後觸發下次排程;
- CRON:觸發任務執行的Cron表達式;
- 固定速度:固件速度的時間間隔,機關為秒;
- 固定延遲:固件延遲的時間間隔,機關為秒;

任務配置:
- 運作模式:
    BEAN模式:任務以JobHandler方式維護在執行器端;需要結合 "JobHandler" 屬性比對執行器中任務;
    GLUE模式(Java):任務以源碼方式維護在排程中心;該模式的任務實際上是一段繼承自IJobHandler的Java類代碼并 "groovy" 源碼方式維護,它在執行器項目中運作,可使用@Resource/@Autowire注入執行器裡中的其他服務;
    GLUE模式(Shell):任務以源碼方式維護在排程中心;該模式的任務實際上是一段 "shell" 腳本;
    GLUE模式(Python):任務以源碼方式維護在排程中心;該模式的任務實際上是一段 "python" 腳本;
    GLUE模式(PHP):任務以源碼方式維護在排程中心;該模式的任務實際上是一段 "php" 腳本;
    GLUE模式(NodeJS):任務以源碼方式維護在排程中心;該模式的任務實際上是一段 "nodejs" 腳本;
    GLUE模式(PowerShell):任務以源碼方式維護在排程中心;該模式的任務實際上是一段 "PowerShell" 腳本;
- JobHandler:運作模式為 "BEAN模式" 時生效,對應執行器中新開發的JobHandler類“@JobHandler”注解自定義的value值;
- 執行參數:任務執行所需的參數;     

進階配置:
- 路由政策:當執行器叢集部署時,提供豐富的路由政策,包括;
    FIRST(第一個):固定選擇第一個機器;
    LAST(最後一個):固定選擇最後一個機器;
    ROUND(輪詢):;
    RANDOM(随機):随機選擇線上的機器;
    CONSISTENT_HASH(一緻性HASH):每個任務按照Hash算法固定選擇某一台機器,且所有任務均勻散列在不同機器上。
    LEAST_FREQUENTLY_USED(最不經常使用):使用頻率最低的機器優先被選舉;
    LEAST_RECENTLY_USED(最近最久未使用):最久未使用的機器優先被選舉;
    FAILOVER(故障轉移):按照順序依次進行心跳檢測,第一個心跳檢測成功的機器標明為目标執行器并發起排程;
    BUSYOVER(忙碌轉移):按照順序依次進行空閑檢測,第一個空閑檢測成功的機器標明為目标執行器并發起排程;
    SHARDING_BROADCAST(分片廣播):廣播觸發對應叢集中所有機器執行一次任務,同時系統自動傳遞分片參數;可根據分片參數開發分片任務;
- 子任務:每個任務都擁有一個唯一的任務ID(任務ID可以從任務清單擷取),當本任務執行結束并且執行成功時,将會觸發子任務ID所對應的任務的一次主動排程。
- 排程過期政策:
    - 忽略:排程過期後,忽略過期的任務,從目前時間開始重新計算下次觸發時間;
    - 立即執行一次:排程過期後,立即執行一次,并從目前時間開始重新計算下次觸發時間;
- 阻塞處理政策:排程過于密集執行器來不及處理時的處理政策;
    單機串行(預設):排程請求進入單機執行器後,排程請求進入FIFO隊列并以串行方式運作;
    丢棄後續排程:排程請求進入單機執行器後,發現執行器存在運作的排程任務,本次請求将會被丢棄并标記為失敗;
    覆寫之前排程:排程請求進入單機執行器後,發現執行器存在運作的排程任務,将會終止運作中的排程任務并清空隊列,然後運作本地排程任務;
- 任務逾時時間:支援自定義任務逾時時間,任務運作逾時将會主動中斷任務;
- 失敗重試次數;支援自定義任務失敗重試次數,當任務失敗時将會按照預設的失敗重試次數主動進行重試;           
你看,奇怪的知識又增加了!