SpringBoot+Quartz+MySQL實現分布式定時任務


第一步:引入依賴

     <!--quartz相關依賴-->
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz</artifactId>
            <version>2.3.0</version>
        </dependency>
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz-jobs</artifactId>
            <version>2.3.0</version>
        </dependency>
        <!--定時任務需要依賴context模塊-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
        </dependency>

第二步:創建MySQL表,Quartz是基於表來感知其他定時任務節點的,節點間不會直接通信。建表語句在jar包中自帶了。

org\quartz-scheduler\quartz\2.3.0\quartz-2.3.0.jar!\org\quartz\impl\jdbcjobstore\tables_mysql_innodb.sql

 

 

 第三步:配置線程池,我這里是因為項目的其他地方有用到線程池,你也可以選擇在Quartz的配置類中注入。

(我在其他位置使用了線程池,占用了一個線程,所以當我將核心線程數量設置為1時,定時任務不會執行;需確保有足夠的線程來執行)

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * @Author 1
 * @Description 配置線程池交給Spring容器管理
 * @Date 2020/8/26 18:23
 **/
@Configuration
public class ExecturConfig {
    @Bean("taskExector")
    public Executor taskExector() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        //核心線程池數量
        executor.setCorePoolSize(2);
        //最大線程數量
        executor.setMaxPoolSize(5);
        //線程池的隊列容量
        executor.setQueueCapacity(10);
        //線程名稱的前綴
        executor.setThreadNamePrefix("expireOrderHandle-");
        //配置拒絕策略
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
        executor.initialize();
        return executor;
    }

}

第四步:因為定時任務業務中需要使用到注入Spring容器的類,所以配置注入,否則報空指針異常。

參考了一位大佬的博客:https://blog.csdn.net/qq_39513430/article/details/104996237

import org.quartz.spi.TriggerFiredBundle;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.scheduling.quartz.AdaptableJobFactory;
import org.springframework.stereotype.Component;

@Component("myAdaptableJobFactory")
public class MyAdaptableJobFactory extends AdaptableJobFactory {

    //AutowireCapableBeanFactory 可以將一個對象添加到SpringIOC容器中,並且完成該對象注入
    @Autowired
    private AutowireCapableBeanFactory autowireCapableBeanFactory;
    
    /**
     * 該方法需要將實例化的任務對象手動的添加到springIOC容器中並且完成對象的注入
     */
    @Override
    protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
        Object obj = super.createJobInstance(bundle);
        //將obj對象添加Spring IOC容器中,並完成注入
        this.autowireCapableBeanFactory.autowireBean(obj);
        return obj;
    }

}

第五步:添加Quartz屬性文件

關於屬性配置解釋參考https://blog.csdn.net/github_36429631/article/details/63254055

#============================================================================
# Configure JobStore
# Using Spring datasource in SchedulerConfig.java
# Spring uses LocalDataSourceJobStore extension of JobStoreCMT
#============================================================================ #設置為TRUE不會出現序列化非字符串類到 BLOB 時產生的類版本問題 org.quartz.jobStore.useProperties=true #quartz相關數據表前綴名 org.quartz.jobStore.tablePrefix = QRTZ_ #開啟分布式部署 org.quartz.jobStore.isClustered = true #分布式節點有效性檢查時間間隔,單位:毫秒 org.quartz.jobStore.clusterCheckinInterval = 20000 #信息保存時間 默認值60秒 org.quartz.jobStore.misfireThreshold = 60000 #事務隔離級別為“讀已提交” org.quartz.jobStore.txIsolationLevelReadCommitted = true #配置線程池實現類 org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate #============================================================================ # Configure Main Scheduler Properties # Needed to manage cluster instances #============================================================================ org.quartz.scheduler.instanceName = ClusterQuartz org.quartz.scheduler.instanceId= AUTO #如果你想quartz-scheduler出口本身通過RMI作為服務器,然后設置“出口”標志true(默認值為false)。 org.quartz.scheduler.rmi.export = false #true:鏈接遠程服務調度(客戶端),這個也要指定registryhost和registryport,默認為false # 如果export和proxy同時指定為true,則export的設置將被忽略 org.quartz.scheduler.rmi.proxy = false org.quartz.scheduler.wrapJobExecutionInUserTransaction = false #============================================================================ # Configure ThreadPool # Can also be configured in spring configuration #============================================================================ #線程池的實現類(一般使用SimpleThreadPool即可滿足幾乎所有用戶的需求) #org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool #org.quartz.threadPool.threadCount = 5 #org.quartz.threadPool.threadPriority = 5 #org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true

第六步:寫任務類

package com.website.task;

