zoukankan      html  css  js  c++  java
  • 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属性指定到对应的表。
  • 相关阅读:
    创建型模式之单例模式
    创建型模式之抽象工厂模式
    创建型模式之工厂模式
    设计模式的6大原则
    设计模式简介以及分类介绍
    线程同步的5种方式
    jvm内存分区及各区线程问题
    leetcode-Best Time to Buy and Sell Stock
    leetcode-Maximum Subarray
    实习小记-大公司小公司
  • 原文地址:https://www.cnblogs.com/Mr-XiaoLiu/p/10444535.html
Copyright © 2011-2022 走看看