Spring Boot 定時任務入門

1. 概述

在產品的色彩斑斕的黑的需求中,有存在一類需求,是需要去定時執行的,此時就需要使用到定時任務。例如說,每分鐘掃描超時支付的訂單,每小時清理一次日志文件,每天統計前一天的數據并生成報表,每個月月初的工資單的推送,每年一次的生日提醒等等。

在 JDK 中,內置了兩個類,可以實現定時任務的功能:

  • java.util.Timer :可以通過創建 java.util.TimerTask 調度任務,在同一個線程中串行執行,相互影響。也就是說,對于同一個 Timer 里的多個 TimerTask 任務,如果一個 TimerTask 任務在執行中,其它 TimerTask 即使到達執行的時間,也只能排隊等待。因為 Timer 是串行的,同時存在 坑坑 ,所以后來 JDK 又推出了 ScheduledExecutorService ,Timer 也基本不再使用。
  • java.util.concurrent.ScheduledExecutorService :在 JDK 1.5 新增,基于線程池設計的定時任務類,每個調度任務都會被分配到線程池中并發執行,互不影響。這樣,ScheduledExecutorService 就解決了 Timer 串行的問題。

在日常開發中,我們很少直接使用 Timer 或 ScheduledExecutorService 來實現定時任務的需求。主要有幾點原因:

  • 它們僅支持按照指定頻率,不直接支持指定時間的定時調度,需要我們結合 Calendar 自行計算,才能實現復雜時間的調度。例如說,每天、每周五、2019-11-11 等等。
  • 它們是進程級別,而我們為了實現定時任務的高可用,需要部署多個進程。此時需要等多考慮,多個進程下,同一個任務在相同時刻,不能重復執行。
  • 項目可能存在定時任務較多,需要統一的管理,此時不得不進行二次封裝。

所以,一般情況下,我們會選擇專業的調度任務中間件

關于“任務”的叫法,也有叫“作業”的。在英文上,有 Task 也有 Job 。本質是一樣的,本文兩種都會用。

然后,一般來說是調度任務,定時執行。所以會在本文,或者其它文章中,會看到“調度”或“定時”的字眼兒。

在 Spring 體系中,內置了兩種定時任務的解決方案:

  • 第一種,Spring Framework 的 Spring Task 模塊,提供了輕量級的定時任務的實現。

  • 第二種,Spring Boot 2.0 版本,整合了 Quartz 作業調度框架,提供了功能強大的定時任務的實現。

    注:Spring Framework 已經內置了 Quartz 的整合。Spring Boot 1.X 版本未提供 Quartz 的自動化配置,而 2.X 版本提供了支持。

在 Java 生態中,還有非常多優秀的開源的調度任務中間件:

  • Elastic-Job

    唯品會基于 Elastic-Job 之上,演化出了 Saturn 項目。

  • Apache DolphinScheduler

  • XXL-JOB

目前國內采用 Elastic-Job 和 XXL-JOB 為主。使用 XXL-JOB 的團隊會更多一些,主要是上手較為容易,運維功能更為完善。

本文,我們會按照 Spring Task、Quartz、XXL-JOB 的順序,進行分別入門。而在文章的結尾,會簡單聊聊分布式定時任務的實現原理。

2. 快速入門 Spring Task

考慮到實際場景下,我們很少使用 Spring Task ,所以本小節會寫的比較簡潔。如果對 Spring Task 比較感興趣的胖友,可以自己去閱讀 《Spring Framework Documentation —— Task Execution and Scheduling》 文檔,里面有 Spring Task 相關的詳細文檔。

在本小節,我們會使用 Spring Task 功能,實現一個每 2 秒打印一行執行日志的定時任務。

2.1 引入依賴

pom.xml 文件中,引入相關依賴。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.2.1.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent><modelVersion>4.0.0</modelVersion><artifactId>lab-28-task-demo</artifactId><dependencies><!-- 實現對 Spring MVC 的自動化配置 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency></dependencies></project>

因為 Spring Task 是 Spring Framework 的模塊,所以在我們引入 spring-boot-starter-web 依賴后,無需特別引入它。

同時,考慮到我們希望讓項目啟動時,不自動結束 JVM 進程,所以我們引入了 spring-boot-starter-web 依賴。

2.2 ScheduleConfiguration

cn.sunrise.springboot.lab28.task.config 包路徑下,創建 ScheduleConfiguration 類,配置 Spring Task 。代碼如下:

// ScheduleConfiguration.java@Configuration
@EnableScheduling
public class ScheduleConfiguration {
}
  • 在類上,添加 @EnableScheduling 注解,啟動 Spring Task 的定時任務調度的功能。

2.3 DemoJob

cn.sunrise.springboot.lab28.task.job 包路徑下,創建 DemoJob 類,示例定時任務類。代碼如下:

// DemoJob.java@Component
public class DemoJob {private Logger logger = LoggerFactory.getLogger(getClass());private final AtomicInteger counts = new AtomicInteger();@Scheduled(fixedRate = 2000)public void execute() {logger.info("[execute][定時第 ({}) 次執行]", counts.incrementAndGet());}}
  • 在類上,添加 @Component 注解,創建 DemoJob Bean 對象。
  • 創建 #execute() 方法,實現打印日志。同時,在該方法上,添加 @Scheduled 注解,設置每 2 秒執行該方法。

雖然說,@Scheduled 注解,可以添加在一個類上的多個方法上,但是艿艿的個人習慣上,還是一個 Job 類,一個定時任務。😈

2.4 Application

創建 Application.java 類,配置 @SpringBootApplication 注解即可。代碼如下:

@SpringBootApplication
public class Application {public static void main(String[] args) {SpringApplication.run(Application.class, args);}}

運行 Application 類,啟動示例項目。輸出日志精簡如下:

# 初始化一個 ThreadPoolTaskScheduler 任務調度器
2019-11-30 18:02:58.415  INFO 83730 --- [           main] o.s.s.c.ThreadPoolTaskScheduler          : Initializing ExecutorService 'taskScheduler'# 每 2 秒,執行一次 DemoJob 的任務
2019-11-30 18:02:58.449  INFO 83730 --- [ pikaqiu-demo-1] c.i.springboot.lab28.task.job.DemoJob    : [execute][定時第 (1) 次執行]
2019-11-30 18:03:00.438  INFO 83730 --- [ pikaqiu-demo-1] c.i.springboot.lab28.task.job.DemoJob    : [execute][定時第 (2) 次執行]
2019-11-30 18:03:02.442  INFO 83730 --- [ pikaqiu-demo-2] c.i.springboot.lab28.task.job.DemoJob    : [execute][定時第 (3) 次執行]
  • 通過日志,我們可以看到,初始化一個 ThreadPoolTaskScheduler 任務調度器。之后,每 2 秒,執行一次 DemoJob 的任務。

至此,我們已經完成了 Spring Task 調度任務功能的入門。實際上,Spring Task 還提供了異步任務 ,這個我們在其它文章中,詳細講解。

下面「2.5 @Scheduled」和「2.6 應用配置文件」兩個小節,是補充知識,建議看看。

2.5 @Scheduled

@Scheduled 注解,設置定時任務的執行計劃。