import com.website.mapper.WebTeacherMapper;
import com.website.pojo.ao.TeacherSalaryRuleAO;
import com.website.pojo.bo.TeacherSalaryRuleDetailBO;
import com.website.pojo.bo.TeacherSalaryRuleRelationBO;
import com.website.pojo.bo.TeacherSalaryStatTempBO;
import com.website.pojo.bo.TeacherSalaryStatisticBO;
import io.jsonwebtoken.lang.Collections;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.PersistJobDataAfterExecution;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.quartz.QuartzJobBean;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@PersistJobDataAfterExecution
@DisallowConcurrentExecution
@Slf4j
public class QuartzJob extends QuartzJobBean {
    @Autowired
    private WebTeacherMapper webTeacherMapper;

    @Override
    protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
        log.info("統計老師月薪資定時任務開始執行。");
        System.out.println("任務編寫位置");
        log.info("統計老師月薪資定時任務執行完畢。");
    }

}

第七步:配置定時器

import com.website.task.QuartzJob;
import org.quartz.Scheduler;
import org.quartz.TriggerKey;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.PropertiesFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.scheduling.quartz.CronTriggerFactoryBean;
import org.springframework.scheduling.quartz.JobDetailFactoryBean;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;

import javax.sql.DataSource;
import java.io.IOException;
import java.util.Properties;
import java.util.concurrent.Executor;

@Configuration
public class SchedulerConfig {

    @Autowired
    private DataSource dataSource;

    @Autowired
    private Executor taskExector;

    @Autowired
    private MyAdaptableJobFactory myAdaptableJobFactory;



    @Bean
    public Scheduler scheduler() throws Exception {
        Scheduler scheduler = schedulerFactoryBean().getScheduler();
        TriggerKey triggerKey1 = TriggerKey.triggerKey("trigger1", "TriggerTest111");
        /*========如果有必要可以配置刪除任務,開始====================*/
        //停止觸發器
//        scheduler.pauseTrigger(triggerKey1);
        //移除觸發器
//        scheduler.unscheduleJob(triggerKey1);
//        JobKey jobKey1 = JobKey.jobKey("job1111------", "quartzTest--------");
        //刪除任務
//        boolean b = scheduler.deleteJob(jobKey1);
//        System.out.println(b);
        /*=========結束====================*/
        scheduler.start();
        return scheduler;
    }

    @Bean
    public SchedulerFactoryBean schedulerFactoryBean() throws IOException {
        SchedulerFactoryBean factory = new SchedulerFactoryBean();
        //開啟更新job
        factory.setOverwriteExistingJobs(true);
        //如果不配置就會使用quartz.properties中的instanceName
        //factory.setSchedulerName("Cluster_Scheduler");
        //配置數據源,這是quartz使用的表的數據庫存放位置
        factory.setDataSource(dataSource);
        //設置實例在spring容器中的key
        factory.setApplicationContextSchedulerContextKey("applicationContext");
        //配置線程池
        factory.setTaskExecutor(taskExector);
        //配置配置文件
        factory.setQuartzProperties(quartzProperties());
        //設置調度器自動運行
        factory.setAutoStartup(true);
        //配置任務執行規則,參數是一個可變數組
        factory.setTriggers(trigger1().getObject());
        // 解決mapper無法注入問題,此處配合第四步的配置。
 factory.setJobFactory(myAdaptableJobFactory); return factory;
    }


    @Bean
    public Properties quartzProperties() throws IOException {
        PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
        propertiesFactoryBean.setLocation(new ClassPathResource("/quartz.properties"));

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

    @Bean
    public JobDetailFactoryBean job1() {
        JobDetailFactoryBean jobDetail = new JobDetailFactoryBean();
        //配置任務的具體實現
        jobDetail.setJobClass(QuartzJob.class);
        //是否持久化
        jobDetail.setDurability(true);
        //出現異常是否重新執行
        jobDetail.setRequestsRecovery(true);
        //配置定時任務信息
        jobDetail.setName("TeacherSalaryJob");
        jobDetail.setGroup("TeacherSalaryJobGroup");
        jobDetail.setDescription("這是每月1號凌晨統計教師薪資任務");
        return jobDetail;
    }

    @Bean
    public CronTriggerFactoryBean trigger1() {
        CronTriggerFactoryBean cronTrigger = new CronTriggerFactoryBean();
        //定時規則的分組
        cronTrigger.setGroup("TeacherSalaryTriggerGroup");
        cronTrigger.setName("TeacherSalaryTrigger");
        //配置執行的任務jobdetail
        cronTrigger.setJobDetail(job1().getObject());
        //配置執行規則 每月一號0點過1分執行一次
        cronTrigger.setCronExpression("0 1 0 1 * ? ");
        return cronTrigger;
    }

}

到此完畢,另外發現如果執行任務的代碼中報錯,會導致定時任務停止循環,重啟也不會再執行。建議任務內容用try...catch代碼塊包裹起來,打印好日志。

已中斷的任務清空Quartz所有表格,再啟動項目即可再次觸發啟動任務。

如果某一天定時任務突然不執行了,網上很多情況都是遠程調用沒有加超時中斷,從而導致線程阻塞引起的。

 

拋異常中斷周期執行原因暫未明確,有知道的大佬還請不吝賜教。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM