SpringBoot定時任務(schedule、quartz)

Scheduled

 

只適合處理簡單的計劃任務,不能處理分佈式計劃任務。優勢:是spring框架提供的計劃任務,開發簡單,執行效率比較高。且在計劃任務數量太多的時候,可能出現阻塞,崩潰,延遲啓動等問題。
  

Scheduled定時任務是spring3.0版本之後自帶的一個定時任務。其所屬Spring的資源包爲:spring-context-support。所以需要使用Scheduled定時任務機制時,需要在工程中依賴對應資源,具體如下:

 

<!-- scheduled所屬資源爲spring-context-support -->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context-support</artifactId>
</dependency>

 

如果在spring應用中需要啓用Scheduled定時任務,則需要在啓動類上增加註解@EnableScheduling,代表啓用Scheduled定時任務機制。具體如下:

 

@SpringBootApplication
@EnableScheduling
public class AppStarter {

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

 

Scheduled定時任務的核心在於註解@Scheduled,這個註解的核心屬性是cron,代表定時任務的觸發計劃表達式。這個表達式的格式爲:

 

@Scheduled(cron="seconds minutes hours day month week")

 

 

@Scheduled(cron="seconds minutes hours day month week year")

 

推薦使用第一種表達式形式,因爲在很多其他技術中都有不同的定時任務機制,其中用於設置觸發計劃的表達式都是第一種cron表達式。第二種表達式不能說是Spring Scheduled特有的,也是隻有少數技術支持的。

 

cron表達式中,每個位置的約束如下:

 

 

星號(*):可用在所有字段中,表示對應時間域的每一個時刻,例如,*在分鐘字段時,表示“每分鐘”;


問號(?):該字符只在日期和星期字段中使用,它通常指定爲“無意義的值”,相當於佔位符;


減號(-):表達一個範圍,如在小時字段中使用“10-12”,則表示從10到12點,即10,11,12;


逗號(,):表達一個列表值,如在星期字段中使用“MON,WED,FRI”,則表示星期一,星期三和星期五;


斜槓(/):x/y表達一個等步長序列,x爲起始值,y爲增量步長值。如在秒數字段中使用0/15,則表示爲0,15,30和45秒,而5/15在分鐘字段中表示5,20,35,50,你也可以使用*/y,它等同於0/y;

  

L:該字符只在日期和星期字段中使用,代表“Last”的意思,但它在兩個字段中意思不同。L在日期字段中,表示這個月份的最後一天,如一月的31號,非閏年二月的28號;如果L用在星期中,則表示星期六,等同於7。但是,如果L出現在星期字段裏,而且在前面有一個數值X,則表示“這個月的最後X天”,例如,6L表示該月的最後星期五;

  

W:該字符只能出現在日期字段裏,是對前導日期的修飾,表示離該日期最近的工作日。例如15W表示離該月15號最近的工作日,如果該月15號是星期六,則匹配14號星期五;如果15日是星期日,則匹配16號星期一;如果15號是星期二,那結果就是15號星期二。但必須注意關聯的匹配日期不能夠跨月,如你指定1W,如果1號是星期六,結果匹配的是3號星期一,而非上個月最後的那天。W字符串只能指定單一日期,而不能指定日期範圍;

 

LW組合:在日期字段可以組合使用LW,它的意思是當月的最後一個工作日;

  

井號(#):該字符只能在星期字段中使用,表示當月某個工作日。如6#3表示當月的第三個星期五(6表示星期五,#3表示當前的第三個),而4#5表示當月的第五個星期三,假設當月沒有第五個星期三,忽略不觸發;

  

C:該字符只在日期和星期字段中使用,代表“Calendar”的意思。它的意思是計劃所關聯的日期,如果日期沒有被關聯,則相當於日曆中所有日期。例如5C在日期字段中就相當於日曆5日以後的第一天。1C在星期字段中相當於星期日後的第一天。

 

Cron表達式對特殊字符的大小寫不敏感,對代表星期的縮寫英文大小寫也不敏感。

  

計劃任務Scheduled是通過一個線程池實現的。是一個多線程的調度。SpringBoot會初始化一個線程池,線程池默認大小爲1,專門用於執行計劃任務。每個計劃任務啓動的時候,都從線程池中獲取一個線程執行,如果發生異常,只是執行當前任務的線程發生異常,而不是計劃任務調度線程發生異常。如果當前定時任務還未執行完成,當相同的定時任務又進入到執行週期時,不會觸發新的定時任務。如:

 

@Scheduled(cron="* * * * * ?")
public void test1(){
    Random r = new Random();
    /*int i = r.nextInt(100);
    if(i % 3 == 0){
        throw new RuntimeException("error");
    }*/
    System.out.println(Thread.currentThread().getName() + " cron=* * * * * ? --- " + new Date());
    try{
        Thread.sleep(2000);
    }catch(Exception e){
        e.printStackTrace();
    }
}

 

如結果所示(每次的線程名稱一致,由於前一個定時任務未執行完成,因此造成後一個任務的推遲,而不是1秒執行一次,而是3秒):

 

pool-1-thread-1 cron=* * * * * ? --- Thu Sep 19 02:23:20 CST 2019
pool-1-thread-1 cron=* * * * * ? --- Thu Sep 19 02:23:23 CST 2019
pool-1-thread-1 cron=* * * * * ? --- Thu Sep 19 02:23:26 CST 2019
pool-1-thread-1 cron=* * * * * ? --- Thu Sep 19 02:23:29 CST 2019

 

quartz

 

Quartz是OpenSymphony開源組織在Job scheduling領域又一個開源項目,它可以與J2EE與J2SE應用程序相結合也可以單獨使用。Quartz可以用來創建簡單或爲運行十個,百個,甚至是好幾萬個Jobs這樣複雜的程序。

 

Quartz是一個完全由java編寫的開源作業調度框架。不要讓作業調度這個術語嚇着你。儘管Quartz框架整合了許多額外功能, 但就其簡易形式看,你會發現它易用得簡直讓人受不了!


在開發Quartz相關應用時,只要定義了Job(任務),Trigger(觸發器)和Scheduler(調度器),即可實現一個定時調度能力。其中Scheduler是Quartz中的核心,Scheduler負責管理Quartz應用運行時環境,Scheduler不是靠自己完成所有的工作,是根據Trigger的觸發標準,調用Job中的任務執行邏輯,來完成完整的定時任務調度。


Job - 定時任務內容是什麼。
Trigger - 在什麼時間上執行job。
Scheduler - 維護定時任務環境,並讓觸發器生效。


在SpringBoot中應用Quartz,需要依賴下述資源:

 

<dependencies>
        <!-- scheduled所屬資源爲spring-context-support,在Spring中對Quartz的支持,是集成在spring-context-support包中。
            org.springframework.scheduling.quartz
         -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
        </dependency>
        <!-- Quartz座標 -->
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz</artifactId>
            <version>2.2.1</version>
            <!-- Quartz默認需要slf4j支持。springboot中,提供了更高版本的slf4j -->
            <exclusions>
                <exclusion>
                    <artifactId>slf4j-api</artifactId>
                    <groupId>org.slf4j</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <!-- Spring tx 座標,quartz可以提供分佈式定時任務環境。多個分佈點上的Quartz任務,是通過數據庫實現任務信息傳遞的。
            通過數據庫中的數據,保證一個時間點上,只有一個分佈環境執行定時任務。
         -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
        </dependency>
</dependencies>

 

啓動器添加註解@EnableScheduling:

 

/**
 * @EnableScheduling 必要
 * 開啓定時任務機制。
 */
@SpringBootApplication
@EnableScheduling
public class AppStarter {

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

 

定義JOB任務以及JOB任務調用的模擬業務對象:

 

public class SpringBootQuartzJobDemo implements Job {

    // 用於模擬任務中的業務對象。也可能是數據訪問對象,或其他類型的對象。
    @Autowired
    private CommonsUtil4Quartz commonsUtil4Quartz;
    
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        System.out.println("SpringBootQuartzJobDemo : " + new Date());
        this.commonsUtil4Quartz.testMethod();
    }

}

 

@Component
public class CommonsUtil4Quartz {

    public void testMethod(){
        System.out.println("CommonsUtil4Quartz testMethod run...");
    }
}

 

創建Trigger以及JobDetail對象,並用Schedule配置定時任務:

 

/**
 * 初始化類
 * Quartz環境初始化。
 *
 */
@Configuration
public class QuartzConfiguration {

    /**
     * 創建Job對象。在Spring環境中,創建一個類型的對象的時候,很多情況下,都是通過FactoryBean來間接創建的。
     * 如果有多個Job對象,定義多次方法。
     *
     * 在JobDetailFactoryBean類型中,用於創建JobDetail對象的方法,其底層使用的邏輯是:Class.newInstance()
     * 也就是說,JobDetail對象不是通過Spring容器管理的。
     * 因爲Spring容器不管理JobDetail對象,那麼Job中需要自動裝配的屬性,就無法實現自動狀態。如上JOB的第10行會報空指針異常。
     *
     * 解決方案是:將JobDetail加入到Spring容器中,讓Spring容器管理JobDetail對象。
     * 需要重寫Factory相關代碼。實現Spring容器管理JobDetail。
     * @return
     */
    @Bean
    public JobDetailFactoryBean initJobDetailFactoryBean(){
        JobDetailFactoryBean factoryBean =
                new JobDetailFactoryBean();
        // 提供job類型。
        factoryBean.setJobClass(SpringBootQuartzJobDemo.class);
        
        return factoryBean;
    }
    
    /**
     * 管理Trigger對象
     * CronTrigger - 就是Trigger的一個實現類型。其中用於定義週期時間的是CronSchedulerBuilder
     * 實際上,CronTrigger是用於管理一個Cron表達式的類型。
     * @param jobDetailFactoryBean - 上一個方法初始化的JobDetailFactoryBean
     * @return
     */
    @Bean(name="cronTriggerFactoryBean1")
    public CronTriggerFactoryBean initCronTriggerFactoryBean(
            ){
        CronTriggerFactoryBean factoryBean =
                new CronTriggerFactoryBean();
        
        JobDetailFactoryBean jobDetailFactoryBean = this.initJobDetailFactoryBean();
        
        factoryBean.setJobDetail(jobDetailFactoryBean.getObject());
        
        factoryBean.setCronExpression("0/3 * * * * ?");
        
        return factoryBean;
    }
    
    /**
     * 初始化Scheduler
     * @param cronTriggerFactoryBean - 上一個方法初始化的CronTriggerFactoryBean
     * @return
     */
    @Bean
    public SchedulerFactoryBean initSchedulerFactoryBean(
            CustomJobFactory customJobFactory,
            CronTriggerFactoryBean[] cronTriggerFactoryBean){
        SchedulerFactoryBean factoryBean =
                new SchedulerFactoryBean();
        CronTrigger[] triggers = new CronTrigger[cronTriggerFactoryBean.length];
        for(int i = 0; i < cronTriggerFactoryBean.length; i++){
            triggers[i] = cronTriggerFactoryBean[i].getObject();
        }
        // 註冊觸發器,一個Scheduler可以註冊若干觸發器。
        factoryBean.setTriggers(triggers);
        // 爲Scheduler設置JobDetail的工廠。可以覆蓋掉SpringBoot提供的默認工廠,保證JobDetail中的自動裝配有效。
        factoryBean.setJobFactory(customJobFactory);
        
        return factoryBean;
    }
    
}

 

重寫JobFactory:

 

/**
 * 重寫的工廠對象。
 */
@Component
public class CustomJobFactory extends AdaptableJobFactory {

    /**
     * AutowireCapableBeanFactory : 簡單理解爲Spring容器,是Spring容器Context的一個Bean對象管理工程。
     * 可以實現自動裝配邏輯,和對象創建邏輯。
     * 是SpringIoC容器的一個重要組成部件。
     */
    @Autowired
    private AutowireCapableBeanFactory autowireCapableBeanFactory;
    
    @Override
    protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
        // 通過父類型中的方法,創建JobDetail對象。
        Object obj = super.createJobInstance(bundle);
        // 將JobDetail對象加入到Spring容器中,讓Spring容器管理,並實現自動裝配邏輯。
        this.autowireCapableBeanFactory.autowireBean(obj);
        
        return obj;
    }

}

 

分佈式quartz配置

 

1、資源依賴配置:由於分佈式的原因,Quartz中提供分佈式處理的jar包以及數據庫及連接相關的依賴。

 

<dependencies>
        <!-- scheduled所屬資源爲spring-context-support -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
        </dependency>
        <!-- Quartz座標 -->
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz</artifactId>
            <version>2.2.1</version>
            <exclusions>
                <exclusion>
                    <artifactId>slf4j-api</artifactId>
                    <groupId>org.slf4j</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <!-- 是Quartz中提供分佈式處理的jar包。-->
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz-jobs</artifactId>
            <version>2.2.1</version>
        </dependency>
        <!-- Sprng tx 座標 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
        </dependency>
        <!-- web啓動器 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--jdbc -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <!--mysql -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!-- druid數據庫連接池 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.0.9</version>
        </dependency>
</dependencies>

 

2、提供數據庫相關配置:

 

spring.datasource.url=jdbc:mysql://localhost:3306/quartz?autoReconnect=true&useUnicode=true&characterEncoding=utf-8
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.initialSize=5
spring.datasource.minIdle=5
spring.datasource.maxActive=20
spring.datasource.maxWait=600000
spring.datasource.timeBetweenEvictionRunsMillis=600000
spring.datasource.minEvictableIdleTimeMillis=300000
spring.datasource.validationQuery=SELECT 1
spring.datasource.testWhileIdle=true
spring.datasource.testOnBorrow=false
spring.datasource.testOnReturn=false

server.port=8080

 

3、提供Quartz配置信息,爲Quartz提供數據庫配置信息,如數據庫,表格的前綴之類的。

 

# 是否使用properties作爲數據存儲
org.quartz.jobStore.useProperties=false
# 數據庫中的表格命名前綴
org.quartz.jobStore.tablePrefix = QRTZ_
# 是否是一個集羣,是不是分佈式的任務
org.quartz.jobStore.isClustered = true
# 集羣檢查週期,單位毫秒。可以自定義縮短時間。當某一個節點宕機的時候,其他節點等待多久後開始執行任務。
org.quartz.jobStore.clusterCheckinInterval = 5000
# 單位毫秒, 集羣中的節點退出後,再次檢查進入的時間間隔。
org.quartz.jobStore.misfireThreshold = 60000
# 事務隔離級別
org.quartz.jobStore.txIsolationLevelReadCommitted = true
# 存儲的事務管理類型
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
# 使用的Delegate類型
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
# 集羣的命名,一個集羣要有相同的命名。
org.quartz.scheduler.instanceName = ClusterQuartz
# 節點的命名,可以自定義。AUTO代表自動生成。
org.quartz.scheduler.instanceId= AUTO
# rmi遠程協議是否發佈
org.quartz.scheduler.rmi.export = false
# rmi遠程協議代理是否創建
org.quartz.scheduler.rmi.proxy = false
# 是否使用用戶控制的事務環境觸發執行job。
org.quartz.scheduler.wrapJobExecutionInUserTransaction = false

 

4、初始化數據庫

  

建表語句可以自己在官方網站中查找(Quartz-lib中),使用tables-mysql.sql建表。

  

5、定義JOB類

  

啓動器和普通quartz無差異,但是JOB自身定義有些許差異:

 

/**
 * 使用Spring提供的Quartz相關Job類型實現Job的定義。
 * 父類型QuartzJobBean中,提供了分佈式環境中任務的配置定義。
 * 保證分佈式環境中的任務是有效的。
 */
@PersistJobDataAfterExecution // 當job執行結束,持久化job信息到數據庫
@DisallowConcurrentExecution // 保證job的唯一性(單例)
public class SpringBootQuartzJobDemo extends QuartzJobBean {

    @Override
    protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
        System.out.println("SpringBootQuartzJobDemo : " + new Date());
    }
}

 

6、QuartzConfiguration類型定義

 

@Configuration
public class QuartzConfiguration {

    @Autowired
    private DataSource dataSource;
    /**
     * 創建調度器, 可以省略的。
     * @return
     * @throws Exception
     */
    @Bean
    public Scheduler scheduler() throws Exception {
        Scheduler scheduler = schedulerFactoryBean().getScheduler();
        scheduler.start();
        return scheduler;
    }

    /**
     * 創建調度器工廠bean對象。
     * @return
     * @throws IOException
     */
    @Bean
    public SchedulerFactoryBean schedulerFactoryBean() throws IOException {
        SchedulerFactoryBean factory = new SchedulerFactoryBean();

        factory.setSchedulerName("Cluster_Scheduler");
        factory.setDataSource(dataSource);
        factory.setApplicationContextSchedulerContextKey("applicationContext");
        // 設置調度器中的線程池。
        factory.setTaskExecutor(schedulerThreadPool());
        // 設置觸發器
        factory.setTriggers(trigger().getObject());
        // 設置quartz的配置信息
        factory.setQuartzProperties(quartzProperties());
        return factory;
    }

    /**
     * 讀取quartz.properties配置文件的方法。
     * @return
     * @throws IOException
     */
    @Bean
    public Properties quartzProperties() throws IOException {
        PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
        propertiesFactoryBean.setLocation(new ClassPathResource("/quartz.properties"));

        // 在quartz.properties中的屬性被讀取並注入後再初始化對象
        propertiesFactoryBean.afterPropertiesSet();
        return propertiesFactoryBean.getObject();
    }