常用屬性如下:

  • cron 屬性:Spring Cron 表達式。例如說,"0 0 12 * * ?" 表示每天中午執行一次,"11 11 11 11 11 ?" 表示 11 月 11 號 11 點 11 分 11 秒執行一次(哈哈哈)。更多示例和講解,可以看看 《Spring Cron 表達式》 文章。注意,以調用完成時刻為開始計時時間。
  • fixedDelay 屬性:固定執行間隔,單位:毫秒。注意,以調用完成時刻為開始計時時間。
  • fixedRate 屬性:固定執行間隔,單位:毫秒。注意,以調用開始時刻為開始計時時間。
  • 這三個屬性,有點雷同,可以看看 《@Scheduled 定時任務的fixedRate、fixedDelay、cron 的區別》 ,一定要分清楚差異。

不常用屬性如下:

  • initialDelay 屬性:初始化的定時任務執行延遲,單位:毫秒。
  • zone 屬性:解析 Spring Cron 表達式的所屬的時區。默認情況下,使用服務器的本地時區。
  • initialDelayString 屬性:initialDelay 的字符串形式。
  • fixedDelayString 屬性:fixedDelay 的字符串形式。
  • fixedRateString 屬性:fixedRate 的字符串形式。

2.6 應用配置文件

application.yml 中,添加 Spring Task 定時任務的配置,如下:

spring:task:# Spring Task 調度任務的配置,對應 TaskSchedulingProperties 配置類scheduling:thread-name-prefix: pikaqiu-demo- # 線程池的線程名的前綴。默認為 scheduling- ,建議根據自己應用來設置pool:size: 10 # 線程池大小。默認為 1 ,根據自己應用來設置shutdown:await-termination: true # 應用關閉時,是否等待定時任務執行完成。默認為 false ,建議設置為 trueawait-termination-period: 60 # 等待任務完成的最大時長,單位為秒。默認為 0 ,根據自己應用來設置
  • spring.task.scheduling 配置項,Spring Task 調度任務的配置,對應 TaskSchedulingProperties 配置類。
  • Spring Boot TaskSchedulingAutoConfiguration 自動化配置類,實現 Spring Task 的自動配置,創建 ThreadPoolTaskScheduler 基于線程池的任務調度器。本質上,ThreadPoolTaskScheduler 是基于 ScheduledExecutorService 的封裝,增強在調度時間上的功能。

注意spring.task.scheduling.shutdown 配置項,是為了實現 Spring Task 定時任務的優雅關閉。我們想象一下,如果定時任務在執行的過程中,如果應用開始關閉,把定時任務需要使用到的 Spring Bean 進行銷毀,例如說數據庫連接池,那么此時定時任務還在執行中,一旦需要訪問數據庫,可能會導致報錯。

  • 所以,通過配置 await-termination = true ,實現應用關閉時,等待定時任務執行完成。這樣,應用在關閉的時,Spring 會優先等待 ThreadPoolTaskScheduler 執行完任務之后,再開始 Spring Bean 的銷毀。
  • 同時,又考慮到我們不可能無限等待定時任務全部執行結束,因此可以配置 await-termination-period = 60 ,等待任務完成的最大時長,單位為秒。具體設置多少的等待時長,可以根據自己應用的需要。

3.快速入門 Quartz 單機

考慮到我們要實現定時任務的高可用,需要部署多個 JVM 進程。比較舒服的是,Quartz 自帶了集群方案。它通過將作業信息存儲到關系數據庫中,并使用關系數據庫的行鎖來實現執行作業的競爭,從而保證多個進程下,同一個任務在相同時刻,不能重復執行。

可能很多胖友對 Quartz 還不是很了解,我們先來看一段簡介:

FROM https://www.oschina.net/p/quartz

Quartz 是一個開源的作業調度框架,它完全由 Java 寫成,并設計用于 J2SE 和 J2EE 應用中。它提供了巨大的靈活性而不犧牲簡單性。你能夠用它來為執行一個作業而創建簡單的或復雜的調度。

它有很多特征,如:數據庫支持,集群,插件,EJB 作業預構建,JavaMail 及其它,支持 cron-like 表達式等等。

在 Quartz 體系結構中,有三個組件非常重要:

  • Scheduler :調度器
  • Trigger :觸發器
  • Job :任務

不了解的朋友,可以直接看看 《Quartz 入門詳解》 文章。

Quartz 分成單機模式和集群模式。

  • 本小節,我們先來學習下 Quartz 的單機模式,入門比較快。
  • 下一下「5. 再次入門 Quartz 集群」 ,我們再來學習下 Quartz 的集群模式。在生產環境下,一定一定一定要使用 Quartz 的集群模式,保證定時任務的高可用。

3.1 引入依賴

pom.xml 文件中,引入相關依賴。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.2.1.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent><modelVersion>4.0.0</modelVersion><artifactId>lab-28-task-quartz-memory</artifactId><dependencies><!-- 實現對 Spring MVC 的自動化配置 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- 實現對 Quartz 的自動化配置 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-quartz</artifactId></dependency></dependencies></project>

3.2 示例 Job

cn.risesun.springboot.lab28.task.config.job 包路徑下,我們來創建示例 Job 。

創建 DemoJob01 類,示例定時任務 01 類。代碼如下:

// DemoJob01.javapublic class DemoJob01 extends QuartzJobBean {private Logger logger = LoggerFactory.getLogger(getClass());private final AtomicInteger counts = new AtomicInteger();@Autowiredprivate DemoService demoService;@Overrideprotected void executeInternal(JobExecutionContext context) throws JobExecutionException {logger.info("[executeInternal][定時第 ({}) 次執行, demoService 為 ({})]", counts.incrementAndGet(),demoService);}}
  • 繼承 QuartzJobBean 抽象類,實現 #executeInternal(JobExecutionContext context) 方法,執行自定義的定時任務的邏輯。

  • QuartzJobBean 實現了 org.quartz.Job 接口,提供了 Quartz 每次創建 Job 執行定時邏輯時,將該 Job Bean 的依賴屬性注入。例如說,DemoJob01 需要 @Autowired 注入的 demoService 屬性。核心代碼如下:

    // QuartzJobBean.javapublic final void execute(JobExecutionContext context) throws JobExecutionException {try {// 將當前對象,包裝成 BeanWrapper 對象BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);// 設置屬性到 bw 中MutablePropertyValues pvs = new MutablePropertyValues();pvs.addPropertyValues(context.getScheduler().getContext());pvs.addPropertyValues(context.getMergedJobDataMap());bw.setPropertyValues(pvs, true);} catch (SchedulerException ex) {throw new JobExecutionException(ex);}// 執行提供給子類實現的抽象方法this.executeInternal(context);
    }protected abstract void executeInternal(JobExecutionContext context) throws JobExecutionException;
    
    • 這樣一看,是不是清晰很多。不要懼怕中間件的源碼,好奇哪個類或者方法,就點進去看看。
  • counts 屬性,計數器。用于我們后面我們展示,每次 DemoJob01 都會被 Quartz 創建出一個新的 Job 對象,執行任務。這個很重要,也要非常小心。

創建 DemoJob02 類,示例定時任務 02 類。代碼如下:

// DemoJob02.javapublic class DemoJob02 extends QuartzJobBean {private Logger logger = LoggerFactory.getLogger(getClass());@Overrideprotected void executeInternal(JobExecutionContext context) throws JobExecutionException {logger.info("[executeInternal][我開始的執行了]");}}
  • 比較簡單,為了后面演示案例之用。

3.3 ScheduleConfiguration

cn.risesun.springboot.lab28.task.config 包路徑下,創建 ScheduleConfiguration 類,配置上述的兩個示例 Job 。代碼如下:

// ScheduleConfiguration.java@Configuration
public class ScheduleConfiguration {public static class DemoJob01Configuration {@Beanpublic JobDetail demoJob01() {return JobBuilder.newJob(DemoJob01.class).withIdentity("demoJob01") // 名字為 demoJob01.storeDurably() // 沒有 Trigger 關聯的時候任務是否被保留。因為創建 JobDetail 時,還沒 Trigger 指向它,所以需要設置為 true ,表示保留。.build();}@Beanpublic Trigger demoJob01Trigger() {// 簡單的調度計劃的構造器SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(5) // 頻率。.repeatForever(); // 次數。// Trigger 構造器return TriggerBuilder.newTrigger().forJob(demoJob01()) // 對應 Job 為 demoJob01.withIdentity("demoJob01Trigger") // 名字為 demoJob01Trigger.withSchedule(scheduleBuilder) // 對應 Schedule 為 scheduleBuilder.build();}}public static class DemoJob02Configuration {@Beanpublic JobDetail demoJob02() {return JobBuilder.newJob(DemoJob02.class).withIdentity("demoJob02") // 名字為 demoJob02.storeDurably() // 沒有 Trigger 關聯的時候任務是否被保留。因為創建 JobDetail 時,還沒 Trigger 指向它,所以需要設置為 true ,表示保留。.build();}@Beanpublic Trigger demoJob02Trigger() {// 基于 Quartz Cron 表達式的調度計劃的構造器CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule("0/10 * * * * ? *");// Trigger 構造器return TriggerBuilder.newTrigger().forJob(demoJob02()) // 對應 Job 為 demoJob02.withIdentity("demoJob02Trigger") // 名字為 demoJob02Trigger.withSchedule(scheduleBuilder) // 對應 Schedule 為 scheduleBuilder.build();}}}
  • 內部創建了 DemoJob01Configuration 和 DemoJob02Configuration 兩個配置類,分別配置 DemoJob01 和 DemoJob02 兩個 Quartz Job 。
  • == DemoJob01Configuration ==
  • #demoJob01() 方法,創建 DemoJob01 的 JobDetail Bean 對象。
  • #demoJob01Trigger() 方法,創建 DemoJob01 的 Trigger Bean 對象。其中,我們使用 SimpleScheduleBuilder 簡單的調度計劃的構造器,創建了每 5 秒執行一次,無限重復的調度計劃。
  • == DemoJob2Configuration ==
  • #demoJob2() 方法,創建 DemoJob02 的 JobDetail Bean 對象。
  • #demoJob02Trigger() 方法,創建 DemoJob02 的 Trigger Bean 對象。其中,我們使用 CronScheduleBuilder 基于 Quartz Cron 表達式的調度計劃的構造器,創建了每 10 秒執行一次的調度計劃。這里,推薦一個 Quartz/Cron/Crontab 表達式在線生成工具 ,方便幫我們生成 Quartz Cron 表達式,并計算出最近 5 次運行時間。

😈 因為 JobDetail 和 Trigger 一般是成雙成對出現,所以習慣配置成一個 Configuration 配置類。

3.4 Application

創建 Application.java 類,配置 @SpringBootApplication 注解即可。代碼如下:

@SpringBootApplication
public class Application {public static void main(String[] args) {SpringApplication.run(Application.class, args);}}

運行 Application 類,啟動示例項目。輸出日志精簡如下:

# 創建了 Quartz QuartzScheduler 并啟動
2019-11-30 23:40:05.123  INFO 92812 --- [           main] org.quartz.impl.StdSchedulerFactory      : Using default implementation for ThreadExecutor
2019-11-30 23:40:05.130  INFO 92812 --- [           main] org.quartz.core.SchedulerSignalerImpl    : Initialized Scheduler Signaller of type: class org.quartz.core.SchedulerSignalerImpl
2019-11-30 23:40:05.130  INFO 92812 --- [           main] org.quartz.core.QuartzScheduler          : Quartz Scheduler v.2.3.2 created.
2019-11-30 23:40:05.131  INFO 92812 --- [           main] org.quartz.simpl.RAMJobStore             : RAMJobStore initialized.
2019-11-30 23:40:05.132  INFO 92812 --- [           main] org.quartz.core.QuartzScheduler          : Scheduler meta-data: Quartz Scheduler (v2.3.2) 'quartzScheduler' with instanceId 'NON_CLUSTERED'Scheduler class: 'org.quartz.core.QuartzScheduler' - running locally.NOT STARTED.Currently in standby mode.Number of jobs executed: 0Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 10 threads.Using job-store 'org.quartz.simpl.RAMJobStore' - which does not support persistence. and is not clustered.2019-11-30 23:40:05.132  INFO 92812 --- [           main] org.quartz.impl.StdSchedulerFactory      : Quartz scheduler 'quartzScheduler' initialized from an externally provided properties instance.
2019-11-30 23:40:05.132  INFO 92812 --- [           main] org.quartz.impl.StdSchedulerFactory      : Quartz scheduler version: 2.3.2
2019-11-30 23:40:05.132  INFO 92812 --- [           main] org.quartz.core.QuartzScheduler          : JobFactory set to: org.springframework.scheduling.quartz.SpringBeanJobFactory@203dd56b
2019-11-30 23:40:05.158  INFO 92812 --- [           main] o.s.s.quartz.SchedulerFactoryBean        : Starting Quartz Scheduler now
2019-11-30 23:40:05.158  INFO 92812 --- [           main] org.quartz.core.QuartzScheduler          : Scheduler quartzScheduler_$_NON_CLUSTERED started.# DemoJob01
2019-11-30 23:40:05.164  INFO 92812 --- [eduler_Worker-1] c.i.springboot.lab28.task.job.DemoJob01  : [executeInternal][定時第 (1) 次執行, demoService 為 (cn.iocoder.springboot.lab28.task.service.DemoService@23d75d74)]
2019-11-30 23:40:09.866  INFO 92812 --- [eduler_Worker-2] c.i.springboot.lab28.task.job.DemoJob01  : [executeInternal][定時第 (1) 次執行, demoService 為 (cn.iocoder.springboot.lab28.task.service.DemoService@23d75d74)]
2019-11-30 23:40:14.865  INFO 92812 --- [eduler_Worker-4] c.i.springboot.lab28.task.job.DemoJob01  : [executeInternal][定時第 (1) 次執行, demoService 為 (cn.iocoder.springboot.lab28.task.service.DemoService@23d75d74)]# DemoJob02
2019-11-30 23:40:10.004  INFO 92812 --- [eduler_Worker-3] c.i.springboot.lab28.task.job.DemoJob02  : [executeInternal][我開始的執行了]
2019-11-30 23:40:20.001  INFO 92812 --- [eduler_Worker-6] c.i.springboot.lab28.task.job.DemoJob02  : [executeInternal][我開始的執行了]
2019-11-30 23:40:30.002  INFO 92812 --- [eduler_Worker-9] c.i.springboot.lab28.task.job.DemoJob02  : [executeInternal][我開始的執行了]
  • 項目啟動時,會創建了 Quartz QuartzScheduler 并啟動。
  • 考慮到閱讀日志方便,艿艿這里把 DemoJob01 和 DemoJob02 的日志分開來了。
  • 對于 DemoJob01 ,每 5 秒左右執行一次。同時我們可以看到,demoService 成功注入,而 counts 每次都是 1 ,說明每次 DemoJob01 都是新創建的。
  • 對于 DemoJob02 ,每 10 秒執行一次。

下面「3.5 應用配置文件」兩個小節,是補充知識,建議看看。

3.5 應用配置文件

application.yml 中,添加 Quartz 的配置,如下:

spring:# Quartz 的配置,對應 QuartzProperties 配置類quartz:job-store-type: memory # Job 存儲器類型。默認為 memory 表示內存,可選 jdbc 使用數據庫。auto-startup: true # Quartz 是否自動啟動startup-delay: 0 # 延遲 N 秒啟動wait-for-jobs-to-complete-on-shutdown: true # 應用關閉時,是否等待定時任務執行完成。默認為 false ,建議設置為 trueoverwrite-existing-jobs: false # 是否覆蓋已有 Job 的配置properties: # 添加 Quartz Scheduler 附加屬性,更多可以看 http://www.quartz-scheduler.org/documentation/2.4.0-SNAPSHOT/configuration.html 文檔org:quartz:threadPool:threadCount: 25 # 線程池大小。默認為 10 。threadPriority: 5 # 線程優先級class: org.quartz.simpl.SimpleThreadPool # 線程池類型
#    jdbc: # 這里暫時不說明,使用 JDBC 的 JobStore 的時候,才需要配置
  • spring.quartz 配置項,Quartz 的配置,對應 QuartzProperties 配置類。
  • Spring Boot QuartzAutoConfiguration 自動化配置類,實現 Quartz 的自動配置,創建 Quartz Scheduler(調度器) Bean 。

注意spring.quartz.wait-for-jobs-to-complete-on-shutdown 配置項,是為了實現 Quartz 的優雅關閉,建議開啟。關于這塊,和我們在 Spring Task 的「2.6 應用配置文件」 提到的是一致的。

4. 再次入門 Quartz 集群

實際場景下,我們必然需要考慮定時任務的高可用,所以基本上,肯定使用 Quartz 的集群方案。因此本小節,我們使用 Quartz 的 JDBC 存儲器 JobStoreTX ,并是使用 MySQL 作為數據庫。

如下是 Quartz 兩種存儲器的對比:

FROM https://blog.csdn.net/Evankaka/article/details/45540885

類型優點缺點
RAMJobStore不要外部數據庫,配置容易,運行速度快因為調度程序信息是存儲在被分配給 JVM 的內存里面,所以,當應用程序停止運行時,所有調度信息將被丟失。另外因為存儲到JVM內存里面,所以可以存儲多少個 Job 和 Trigger 將會受到限制
JDBC 作業存儲支持集群,因為所有的任務信息都會保存到數據庫中,可以控制事物,還有就是如果應用服務器關閉或者重啟,任務信息都不會丟失,并且可以恢復因服務器關閉或者重啟而導致執行失敗的任務運行速度的快慢取決與連接數據庫的快慢

另外,本小節提供的示例和 「3. 快速入門 Quartz 單機」 基本一致。

4.1 引入依賴

pom.xml 文件中,引入相關依賴。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.1.10.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent><modelVersion>4.0.0</modelVersion><artifactId>lab-28-task-quartz-jdbc</artifactId><dependencies><!-- 實現對數據庫連接池的自動化配置 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId></dependency><dependency> <!-- 本示例,我們使用 MySQL --><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.48</version></dependency><!-- 實現對 Spring MVC 的自動化配置 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- 實現對 Quartz 的自動化配置 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-quartz</artifactId></dependency><!-- 方便等會寫單元測試 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies></project>
  • 和 「3.1 引入依賴」 基本一致,只是額外引入 spring-boot-starter-test 依賴,等會會寫兩個單元測試方法。

4.2 示例 Job

cn.sunrise.springboot.lab28.task.config.job 包路徑下,創建DemoJob01 和 DemoJob02 類。代碼如下:

// DemoJob01.java@DisallowConcurrentExecution
public class DemoJob01 extends QuartzJobBean {private Logger logger = LoggerFactory.getLogger(getClass());@Autowiredprivate DemoService demoService;@Overrideprotected void executeInternal(JobExecutionContext context) {logger.info("[executeInternal][我開始的執行了, demoService 為 ({})]", demoService);}}// DemoJob02.java@DisallowConcurrentExecution
public class DemoJob02 extends QuartzJobBean {private Logger logger = LoggerFactory.getLogger(getClass());@Overrideprotected void executeInternal(JobExecutionContext context) {logger.info("[executeInternal][我開始的執行了]");}}
  • 相比 「3.2 示例 Job」 來說,在類上添加了 Quartz 的 @DisallowConcurrentExecution 注解,保證相同 JobDetail 在多個 JVM 進程中,有且僅有一個節點在執行。

注意,不是以 Quartz Job 為維度,保證在多個 JVM 進程中,有且僅有一個節點在執行,而是以 JobDetail 為維度。雖然說,絕大多數情況下,我們會保證一個 Job 和 JobDetail 是一一對應。😈 所以,搞不清楚這個概念的胖友,最好搞清楚這個概念。實在有點懵逼,保證一個 Job 和 JobDetail 是一一對應就對了。

而 JobDetail 的唯一標識是 JobKey ,使用 name + group 兩個屬性。一般情況下,我們只需要設置 name 即可,而 Quartz 會默認 group = DEFAULT

不過這里還有一點要補充,也是需要注意的,在 Quartz 中,相同 Scheduler 名字的節點,形成一個 Quartz 集群。在下文中,我們可以通過 spring.quartz.scheduler-name 配置項,設置 Scheduler 的名字。

**【重要】*為什么要說這個呢?因為我們要完善一下上面的說法:通過在 Job 實現類上添加 @DisallowConcurrentExecution 注解,實現在*相同 Quartz Scheduler 集群中,相同 JobKey 的 JobDetail ,保證在多個 JVM 進程中,有且僅有一個節點在執行。

4.3 應用配置文件

application.yml 中,添加 Quartz 的配置,如下:

spring:datasource:user:url: jdbc:mysql://127.0.0.1:3306/lab-28-quartz-jdbc-user?useSSL=false&useUnicode=true&characterEncoding=UTF-8driver-class-name: com.mysql.jdbc.Driverusername: rootpassword:quartz:url: jdbc:mysql://127.0.0.1:3306/lab-28-quartz-jdbc-quartz?useSSL=false&useUnicode=true&characterEncoding=UTF-8driver-class-name: com.mysql.jdbc.Driverusername: rootpassword:# Quartz 的配置,對應 QuartzProperties 配置類quartz:scheduler-name: clusteredScheduler # Scheduler 名字。默認為 schedulerNamejob-store-type: jdbc # Job 存儲器類型。默認為 memory 表示內存,可選 jdbc 使用數據庫。auto-startup: true # Quartz 是否自動啟動startup-delay: 0 # 延遲 N 秒啟動wait-for-jobs-to-complete-on-shutdown: true # 應用關閉時,是否等待定時任務執行完成。默認為 false ,建議設置為 trueoverwrite-existing-jobs: false # 是否覆蓋已有 Job 的配置properties: # 添加 Quartz Scheduler 附加屬性,更多可以看 http://www.quartz-scheduler.org/documentation/2.4.0-SNAPSHOT/configuration.html 文檔org:quartz:# JobStore 相關配置jobStore:# 數據源名稱dataSource: quartzDataSource # 使用的數據源class: org.quartz.impl.jdbcjobstore.JobStoreTX # JobStore 實現類driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegatetablePrefix: QRTZ_ # Quartz 表前綴isClustered: true # 是集群模式clusterCheckinInterval: 1000useProperties: false# 線程池相關配置threadPool:threadCount: 25 # 線程池大小。默認為 10 。threadPriority: 5 # 線程優先級class: org.quartz.simpl.SimpleThreadPool # 線程池類型jdbc: # 使用 JDBC 的 JobStore 的時候,JDBC 的配置initialize-schema: never # 是否自動使用 SQL 初始化 Quartz 表結構。這里設置成 never ,我們手動創建表結構。
  • 配置項比較多,我們主要對比 「3.5 應用配置文件」 來看看。
  • spring.datasource 配置項下,用于創建多個數據源的配置。
    • user 配置,連接 lab-28-quartz-jdbc-user 庫。目的是,為了模擬我們一般項目,使用到的業務數據庫。
    • quartz 配置,連接 lab-28-quartz-jdbc-quartz 庫。目的是,Quartz 會使用單獨的數據庫。😈 如果我們有多個項目需要使用到 Quartz 數據庫的話,可以統一使用一個,但是要注意配置 spring.quartz.scheduler-name 設置不同的 Scheduler 名字,形成不同的 Quartz 集群。
  • spring.quartz 配置項下,額外增加了一些配置項,我們逐個來看看。
    • scheduler-name 配置,Scheduler 名字。這個我們在上文解釋了很多次了,如果還不明白,請拍死自己。
    • job-store-type 配置,設置了使用 "jdbc" 的 Job 存儲器。
    • properties.org.quartz.jobStore 配置,增加了 JobStore 相關配置。重點是,通過 dataSource 配置項,設置了使用名字為 "quartzDataSource" 的 DataSource 為數據源。😈 在 「4.4 DataSourceConfiguration」 中,我們會使用 spring.datasource.quartz 配置,來創建該數據源。
    • jdbc 配置項,雖然名字叫這個,主要是為了設置使用 SQL 初始化 Quartz 表結構。這里,我們設置 initialize-schema = never ,我們手動創建表結構。

咳咳咳,配置項確實有點多。如果暫時搞不明白的胖友,可以先簡單把 spring.datasource 數據源,修改成自己的即可。

4.4 初始化 Quartz 表結構

在 Quartz Download 地址,下載對應版本的發布包。解壓后,我們可以在 src/org/quartz/impl/jdbcjobstore/ 目錄,看到各種數據庫的 Quartz 表結構的初始化腳本。這里,因為我們使用 MySQL ,所以使用 tables_mysql_innodb.sql 腳本。

在數據庫中執行該腳本,完成初始化 Quartz 表結構。如下圖所示:Quartz 表結構

關于每個 Quartz 表結構的說明,可以看看 《Quartz 框架(二)——JobStore 數據庫表字段詳解》 文章。

我們會發現,每個表都有一個 SCHED_NAME 字段,Quartz Scheduler 名字。這樣,實現每個 Quartz 集群,數據層面的拆分。

4.5 DataSourceConfiguration

cn.risesun.springboot.lab28.task.config 包路徑下,創建 DataSourceConfiguration 類,配置數據源。代碼如下:

// DataSourceConfiguration.java@Configuration
public class DataSourceConfiguration {/*** 創建 user 數據源的配置對象*/@Primary@Bean(name = "userDataSourceProperties")@ConfigurationProperties(prefix = "spring.datasource.user") // 讀取 spring.datasource.user 配置到 DataSourceProperties 對象public DataSourceProperties userDataSourceProperties() {return new DataSourceProperties();}/*** 創建 user 數據源*/@Primary@Bean(name = "userDataSource")@ConfigurationProperties(prefix = "spring.datasource.user.hikari") // 讀取 spring.datasource.user 配置到 HikariDataSource 對象public DataSource userDataSource() {// 獲得 DataSourceProperties 對象DataSourceProperties properties =  this.userDataSourceProperties();// 創建 HikariDataSource 對象return createHikariDataSource(properties);}/*** 創建 quartz 數據源的配置對象*/@Bean(name = "quartzDataSourceProperties")@ConfigurationProperties(prefix = "spring.datasource.quartz") // 讀取 spring.datasource.quartz 配置到 DataSourceProperties 對象public DataSourceProperties quartzDataSourceProperties() {return new DataSourceProperties();}/*** 創建 quartz 數據源*/@Bean(name = "quartzDataSource")@ConfigurationProperties(prefix = "spring.datasource.quartz.hikari")@QuartzDataSourcepublic DataSource quartzDataSource() {// 獲得 DataSourceProperties 對象DataSourceProperties properties =  this.quartzDataSourceProperties();// 創建 HikariDataSource 對象return createHikariDataSource(properties);}private static HikariDataSource createHikariDataSource(DataSourceProperties properties) {// 創建 HikariDataSource 對象HikariDataSource dataSource = properties.initializeDataSourceBuilder().type(HikariDataSource.class).build();// 設置線程池名if (StringUtils.hasText(properties.getName())) {dataSource.setPoolName(properties.getName());}return dataSource;}}
  • 基于 spring.datasource.user 配置項,創建了名字為 "userDataSource" 的 DataSource Bean 。并且,在其上我們添加了 @Primay 注解,表示其是數據源。
  • 基于 spring.datasource.quartz 配置項,創建了名字為 "quartzDataSource" 的 DataSource Bean 。并且,在其上我們添加了 @QuartzDataSource 注解,表示其是 Quartz 的數據源。😈 注意,一定要配置啊!!!!

4.6 定時任務配置

完成上述的工作之后,我們需要配置 Quartz 的定時任務。目前,有兩種方式:

  • 方式一,「4.6.1 Bean 自動設置」 。
  • 方式二,「4.6.2 Scheduler 手動設置」 。

4.6.1 Bean 自動設置

cn.risesun.springboot.lab28.task.config 包路徑下,創建 ScheduleConfiguration 類,配置上述的兩個示例 Job 。代碼如下:

// ScheduleConfiguration.java@Configuration
public class ScheduleConfiguration {public static class DemoJob01Configuration {@Beanpublic JobDetail demoJob01() {return JobBuilder.newJob(DemoJob01.class).withIdentity("demoJob01") // 名字為 demoJob01.storeDurably() // 沒有 Trigger 關聯的時候任務是否被保留。因為創建 JobDetail 時,還沒 Trigger 指向它,所以需要設置為 true ,表示保留。.build();}@Beanpublic Trigger demoJob01Trigger() {// 簡單的調度計劃的構造器SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(5) // 頻率。.repeatForever(); // 次數。// Trigger 構造器return TriggerBuilder.newTrigger().forJob(demoJob01()) // 對應 Job 為 demoJob01.withIdentity("demoJob01Trigger") // 名字為 demoJob01Trigger.withSchedule(scheduleBuilder) // 對應 Schedule 為 scheduleBuilder.build();}}public static class DemoJob02Configuration {@Beanpublic JobDetail demoJob02() {return JobBuilder.newJob(DemoJob02.class).withIdentity("demoJob02") // 名字為 demoJob02.storeDurably() // 沒有 Trigger 關聯的時候任務是否被保留。因為創建 JobDetail 時,還沒 Trigger 指向它,所以需要設置為 true ,表示保留。.build();}@Beanpublic Trigger demoJob02Trigger() {// 簡單的調度計劃的構造器CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule("0/10 * * * * ? *");// Trigger 構造器return TriggerBuilder.newTrigger().forJob(demoJob02()) // 對應 Job 為 demoJob02.withIdentity("demoJob02Trigger") // 名字為 demoJob02Trigger.withSchedule(scheduleBuilder) // 對應 Schedule 為 scheduleBuilder.build();}}}
  • 和 「3.3 ScheduleConfiguration」 是一模一樣的。

在 Quartz 調度器啟動的時候,會根據該配置,自動調用如下方法:

  • Scheduler#addJob(JobDetail jobDetail, boolean replace) 方法,將 JobDetail 持久化到數據庫。
  • Scheduler#scheduleJob(Trigger trigger) 方法,將 Trigger 持久化到數據庫。

4.6.2 Scheduler 手動設置

一般情況下,推薦使用 Scheduler 手動設置。

創建 QuartzSchedulerTest 類,創建分別添加 DemoJob01 和 DemoJob02 的 Quartz 定時任務配置。代碼如下:

// QuartzSchedulerTest.java@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
public class QuartzSchedulerTest {@Autowiredprivate Scheduler scheduler;@Testpublic void addDemoJob01Config() throws SchedulerException {// 創建 JobDetailJobDetail jobDetail = JobBuilder.newJob(DemoJob01.class).withIdentity("demoJob01") // 名字為 demoJob01.storeDurably() // 沒有 Trigger 關聯的時候任務是否被保留。因為創建 JobDetail 時,還沒 Trigger 指向它,所以需要設置為 true ,表示保留。.build();// 創建 TriggerSimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(5) // 頻率。.repeatForever(); // 次數。Trigger trigger = TriggerBuilder.newTrigger().forJob(jobDetail) // 對應 Job 為 demoJob01.withIdentity("demoJob01Trigger") // 名字為 demoJob01Trigger.withSchedule(scheduleBuilder) // 對應 Schedule 為 scheduleBuilder.build();// 添加調度任務scheduler.scheduleJob(jobDetail, trigger);
//        scheduler.scheduleJob(jobDetail, Sets.newSet(trigger), true);}@Testpublic void addDemoJob02Config() throws SchedulerException {// 創建 JobDetailJobDetail jobDetail = JobBuilder.newJob(DemoJob02.class).withIdentity("demoJob02") // 名字為 demoJob02.storeDurably() // 沒有 Trigger 關聯的時候任務是否被保留。因為創建 JobDetail 時,還沒 Trigger 指向它,所以需要設置為 true ,表示保留。.build();// 創建 TriggerCronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule("0/10 * * * * ? *");Trigger trigger = TriggerBuilder.newTrigger().forJob(jobDetail) // 對應 Job 為 demoJob01.withIdentity("demoJob02Trigger") // 名字為 demoJob01Trigger.withSchedule(scheduleBuilder) // 對應 Schedule 為 scheduleBuilder.build();// 添加調度任務scheduler.scheduleJob(jobDetail, trigger);
//        scheduler.scheduleJob(jobDetail, Sets.newSet(trigger), true);}}
  • 創建 JobDetail 和 Trigger 的代碼,其實和 「4.6.1 Bean 自動設置」 是一致的。
  • 在每個單元測試方法的最后,調用 Scheduler#scheduleJob(JobDetail jobDetail, Trigger trigger) 方法,將 JobDetail 和 Trigger 持久化到數據庫。
  • 如果想要覆蓋數據庫中的 Quartz 定時任務的配置,可以調用 Scheduler#scheduleJob(JobDetail jobDetail, Set<? extends Trigger> triggersForJob, boolean replace) 方法,傳入 replace = true 進行覆蓋配置。

4.7 Application

創建 Application.java 類,配置 @SpringBootApplication 注解即可。代碼如下:

// Application.java@SpringBootApplication
public class Application {public static void main(String[] args) {SpringApplication.run(Application.class, args);}}
  • 運行 Application 類,啟動示例項目。具體的執行日志,和 「3.4 Application」 基本一致,艿艿這里就不重復羅列了。

如果想要測試集群下的運行情況,可以再創建 創建 Application02.java 類,配置 @SpringBootApplication 注解即可。代碼如下:

// Application02.java@SpringBootApplication
public class Application02 {public static void main(String[] args) {// 設置 Tomcat 隨機端口System.setProperty("server.port", "0");// 啟動 Spring Boot 應用SpringApplication.run(Application.class, args);}}
  • 運行 Application02 類,再次啟動一個示例項目。然后,觀察輸出的日志,可以看到啟動的兩個示例項目,都會有 DemoJob01 和 DemoJob02 的執行日志。

5. 快速入門 XXL-JOB

雖然說,Quartz 的功能,已經能夠滿足我們對定時任務的訴求,但是距離生產可用、好用,還是有一定的距離。不過現在呢,開源社區中已經有了很多優秀的調度任務中間件。其中,比較有代表性的就是 XXL-JOB 。其對自己的定義如下:

XXL-JOB 是一個輕量級分布式任務調度平臺,其核心設計目標是開發迅速、學習簡單、輕量級、易擴展。

對于 XXL-JOB 的入門,重點是,要先搭建一個 XXL-JOB 調度中心。😈 因為,本文我們是來在 Spring Boot 項目中,實現一個 XXL-JOB 執行器。

5.1 引入依賴

pom.xml 文件中,引入相關依賴。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.2.1.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent><modelVersion>4.0.0</modelVersion><artifactId>lab-28-task-xxl-job</artifactId><dependencies><!-- 實現對 Spring MVC 的自動化配置 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- XXL-JOB 相關依賴 --><dependency><groupId>com.xuxueli</groupId><artifactId>xxl-job-core</artifactId><version>2.1.1</version></dependency></dependencies></project>

比較可惜的是,目前 XXL-JOB 官方并未提供 Spring Boot Starter 包,略微有點尷尬。

5.2 應用配置文件

application.yml 中,添加 Quartz 的配置,如下:

server:port: 9090 #指定一個端口,避免和 XXL-JOB 調度中心的端口沖突。僅僅測試之用# xxl-job
xxl:job:admin:addresses: http://127.0.0.1:8080/xxl-job-admin # 調度中心部署跟地址 [選填]:如調度中心集群部署存在多個地址則用逗號分隔。執行器將會使用該地址進行"執行器心跳注冊"和"任務結果回調";為空則關閉自動注冊;executor:appname: lab-28-executor # 執行器 AppName [選填]:執行器心跳注冊分組依據;為空則關閉自動注冊ip: # 執行器IP [選填]:默認為空表示自動獲取IP,多網卡時可手動設置指定IP,該IP不會綁定Host僅作為通訊實用;地址信息用于 "執行器注冊" 和 "調度中心請求并觸發任務";port: 6666 # ### 執行器端口號 [選填]:小于等于0則自動獲取;默認端口為9999,單機部署多個執行器時,注意要配置不同執行器端口;logpath: /Users/yunai/logs/xxl-job/lab-28-executor # 執行器運行日志文件存儲磁盤路徑 [選填] :需要對該路徑擁有讀寫權限;為空則使用默認路徑;logretentiondays: 30 # 執行器日志文件保存天數 [選填] : 過期日志自動清理, 限制值大于等于3時生效; 否則, 如-1, 關閉自動清理功能;accessToken: yudaoyuanma # 執行器通訊TOKEN [選填]:非空時啟用;

5.3 XxlJobConfiguration

cn.risesun.springboot.lab28.task.config 包路徑下,創建 DataSourceConfiguration 類,配置 XXL-JOB 執行器。代碼如下:

// XxlJobConfiguration.java@Configuration
public class XxlJobConfiguration {@Value("${xxl.job.admin.addresses}")private String adminAddresses;@Value("${xxl.job.executor.appname}")private String appName;@Value("${xxl.job.executor.ip}")private String ip;@Value("${xxl.job.executor.port}")private int port;@Value("${xxl.job.accessToken}")private String accessToken;@Value("${xxl.job.executor.logpath}")private String logPath;@Value("${xxl.job.executor.logretentiondays}")private int logRetentionDays;@Beanpublic XxlJobSpringExecutor xxlJobExecutor() {// 創建 XxlJobSpringExecutor 執行器XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();xxlJobSpringExecutor.setAdminAddresses(adminAddresses);xxlJobSpringExecutor.setAppName(appName);xxlJobSpringExecutor.setIp(ip);xxlJobSpringExecutor.setPort(port);xxlJobSpringExecutor.setAccessToken(accessToken);xxlJobSpringExecutor.setLogPath(logPath);xxlJobSpringExecutor.setLogRetentionDays(logRetentionDays);// 返回return xxlJobSpringExecutor;}}
  • #xxlJobExecutor() 方法,創建了 Spring 容器下的 XXL-JOB 執行器 Bean 對象。要注意,方法上添加了的 @Bean 注解,配置了啟動和銷毀方法。

5.4 DemoJob

cn.risesun.springboot.lab28.task.job 包路徑下,創建 DemoJob 類,示例定時任務類。代碼如下:

// DemoJob.java@Component
@JobHandler("demoJob")
public class DemoJob extends IJobHandler {private Logger logger = LoggerFactory.getLogger(getClass());private final AtomicInteger counts = new AtomicInteger();@Overridepublic ReturnT<String> execute(String param) throws Exception {// 打印日志logger.info("[execute][定時第 ({}) 次執行]", counts.incrementAndGet());// 返回執行成功return ReturnT.SUCCESS;}}
  • 繼承 XXL-JOB IJobHandler 抽象類,通過實現 #execute(String param) 方法,從而實現定時任務的邏輯。
  • 在方法上,添加 @JobHandler 注解,設置 JobHandler 的名字。后續,我們在調度中心的控制臺中,新增任務時,需要使用到這個名字。

#execute(String param) 方法的返回結果,為 ReturnT 類型。當返回值符合 “ReturnT.code == ReturnT.SUCCESS_CODE” 時表示任務執行成功,否則表示任務執行失敗,而且可以通過 “ReturnT.msg” 回調錯誤信息給調度中心;從而,在任務邏輯中可以方便的控制任務執行結果。

#execute(String param) 方法的方法參數,為調度中心的控制臺中,新增任務時,配置的“任務參數”。一般情況下,不會使用到。

5.5 Application

創建 Application.java 類,配置 @SpringBootApplication 注解即可。代碼如下:

// Application.java@SpringBootApplication
public class Application {public static void main(String[] args) {SpringApplication.run(Application.class, args);}}

運行 Application 類,啟動示例項目。輸出日志精簡如下:

# XXL-JOB 啟動日志
2019-11-29 00:58:42.429  INFO 46957 --- [           main] c.xxl.job.core.executor.XxlJobExecutor   : >>>>>>>>>>> xxl-job register jobhandler success, name:demoJob, jobHandler:cn.iocoder.springboot.lab28.task.job.DemoJob@3af9aa66
2019-11-29 00:58:42.451  INFO 46957 --- [           main] c.x.r.r.provider.XxlRpcProviderFactory   : >>>>>>>>>>> xxl-rpc, provider factory add service success. serviceKey = com.xxl.job.core.biz.ExecutorBiz, serviceBean = class com.xxl.job.core.biz.impl.ExecutorBizImpl
2019-11-29 00:58:42.454  INFO 46957 --- [           main] c.x.r.r.provider.XxlRpcProviderFactory   : >>>>>>>>>>> xxl-rpc, provider factory add service success. serviceKey = com.xxl.job.core.biz.ExecutorBiz, serviceBean = class com.xxl.job.core.biz.impl.ExecutorBizImpl
2019-11-29 00:58:42.565  INFO 46957 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2019-11-29 00:58:42.629  INFO 46957 --- [       Thread-7] com.xxl.rpc.remoting.net.Server          : >>>>>>>>>>> xxl-rpc remoting server start success, nettype = com.xxl.rpc.remoting.net.impl.netty_http.server.NettyHttpServer, port = 6666

此時,因為我們并未在 XXL-JOB 調度中心進行相關的配置,所以 DemoJob 并不會執行。下面,讓我們在 XXL-JOB 調度中心進行相應的配置。

5.6 新增執行器

瀏覽器打開 http://127.0.0.1:8080/xxl-job-admin/jobgroup 地址,即「執行器管理」菜單。如下圖:執行器管理

點擊「新增執行器」按鈕,彈出「新增執行器」界面。如下圖:新增執行器

填寫完 "lab-28-executor" 執行器的信息,點擊「保存」按鈕,進行保存。耐心等待一會,執行器會自動注冊上來。如下圖:執行器管理

  • 執行器列表中顯示在線的執行器列表, 可通過 “OnLine 機器” 查看對應執行器的集群機器。

相同執行器,有且僅需配置一次即可。

5.7 新建任務

瀏覽器打開 http://127.0.0.1:8080/xxl-job-admin/jobinfo 地址,即「任務管理」菜單。如下圖:任務管理

點擊最右邊的「新增」按鈕,彈出「新增」界面。如下圖:新增

填寫完 "demoJob" 任務的信息,點擊「保存」按鈕,進行保存。如下圖:任務管理

點擊 "demoJob" 任務的「操作」按鈕,選擇「啟動」,確認后,該 "demoJob" 任務的狀態就變成了 RUNNING 。如下圖:任務管理

此時,我們打開執行器的 IDE 界面,可以看到 DemoJob 已經在每分鐘執行一次了。日志如下:

2019-11-29 01:30:00.161  INFO 48374 --- [      Thread-18] c.i.springboot.lab28.task.job.DemoJob    : [execute][定時第 (1) 次執行]
2019-11-29 01:31:00.012  INFO 48374 --- [      Thread-18] c.i.springboot.lab28.task.job.DemoJob    : [execute][定時第 (2) 次執行]
2019-11-29 01:32:00.009  INFO 48374 --- [      Thread-18] c.i.springboot.lab28.task.job.DemoJob    : [execute][定時第 (3) 次執行]
2019-11-29 01:33:00.010  INFO 48374 --- [      Thread-18] c.i.springboot.lab28.task.job.DemoJob    : [execute][定時第 (4) 次執行]
2019-11-29 01:34:00.005  INFO 48374 --- [      Thread-18] c.i.springboot.lab28.task.job.DemoJob    : [execute][定時第 (5) 次執行]

并且,我們在調度中心的界面上,點擊 "demoJob" 任務的「操作」按鈕,選擇「查詢日志」,可以看到相應的調度日志。如下圖:查詢日志

至此,我們已經完成了 XXL-JOB 執行器的入門。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/94924.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/94924.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/94924.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

學習:uniapp全棧微信小程序vue3后臺(6)

26.實現描述評分標簽的雙向數據綁定 /pages/wallpaper/picadd Array.prototype.splice() splice() 方法就地移除或者替換已存在的元素和/或添加新的元素。 二次確認 展現 確認標簽 刪除標簽 溫故知新&#xff1a; 標簽&#xff1a; 關閉標簽 27.uni-data-select調用云端分類…

Azure Marketplace 和 Microsoft AppSource的區別

微軟的商業應用生態中&#xff0c;Azure Marketplace 和 Microsoft AppSource 是微軟并行的兩個主要“應用市場”&#xff08;Marketplace&#xff09;&#xff0c;它們共同構成了微軟的“商業市場”&#xff08;Commercial Marketplace&#xff09;計劃&#xff0c;但服務的目…

完整實驗命令解析:從集群搭建到負載均衡配置(2)

一、環境準備與基礎網絡配置1.1 節點角色與網絡規劃節點角色主機名所屬網段IP 地址網關核心功能Web 服務器web110.1.8.0/2410.1.8.1110.1.8.10&#xff08;后期調整為 10.1.8.20&#xff09;部署 Nginx/HTTPD&#xff0c;提供 Web 服務Web 服務器web210.1.8.0/2410.1.8.1210.1.…

uniapp H5禁止微信瀏覽器長按出菜單,只針對圖片

一、問題描述 如圖&#xff1a;uni-image>img,img {pointer-events: none;-webkit-pointer-events: none;-ms-pointer-events: none;-moz-pointer-events: none; }uni-image::before {content: ;position: absolute;top: 0;bottom: 0;left: 0;right: 0;background: transpa…

【機器學習】 15 Gaussian processes

本章目錄 15 Gaussian processes 515 15.1 Introduction 515 15.2 GPs for regression 516 15.2.1 Predictions using noise-free observations 517 15.2.2 Predictions using noisy observations 518 15.2.3 Effect of the kernel parameters 519 15.2.4 Estimating the kern…

Vue加載速度優化,verder.js和element.js加載速度慢解決方法

1. 使用CDN 這里把常用的vue、vuex、elementui、echarts、axios都引入成cdn的方式 1、在index.html引入CDN 找到public/index.html在上方引入下邊的cdn。 [!NOTE] 引入script的時候&#xff0c;一定要把vue.js放到最上邊&#xff0c;最先引入&#xff0c;不然后邊的js加載會…

49.【.NET8 實戰--孢子記賬--從單體到微服務--轉向微服務】--擴展功能--集成網關--Refit跨服務調用

Refit是一個用于.NET平臺的REST庫,它可以將REST API轉換為實時類型安全的接口。通過Refit,我們可以輕松實現微服務之間的跨服務調用,使服務間通信變得更加簡單和類型安全。本文將介紹如何在我們的項目中使用Refit來實現微服務間的通信。 一、什么是Refit Refit是一個強大的REST…

日志ELK、ELFK、EFK

一.ELK架構Elasticsearch Logstash Kibana 數據庫日志處理日志顯示1.logstash的使用&#xff08;1&#xff09;input&#xff1a;輸入&#xff08;2&#xff09;filter&#xff1a;處理&#xff08;3&#xff09;output&#xff1a;輸出2.ELFK架構Filebeat-->Elasticsearc…

【CUDA進階】MMA分析Bank Conflict與Swizzle(下)

目錄前言1. bank conflict 分析2. 通過 padding 解決 bank conflict3. mma 搭配 wmma 實現矩陣乘法計算3.1 代碼實現3.2 補充&#xff1a;stmatrix_sync 函數分析3.3 補充&#xff1a;__shfl_sync 函數詳解4. swizzle 原理講解5. swizzle 實現思路講解結語下載鏈接參考前言 學習…

天氣查詢系統

項目要求 項目知識點 問題與解決 代碼分部 結果展示 項目要求 1.顯示天氣預報系統界面 2.系統可以通過選擇城市配置獲取不同城市天氣信息 3.查看實時的天氣信息 &#xff08;當前溫度、最高溫度、最低溫度、當前濕度、最高濕度、最低濕度、風向、風力、風級等信息&#x…

三重積分的對稱性

文章目錄前言柱面球面前言 規律作息 柱面 太牛了。我完全看不懂。實際上就類似于極坐標系。 球面 看到這么多東西&#xff0c;我真害怕。今天是 8.30 &#xff0c;不管 9.10 有沒有復習完概率的強化&#xff0c;我都一定要開始套卷&#xff0c;還有專業課的復習。?\phi?…

深入理解 RabbitMQ:從底層原理到實戰落地的全維度指南

引言&#xff1a; 本文總字數&#xff1a;約 18500 字預計閱讀時間&#xff1a;45 分鐘 為什么我們需要 RabbitMQ&#xff1f; 在當今分布式系統架構中&#xff0c;消息隊列已成為不可或缺的核心組件。想象一下&#xff0c;當你在電商平臺下單時&#xff0c;系統需要處理庫存…

寬帶有丟包,重傳高的情況怎么優化

寬帶丟包和重傳率高是一個非常影響網絡體驗的常見問題。它會導致游戲卡頓、視頻通話模糊、網頁加載慢等。別擔心&#xff0c;我們可以按照從易到難的順序&#xff0c;系統地排查和優化。請遵循以下步驟&#xff1a;第一步&#xff1a;基礎排查&#xff08;自己動手&#xff0c;…

Kotlin 協程之Channel 的高階應用

前言 了解了 Channel 的基礎概念和基本使用 后&#xff0c;我們再來看一看 Channel 的特性以及高階應用。 Channel 是"熱流" 熱流概念 Channel 是熱流&#xff08;Hot Stream&#xff09;&#xff0c;具備以下特性&#xff1a; 數據的生產和消費是兩套獨立的流程 …

PostgreSQL表空間(Tablespace)作用(管理數據庫對象的存儲位置)(pg_default、pg_global)

文章目錄**1. 靈活的數據存儲管理**- **邏輯與物理分離**&#xff1a;表空間為數據庫對象&#xff08;如表、索引&#xff09;提供了一個邏輯名稱與物理存儲路徑的映射。用戶無需直接操作底層文件路徑&#xff0c;只需通過表空間名稱管理數據。- **多數據庫共享表空間**&#x…

Ansible 核心運維場景落地:YUM 倉庫、SSH 公鑰、固定 IP 配置技巧

1&#xff1a;如何一次性驗證所有主機能否被 Ansible 訪問&#xff1f; 答&#xff1a;使用臨時命令&#xff1a;ansible all -m ansible.builtin.ping或驗證 sudo 是否正常&#xff1a;ansible all -m ansible.builtin.ping --become -K2&#xff1a;如何用 Ansible 統一配置…

rman導致的報錯ORA-27037: unable to obtain file status

有套3節點的11204集群環境&#xff0c;在db2上配置了rman備份&#xff0c;今天例行檢查時發現db1和db3上不定期有報錯&#xff0c;報錯如下&#xff1a;Control file backup creation failed:failure to open backup target file /u01/app/oracle/product/11.2.0/db_1/dbs/snap…

Kubernetes 與 GitOps 的深度融合實踐指南

前言&#xff1a;在云原生技術飛速發展的今天&#xff0c;Kubernetes&#xff08;簡稱 K8s&#xff09;已成為容器編排領域的事實標準&#xff0c;而 GitOps 作為一種基于 Git 的云原生運維理念&#xff0c;正與 K8s 深度融合&#xff0c;為企業實現自動化、可追溯、可審計的應…

REST-assured 接口測試編寫指南

REST-assured 簡介 REST-assured 是一個基于 Java 的 DSL&#xff08;領域特定語言&#xff09;庫&#xff0c;專門用于簡化 RESTful API 測試的編寫。它提供了流暢的 API 接口&#xff0c;使得測試代碼更加易讀易寫&#xff0c;支持 JSON 和 XML 等多種響應格式的驗證。 基本環…

內網應用如何實現外網訪問?外地通過公網地址訪問內網服務器的設置方法

一、內網應用程序在外網需要連接訪問遇到的問題我們經常需要在內網中部署服務&#xff0c;比如一個 Web 服務器或者數據庫&#xff0c;但由于本地沒有公網IP&#xff0c;這些服務無法直接從外地公網訪問。如自己家里的監控系統&#xff0c;在家時能查看&#xff0c;但出門在外就…