quartz配合springboot使用的坑與記錄

公司項目須要用到任務調度,因此用上了quartz。html

目前是試過兩種spring版本的集成,1.5.9跟2.0.3版本,有所區別。spring

1.5的spring並無提供專門的start啓動類,因此不少東西都要本身手動寫,而2.0提供有start,方便不少。數據庫

quartz工做原理:由schedule來調度一個任務,而任務由trigger和jobdetail組成,jobdetail調用job來實現業務規則,這裏借用一張圖:併發

一般來說,一個jobdetail和一個trigger是對應的,他們將設定同一個group名。trigger有簡單trigger,也有cronTrigger,做用是指定觸發規則(觸發幾回、間隔多少等)。而jobdetail用來指定一個job和部分調度規則,咱們要把咱們實現的業務規則寫到job裏面。jobdetail還有一個做用是給job傳遞一些參數(坑1:遺憾的是隻有基礎類型,你沒法傳遞一個Object)。oracle

坑2:很蛋疼的是,schedule的生成並不參與spring的bean生命週期管理,這意味着你沒法在job裏寫業務規則的時候使用@autowire!爲此,spring提供了專門的工廠類來解決這個問題。app

除此以外,quartz還未trigger、job、schedule提供了響應的監聽器,讓你能夠對他們的生命週期作出響應的處理,好比說一個任務觸發前該作什麼,觸發後/結束後該作什麼。框架

若是你將quartz設置爲持久化到數據庫中,那麼以上的設定都是能夠持久化的(坑3:低版本quartz的job分爲靜態非靜態,靜態job的jobdetail數據沒法持久化到庫中),除了監聽器。ide

想讓監聽器在服務重啓後同樣有效的解決方法也很簡單,配置一個全局的監聽類就能夠了,在監聽類中能夠用group來區別不一樣的調度類型,作出不一樣的處理。函數

坑4:cron類型的觸發和簡單觸發彷佛徹底區別開,簡單觸發能夠指定觸發次數,而cron規則不能指定次數,要指定觸發次數,彷佛只能手動計數,而後在監聽器中手動停掉。oop

 

首先是2.0的start集成方式:

yml配置

spring:
  quartz:
    #相關屬性配置
    properties:
      org:
        quartz:
          scheduler:
            instanceName: quartzScheduler
            instanceId: AUTO
          threadPool:
            class: org.quartz.simpl.SimpleThreadPool
            threadCount: 10
            threadPriority: 5
            threadsInheritContextClassLoaderOfInitializingThread: true

這個配置並無實現持久化,也沒有實現集羣,是最簡單的配置。

job須要集成QuartzJobBean

public class MyJob extends QuartzJobBean {
    @Override
    protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        System.out.println("任務啓動");
    }
}

最後須要一個配置類QuartzConfiguration

@Configuration
public class QuartzConfiguration {
    @Bean
    public JobDetail myJobDetail() {
        return JobBuilder.newJob(MyJob.class).withIdentity("myJob").storeDurably().build();
    }

    // 把jobDetail註冊到trigger上去
    @Bean
    public Trigger myJobTrigger() {
        SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule()
                .withIntervalInSeconds(15).repeatForever();

        return TriggerBuilder.newTrigger()
                .forJob(myJobDetail())
                .withIdentity("myJobTrigger")
                .withSchedule(scheduleBuilder)
                .build();
    }
}

很是簡潔,你不須要手動去啓動調度,只須要像這樣,把job、trigger注入,就可使用了。

 

 

1.5的使用就繁瑣得多了:

org.quartz.scheduler.instanceName=DefaultQuartzScheduler
#調度名稱,全局能夠有多個scheduler對象
org.quartz.threadPool.
class=org.quartz.simpl.SimpleThreadPool #線程池實現類,能夠自定義,org.quartz.simpl.SimpleThreadPool是quartz自帶的。
org.quartz.threadPool.threadCount
=20 #可用於併發執行做業的線程數
org.quartz.threadPool.threadPriority
=5 #線程權限值,1-10,默認爲5
org.quartz.jobStore.misfireThreshold
=60000 #用來設置調度引擎對觸發器超時的忍耐時間,有可能任務觸發時線程池已滿或者調度掛了,但不超過這個時間就不算超時。
org.quartz.jobStore.
class= org.quartz.impl.jdbcjobstore.JobStoreTX #指定使用的JobStore,有org.quartz.simpl.RAMJobStore和org.quartz.impl.jdbcjobstore.JobStoreTX,前者表明數據存於內存中,後者表明持久化到數據庫。
org.quartz.jobStore.useProperties
=false #若爲true,則JobDataMaps中的全部值都將是String類型,false就可使用全部基本類型。
org.quartz.jobStore.driverDelegateClass
= org.quartz.impl.jdbcjobstore.oracle.OracleDelegate #不一樣的數據庫對應不一樣的DriverDelegate
org.quartz.jobStore.tablePrefix
=qrtz_ #表前綴,這將體如今數據庫表名上
org.quartz.jobStore.dataSource
=qzDS #設置JobStore應該使用哪一個DataSource,這對應着dataSource後面的那個屬性名 org.quartz.dataSource.qzDS.driver= oracle.jdbc.OracleDriver org.quartz.dataSource.qzDS.URL= jdbc:oracle:thin:@192.168.1.1:1521/orclpdb org.quartz.dataSource.qzDS.user= admin org.quartz.dataSource.qzDS.password= admin

#設置數據庫的各項信息
org.quartz.triggerListener.NAME.class = com.ly.cloud.datacollection.quartz.Listener.LoopTaskTriggerListener
#設置監聽器,除此以外還有SchedulerListeners、JobListener能夠配

這裏要注意,這樣配置依舊是未生效的,你必須手動加載配置。

通常來講,scheduler能夠new,也可使用quartz的全局scheduler。

Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler(); 

這裏要使用配置生成,咱們使用spring來進行配置讀取和bean注入。能夠在spring啓動的時候幹這個事,建立一個ApplicationStartQuartzJobListener,來監聽spring生命週期,當啓動的時候進行scheduler的注入。

@Configuration
@Slf4j
public class ApplicationStartQuartzJobListener implements ApplicationListener<ContextRefreshedEvent> {

    @Autowired
    private QuartzUtils quartzUtils;

    @Autowired
    private LoopTaskMapper loopTaskMapper;

    @Autowired
    private OrdinaryTaskMapper ordinaryTaskMapper;

    /**
     * 初始啓動quartz
     */
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        
    }

    /**
     * 初始注入scheduler
     *
     * @return
     * @throws SchedulerException
     */
    @Bean
    public Scheduler scheduler(SchedulerFactoryBean schedulerFactoryBean) throws SchedulerException {
        return schedulerFactoryBean.getScheduler();
    }

    @Bean
    public MyJobFactory jobFactory() {
        return new MyJobFactory();
    }

    @Bean
    public SchedulerFactoryBean schedulerFactoryBean(MyJobFactory jobFactory) throws IOException {
        SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
        schedulerFactoryBean.setJobFactory(jobFactory);
        schedulerFactoryBean.setQuartzProperties(quartzProperties());
        return schedulerFactoryBean;
    }

    @Bean
    public Properties quartzProperties() throws IOException {
        PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
        propertiesFactoryBean.setLocation(new ClassPathResource("/quartz.properties"));
        // 在quartz.properties中的屬性被讀取並注入後再初始化對象
        propertiesFactoryBean.afterPropertiesSet();
        return propertiesFactoryBean.getObject();
    }
}

前面說過,job裏是沒法使用@autowire的,解決方法是使用spring提供的SpringBeanJobFactory類。因此咱們還得新建這個。

public class MyJobFactory extends SpringBeanJobFactory {

    @Autowired
    private AutowireCapableBeanFactory capableBeanFactory; @Override protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception { //調用父類的方法 Object jobInstance = super.createJobInstance(bundle); //進行注入  capableBeanFactory.autowireBean(jobInstance); return jobInstance; } }

 

這樣,咱們在其餘地方就能夠用@autowire來取這個scheduler了。

    @Autowired
    private Scheduler scheduler;

 

調度的建立使用大致都是同樣的,trigger、jobDetail、job、listener。

        String cronRuler = "* * 4 * ? *";
        Date taskStartDate = new Date();
        
        JobDetail jobDetail = JobBuilder
                .newJob(MyJob.class)
                .withIdentity("123456", "jobGrounp")
                .usingJobData("name", "張三")
                .build();
        CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cronRuler);
        
        CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity("123456", "jobGrounp")
            .withSchedule(cronScheduleBuilder)
            .startAt(taskStartDate)
            .build();    
        scheduler.scheduleJob(jobDetail, cronTrigger);
        scheduler.start();

usingJobData("name", "張三") 能夠用於將必要的變量(基礎類型)傳遞到job中使用。

JobDataMap dataMap = jobExecutionContext.getJobDetail().getJobDataMap();

String name= dataMap.getString("name");

在job中取觸發時間:

Date date = jobExecutionContext.getFireTime();

在job中再次存取變量:

            dataMap.put("fireTimes", fireTimes);
            jobExecutionContext.getJobDetail().getJobBuilder().setJobData(dataMap);

監聽器的建立也差很少,繼承實現就行了,可是此處有三個坑,監聽器必須由一個getName屬性返回一個名字,必須有一個無參構造函數,另外全局的listener同樣沒法經過@autowire注入,由於它不參與spring bean的管理!

想要注入只能用ApplicationContext了。

@Component
@Slf4j
public class MyTriggerListener implements TriggerListener {

    // 監聽器的名字,必須有個名字
    public static final String LISTENER_NAME = "MyTriggerListener";
    
    public MyTriggerListener() {

    }

    @Override
    public String getName() {
        return LISTENER_NAME;
    }

    @Override
    public void triggerFired(Trigger trigger, JobExecutionContext context) {

    }

    @Override
    public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context) {
        return false;
    }

    @Override
    @Transactional
    public void triggerMisfired(Trigger trigger) {
    
    }

    @Override
    @Transactional
    public void triggerComplete(Trigger trigger, JobExecutionContext context,
            CompletedExecutionInstruction triggerInstructionCode) {
            
    }

}

推薦是寫一個QuartzUtill來管理scheduler的啓動、銷燬等操做。cron表達式建議去https://www.pppet.net/擼一下,以確保準確性。

 

 

oracle建表語句,並非全部的表都用到,看你使用的場景,能夠擇選。

DROP TABLE  QRTZ_FIRED_TRIGGERS;
DROP TABLE QRTZ_PAUSED_TRIGGER_GRPS;
DROP TABLE QRTZ_SCHEDULER_STATE;
DROP TABLE QRTZ_LOCKS;
DROP TABLE QRTZ_SIMPLE_TRIGGERS;
DROP TABLE QRTZ_SIMPROP_TRIGGERS;
DROP TABLE QRTZ_CRON_TRIGGERS;
DROP TABLE QRTZ_BLOB_TRIGGERS;
DROP TABLE QRTZ_TRIGGERS;
DROP TABLE QRTZ_JOB_DETAILS;
DROP TABLE QRTZ_CALENDARS;
 
 
-- 存儲每個已配置的 Job 的詳細信息
CREATE TABLE qrtz_job_details
(
  SCHED_NAME VARCHAR2(120) NOT NULL,
  JOB_NAME  VARCHAR2(200) NOT NULL,
  JOB_GROUP VARCHAR2(200) NOT NULL,
  DESCRIPTION VARCHAR2(250) NULL,
  JOB_CLASS_NAME   VARCHAR2(250) NOT NULL,
  IS_DURABLE VARCHAR2(1) NOT NULL,
  IS_NONCONCURRENT VARCHAR2(1) NOT NULL,
  IS_UPDATE_DATA VARCHAR2(1) NOT NULL,
  REQUESTS_RECOVERY VARCHAR2(1) NOT NULL,
  JOB_DATA BLOB NULL,
  CONSTRAINT QRTZ_JOB_DETAILS_PK PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
);
--  存儲已配置的 Trigger 的信息
CREATE TABLE qrtz_triggers
(
  SCHED_NAME VARCHAR2(120) NOT NULL,
  TRIGGER_NAME VARCHAR2(200) NOT NULL,
  TRIGGER_GROUP VARCHAR2(200) NOT NULL,
  JOB_NAME  VARCHAR2(200) NOT NULL,
  JOB_GROUP VARCHAR2(200) NOT NULL,
  DESCRIPTION VARCHAR2(250) NULL,
  NEXT_FIRE_TIME NUMBER(13) NULL,
  PREV_FIRE_TIME NUMBER(13) NULL,
  PRIORITY NUMBER(13) NULL,
  TRIGGER_STATE VARCHAR2(16) NOT NULL,
  TRIGGER_TYPE VARCHAR2(8) NOT NULL,
  START_TIME NUMBER(13) NOT NULL,
  END_TIME NUMBER(13) NULL,
  CALENDAR_NAME VARCHAR2(200) NULL,
  MISFIRE_INSTR NUMBER(2) NULL,
  JOB_DATA BLOB NULL,
  CONSTRAINT QRTZ_TRIGGERS_PK PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
  CONSTRAINT QRTZ_TRIGGER_TO_JOBS_FK FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
  REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP)
);
-- 存儲簡單的 Trigger,包括重複次數,間隔,以及已觸的次數
CREATE TABLE qrtz_simple_triggers
(
  SCHED_NAME VARCHAR2(120) NOT NULL,
  TRIGGER_NAME VARCHAR2(200) NOT NULL,
  TRIGGER_GROUP VARCHAR2(200) NOT NULL,
  REPEAT_COUNT NUMBER(7) NOT NULL,
  REPEAT_INTERVAL NUMBER(12) NOT NULL,
  TIMES_TRIGGERED NUMBER(10) NOT NULL,
  CONSTRAINT QRTZ_SIMPLE_TRIG_PK PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
  CONSTRAINT QRTZ_SIMPLE_TRIG_TO_TRIG_FK FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
  REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);
-- 存儲 Cron Trigger,包括 Cron 表達式和時區信息
CREATE TABLE qrtz_cron_triggers
(
  SCHED_NAME VARCHAR2(120) NOT NULL,
  TRIGGER_NAME VARCHAR2(200) NOT NULL,
  TRIGGER_GROUP VARCHAR2(200) NOT NULL,
  CRON_EXPRESSION VARCHAR2(120) NOT NULL,
  TIME_ZONE_ID VARCHAR2(80),
  CONSTRAINT QRTZ_CRON_TRIG_PK PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
  CONSTRAINT QRTZ_CRON_TRIG_TO_TRIG_FK FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
  REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);
CREATE TABLE qrtz_simprop_triggers
(
  SCHED_NAME VARCHAR2(120) NOT NULL,
  TRIGGER_NAME VARCHAR2(200) NOT NULL,
  TRIGGER_GROUP VARCHAR2(200) NOT NULL,
  STR_PROP_1 VARCHAR2(512) NULL,
  STR_PROP_2 VARCHAR2(512) NULL,
  STR_PROP_3 VARCHAR2(512) NULL,
  INT_PROP_1 NUMBER(10) NULL,
  INT_PROP_2 NUMBER(10) NULL,
  LONG_PROP_1 NUMBER(13) NULL,
  LONG_PROP_2 NUMBER(13) NULL,
  DEC_PROP_1 NUMERIC(13,4) NULL,
  DEC_PROP_2 NUMERIC(13,4) NULL,
  BOOL_PROP_1 VARCHAR2(1) NULL,
  BOOL_PROP_2 VARCHAR2(1) NULL,
  CONSTRAINT QRTZ_SIMPROP_TRIG_PK PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
  CONSTRAINT QRTZ_SIMPROP_TRIG_TO_TRIG_FK FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
  REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);
-- Trigger 做爲 Blob 類型存儲(用於 Quartz 用戶用 JDBC 建立他們本身定製的 Trigger 類型,<span style="color:#800080;">JobStore</span> 並不知道如何存儲實例的時候)
CREATE TABLE qrtz_blob_triggers
(
  SCHED_NAME VARCHAR2(120) NOT NULL,
  TRIGGER_NAME VARCHAR2(200) NOT NULL,
  TRIGGER_GROUP VARCHAR2(200) NOT NULL,
  BLOB_DATA BLOB NULL,
  CONSTRAINT QRTZ_BLOB_TRIG_PK PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
  CONSTRAINT QRTZ_BLOB_TRIG_TO_TRIG_FK FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
  REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);
-- 以 Blob 類型存儲 Quartz 的 Calendar 信息
CREATE TABLE qrtz_calendars
(
  SCHED_NAME VARCHAR2(120) NOT NULL,
  CALENDAR_NAME  VARCHAR2(200) NOT NULL,
  CALENDAR BLOB NOT NULL,
  CONSTRAINT QRTZ_CALENDARS_PK PRIMARY KEY (SCHED_NAME,CALENDAR_NAME)
);
-- 存儲已暫停的 Trigger 組的信息
CREATE TABLE qrtz_paused_trigger_grps
(
  SCHED_NAME VARCHAR2(120) NOT NULL,
  TRIGGER_GROUP  VARCHAR2(200) NOT NULL,
  CONSTRAINT QRTZ_PAUSED_TRIG_GRPS_PK PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP)
);
-- 存儲與已觸發的 Trigger 相關的狀態信息,以及相聯 Job 的執行信息
CREATE TABLE qrtz_fired_triggers
(
  SCHED_NAME VARCHAR2(120) NOT NULL,
  ENTRY_ID VARCHAR2(95) NOT NULL,
  TRIGGER_NAME VARCHAR2(200) NOT NULL,
  TRIGGER_GROUP VARCHAR2(200) NOT NULL,
  INSTANCE_NAME VARCHAR2(200) NOT NULL,
  FIRED_TIME NUMBER(13) NOT NULL,
  SCHED_TIME NUMBER(13) NOT NULL,
  PRIORITY NUMBER(13) NOT NULL,
  STATE VARCHAR2(16) NOT NULL,
  JOB_NAME VARCHAR2(200) NULL,
  JOB_GROUP VARCHAR2(200) NULL,
  IS_NONCONCURRENT VARCHAR2(1) NULL,
  REQUESTS_RECOVERY VARCHAR2(1) NULL,
  CONSTRAINT QRTZ_FIRED_TRIGGER_PK PRIMARY KEY (SCHED_NAME,ENTRY_ID)
);
-- 存儲少許的有關 Scheduler 的狀態信息,和別的 Scheduler 實例(假如是用於一個集羣中)
CREATE TABLE qrtz_scheduler_state
(
  SCHED_NAME VARCHAR2(120) NOT NULL,
  INSTANCE_NAME VARCHAR2(200) NOT NULL,
  LAST_CHECKIN_TIME NUMBER(13) NOT NULL,
  CHECKIN_INTERVAL NUMBER(13) NOT NULL,
  CONSTRAINT QRTZ_SCHEDULER_STATE_PK PRIMARY KEY (SCHED_NAME,INSTANCE_NAME)
);
-- 存儲程序的悲觀鎖的信息(假如使用了悲觀鎖)
CREATE TABLE qrtz_locks
(
  SCHED_NAME VARCHAR2(120) NOT NULL,
  LOCK_NAME  VARCHAR2(40) NOT NULL,
  CONSTRAINT QRTZ_LOCKS_PK PRIMARY KEY (SCHED_NAME,LOCK_NAME)
);
 
create index idx_qrtz_j_req_recovery on qrtz_job_details(SCHED_NAME,REQUESTS_RECOVERY);
create index idx_qrtz_j_grp on qrtz_job_details(SCHED_NAME,JOB_GROUP);
 
create index idx_qrtz_t_j on qrtz_triggers(SCHED_NAME,JOB_NAME,JOB_GROUP);
create index idx_qrtz_t_jg on qrtz_triggers(SCHED_NAME,JOB_GROUP);
create index idx_qrtz_t_c on qrtz_triggers(SCHED_NAME,CALENDAR_NAME);
create index idx_qrtz_t_g on qrtz_triggers(SCHED_NAME,TRIGGER_GROUP);
create index idx_qrtz_t_state on qrtz_triggers(SCHED_NAME,TRIGGER_STATE);
create index idx_qrtz_t_n_state on qrtz_triggers(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP,TRIGGER_STATE);
create index idx_qrtz_t_n_g_state on qrtz_triggers(SCHED_NAME,TRIGGER_GROUP,TRIGGER_STATE);
create index idx_qrtz_t_next_fire_time on qrtz_triggers(SCHED_NAME,NEXT_FIRE_TIME);
create index idx_qrtz_t_nft_st on qrtz_triggers(SCHED_NAME,TRIGGER_STATE,NEXT_FIRE_TIME);
create index idx_qrtz_t_nft_misfire on qrtz_triggers(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME);
create index idx_qrtz_t_nft_st_misfire on qrtz_triggers(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME,TRIGGER_STATE);
create index idx_qrtz_t_nft_st_misfire_grp on qrtz_triggers(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME,TRIGGER_GROUP,TRIGGER_STATE);
 
create index idx_qrtz_ft_trig_inst_name on qrtz_fired_triggers(SCHED_NAME,INSTANCE_NAME);
create index idx_qrtz_ft_inst_job_req_rcvry on qrtz_fired_triggers(SCHED_NAME,INSTANCE_NAME,REQUESTS_RECOVERY);
create index idx_qrtz_ft_j_g on qrtz_fired_triggers(SCHED_NAME,JOB_NAME,JOB_GROUP);
create index idx_qrtz_ft_jg on qrtz_fired_triggers(SCHED_NAME,JOB_GROUP);
create index idx_qrtz_ft_t_g on qrtz_fired_triggers(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP);
 
create index idx_qrtz_ft_tg on qrtz_fired_triggers(SCHED_NAME,TRIGGER_GROUP);

 

 

引用:

Quartz官方文檔

Quartz Scheduler misfireThreshold屬性的意義與觸發器超時後的處理策略

Quartz任務持久化和配置管理

任務調度框架quartz使用總結(異常處理,解決恢復後屢次調度處理)

SpringBoot集成Quartz動態定時任務

相關文章
相關標籤/搜索