    /**
     * 創建Job對象的方法。
     * @return
     */
    @Bean
    public JobDetailFactoryBean job() {
        JobDetailFactoryBean jobDetailFactoryBean = new JobDetailFactoryBean();

        jobDetailFactoryBean.setJobClass(SpringBootQuartzJobDemo.class);
        // 是否持久化job內容
        jobDetailFactoryBean.setDurability(true);
        // 設置是否多次請求嘗試任務。
        jobDetailFactoryBean.setRequestsRecovery(true);

        return jobDetailFactoryBean;
    }

    /**
     * 創建trigger factory bean對象。
     * @return
     */
    @Bean
    public CronTriggerFactoryBean trigger() {
        CronTriggerFactoryBean cronTriggerFactoryBean = new CronTriggerFactoryBean();

        cronTriggerFactoryBean.setJobDetail(job().getObject());
        cronTriggerFactoryBean.setCronExpression("0/2 * * * * ?");

        return cronTriggerFactoryBean;
    }

    /**
     * 創建一個調度器的線程池。
     * @return
     */
    @Bean
    public Executor schedulerThreadPool() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();

        executor.setCorePoolSize(15);
        executor.setMaxPoolSize(25);
        executor.setQueueCapacity(100);

        return executor;
    }
}

 

若JOB任務有定義調用業務等內容,也需要重寫JobFactory,如上述常規quartz,此處不再贅述。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章