Quartz使用記錄總結


Quartz是一個任務調度框架,最近在項目中有用到,所以做個記錄總結。

一、主要元素
  • Scheduler:調度器,控制任務的調度,將JobDetail和Trigger注冊到Scheduler加以控制。
  • Job:任務,是一個接口且只有一個方法void execute(JobExecutionContext context),實現該接口定義任務的執行邏輯。
  • JobDetail:Job實例,一個Job可以創建多個Job實例,每一個實例有自己的屬性。
  • Trigger:觸發器,定義觸發規則。

 

二、簡單使用

  我是在Spring Boot項目中使用的,這個Demo也是基於Spring Boot,實際上還可以更簡潔。Quartz版本為2.3.0。

  1. 增加pom依賴
    <dependency>
          <groupId>org.quartz-scheduler</groupId>
          <artifactId>quartz</artifactId>
          <version>2.3.0</version>
    </dependency>

     

  2. 編寫配置文件
    # quartz.properties
    org.quartz.scheduler.instanceName=TaskScheduler
    org.quartz.threadPool.threadCount=5
    org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX # 數據保存到數據庫,使用JobStoreTX作為JobStore來管理事務
    org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate # 數據庫代理
    org.quartz.jobStore.tablePrefix=QRTZ_ # 表前綴,默認為QRTZ_。主要可用於多個服務數據存儲到同一數據庫,可以創建多組不同的表供不同服務使用。
    org.quartz.jobStore.dataSource=quartzDataSource # 數據源,在下面定義數據源信息的時候需要用到
    
    org.quartz.dataSource.quartzDataSource.driver=com.mysql.jdbc.Driver
    org.quartz.dataSource.quartzDataSource.URL=jdbc:mysql://127.0.0.1:3306/test?autoReconnect=true&autoReconnectForPools=true&useUnicode=true&allowMultiQueries=true&characterEncoding=UTF-8&useSSL=false
    org.quartz.dataSource.quartzDataSource.user=root
    org.quartz.dataSource.quartzDataSource.password=123456
    org.quartz.dataSource.quartzDataSource.maxConnections=5
    org.quartz.dataSource.quartzDataSource.validationQuery=select 1

     

  3. 定義配置類
    @Configuration
    public class QuartzConfig {
    
        @Autowired
        private SpringJobFactory springJobFactory;
        // 配置文件,在application.yml文件中配置
        @Value("${quartz.config}")
        private String quartzConfig;
    
        @Bean(name = "schedulerFactory")
        public SchedulerFactoryBean schedulerFactoryBean() throws IOException {
            SchedulerFactoryBean factory = new SchedulerFactoryBean();
            factory.setAutoStartup(true);
            // 延時5秒啟動
            factory.setStartupDelay(5);
         // 設置配置信息 factory.setQuartzProperties(quartzProperties()); factory.setJobFactory(springJobFactory);
    return factory; } @Bean public Properties quartzProperties() throws IOException { PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean(); propertiesFactoryBean.setLocation(new ClassPathResource(quartzConfig)); propertiesFactoryBean.afterPropertiesSet(); return propertiesFactoryBean.getObject(); } /** * Quartz初始化監聽器 * * @return */ @Bean public QuartzInitializerListener executorListener() { return new QuartzInitializerListener(); } /** * 將Scheduler添加到Spring管理 * * @return * @throws IOException */ @Bean(name = "scheduler") public Scheduler scheduler() throws IOException { return schedulerFactoryBean().getScheduler(); } }

     

  4. 數據庫增加對應表,可以到Quartz發行版的“docs / dbTables”目錄中找到表創建SQL腳本。
  5. 定義Job
    public class TestJob implements Job, Serializable {
        private static final Logger LOGGER = LoggerFactory.getLogger(TestJob.class);
        private static final long serialVersionUID = 1L;
    
        @Override
        public void execute(JobExecutionContext arg0) throws JobExecutionException {
            LOGGER.info("-------------- 執行Quartz測試任務 --------------");
            // Do something ...
        }
    }

     

  6. 創建JobDetail和Trigger並加入調度器(這部分建議寫成接口)
    Class cls = Class.forName("com.xiaoliu.job.TestJob");
    cls.newInstance();
    // 創建JobDetail
    JobDetail job = JobBuilder.newJob(cls).withIdentity("test1",
                        "test")
                        .withDescription("測試任務1").build();
    // 創建觸發器
    CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule("0/10 * * * * ? ")
    // 立即觸發一次,然后按照正常的規則執行下一個周期的任務。
        .withMisfireHandlingInstructionFireAndProceed();
    Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger" + "test1", "test").startNow().withSchedule(cronScheduleBuilder).build();
     // 注冊到scheduler
    scheduler.scheduleJob(job, trigger);

     

   最后運行項目,查看效果。

三、關於Trigger

   使用Quartz的過程需要了解清楚Trigger,它關系到任務觸發規則的定義,以及觸發過程可能遇到的問題處理。在這里只涉及最常用的兩種Trigger:SimpleTrigger和CronTrigger。

 

   先看一下TriggerBuilder這個構造類,里面有各種Trigger的一些公共屬性,主要列舉幾個說明:

  • jobKey:trigger觸發時被執行的job的key。
  • startTime:trigger生效的時間點。
  • endTime:trigger失效的時間點。trigger只在startTime和endTime之間才會被觸發。
  • priority:優先級,默認為5,priority的值可以是任意整數。假設同時執行的trigger有很多,但是Quartz線程池的工作線程很少(沒有足夠的資源同時觸發這些trigger),這個時候會按照優先級高的先觸發。
  • misfire Instructions:錯過觸發策略,trigger定義了一個觸發閥值,在閥值時間范圍內會重新觸發,超過閥值范圍則認為是misfire。因為某種原因,trigger在應該觸發的時候未觸發且錯過了觸發的時機,就需要一定策略來處理misfire,不同的trigger有不同的策略集。所有trigger的默認觸發策略都是MISFIRE_INSTRUCTION_SMART_POLICY,值為0。

 

  ① SimpleTrigger

  可以滿足的調度需求:在具體的時間點執行一次,或者在具體的時間點執行,並且以指定的間隔重復執行若干次。簡單的調度需求可以使用SimpleTrigger。

  SimpleTrigger的主要屬性有:

    • repeatCount:重復間隔
    • repeatInterval:重復次數

例:

SimpleScheduleBuilder.simpleSchedule()
        .withIntervalInSeconds(10) // 10s執行一次
        .repeatForever() // 次數不限
SimpleScheduleBuilder.simpleSchedule()
         .withIntervalInMinutes(1) // 1分鍾執行一次
         .withRepeatCount(10) // 次數為10次

 

  

Misfire策略

  SimpleTrigger的misfire策略有以下幾種:

MISFIRE_INSTRUCTION_SMART_POLICY:0,默認策略。會根據實例的配置及狀態,在所有MISFIRE策略中動態選擇一種Misfire策略。
MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY:-1,忽略所有的超時狀態,按照觸發器的策略執行。
MISFIRE_INSTRUCTION_FIRE_NOW:1,立即執行
MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT:2,立即執行,並重復到指定的次數。
MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT:3,立即執行,且超時期內錯過的執行機會作廢。
MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT:4,以現在為基准,以repeatInterval為周期,延時到下一個激活點執行。
MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_EXISTING_COUNT:5,在下一個激活點執行,並重復到指定的次數。

 

Misfire策略設置方式如下:

SimpleScheduleBuilder.simpleSchedule()
          .withIntervalInSeconds(10)
          .repeatForever()
          .withMisfireHandlingInstructionFireNow()

 

  ② CronTrigger

CronTrigger能支持更復雜的調度需求,通常比SimpleTrigger更有用,CronTrigger的屬性只有Cron Expressions,但Cron表達式的功能非常強大。Cron Expressions是由七個子表達式組成的字符串,用於描述日程表的各個細節。這些子表達式用空格分隔,分別是秒/分/時/日/月/周/年,年不是必須的。

 

表達式 是否必須 允許值 允許的特殊字符
0-59 , - * /
0-59 , - * /
0-23 , - * /
1-31 , - * ? / L W C
1-12 或 JAN-DEC , - * /
1-7 或 SUN-SAT , - * ? / L C #
空 或 1970-2099 , - * /

比如上面例子中的表達式:0/10 * * * * ? ,表示每10s執行一次。cron的具體規則網上很多,不是本文的重點。

Misfire策略

  CronTrigger的misfire策略有以下幾種:

MISFIRE_INSTRUCTION_SMART_POLICY:0,默認策略。在CronTrigger中解釋為MISFIRE_INSTRUCTION_FIRE_ONCE_NOW。
MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY:-1,忽略所有的超時狀態,按照觸發器的策略執行。 MISFIRE_INSTRUCTION_DO_NOTHING:2,什么都不做,然后就按照正常的計划執行。 MISFIRE_INSTRUCTION_FIRE_ONCE_NOW:1,立即觸發一次,觸發后恢復正常的頻率。

Misfire策略設置方式如下:

CronScheduleBuilder.cronSchedule("0/10 * * * * ? ")
.withMisfireHandlingInstructionFireAndProceed();

 

四、問題記錄

  在項目中使用時遇到了一個問題,也是我后面進一步了解Quartz的原因,在此記錄。

  Job本地運行正常,但部署到Linux服務器后,觀察業務發現Job未執行,也找不到相關錯誤日志。

  最開始的思考問題的原因可能是:Linux和Windows系統或其他環境問題,調度策略或者其他配置問題。通過接口手動觸發Job是正常的,修改觸發策略等等配置后依然無法解決。第二天發現測試系統部分任務成功執行了,查看日志發現有ClassNotFoundException,原因是執行了一個不屬於當前服務的Job。問題好像有點苗頭了,查看qrtz_triggers表的記錄,失敗Job對應的trigger記錄trigger_state的值為ERROR,那么原因找到了:多個服務中的Quartz使用同一組表,維護同一組數據。當服務觸發了一個不屬於本服務的Job后(ClassNotFoundException,業務處理失敗),會修改觸發記錄,其他服務就不會重復觸發該任務,也不會產生錯誤日志。

trigger_state的值有:

  • WAITING:等待
  • PAUSED:暫停
  • ACQUIRED:正常執行
  • BLOCKED:阻塞
  • ERROR:錯誤

解決方案:

  1. 任務調度整合在一個服務里,其他服務開放業務處理接口。(服務有多節點時不可行)
  2. 每個服務創建一組數據庫表,通過在配置文件配置org.quartz.jobStore.tablePrefix屬性指定到對應的表。


免責聲明!

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



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