quartz学习笔记

Posted 若曦`

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了quartz学习笔记相关的知识,希望对你有一定的参考价值。

1. quartz组件

(1) Job

编写Job类 (任务类),实现Job接口,重写exeute方法,此方法就是要执行的任务,类似于Runable

public class MyJob implements Job 
    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException 
        System.out.println("任务正在执行 - "+ LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
    

(2) JobDetail

JobDetail是对Job的封装可以设置许多属性,还包括一个比较重要的JobDataMap,用来存储Job实例的状态信息

调度器需要借助JobDetail对象来添加Job实例

JobDetail jobDetail = JobBuilder.newJob(MyJob.class)
        .withIdentity("任务1","组1")
        .build();

(3) trigger

触发器用来告诉调度程序任务什么时候触发,框架提供了5种触发器类型,但两个最常用的SimpleTrigger和CronTrigger

  • SimpleTrigger:执行N次,重复N次
  • CronTrigger:几秒 几分 几时 哪日 哪月 哪周 哪年,执行
//触发器
Trigger trigger = TriggerBuilder.newTrigger()
           .withIdentity("触发器1","组1")
           //触发策略  可以设置是间隔执行,还是到某个时间执行
           //此处为间隔1秒执行一次,一直执行
           .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(1).repeatForever())
           .build();

trigger的优先级

  • 优先级数字越大触发器优先级越高,在触发器并发执行情况下,优先级越高的触发器执行越靠前

  • 若触发器优先级未设置,那么优先级最低

  Trigger trigger = TriggerBuilder.newTrigger()
          //设置trigger的优先级
          .withPriority(10)
          .withIdentity("触发器1","组1")
          .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(1).repeatForever())
          .build();

(4) Scheduler

Scheduler称之为调度器,用于管理触发器和Job,例如启动某一任务,暂停某一任务等职责。

Job会被注册进Scheduler中,Scheduler通过调用Trigger来执行

//调度器
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
//将job与trigger都放入调度器中
scheduler.scheduleJob(jobDetail, trigger);
//启动定时任务
scheduler.start();

调度器的几种行为

  • start 开始
  • stop 停止
  • pause暂停
  • resume重试

(5) JobExecuteContext

看名字就知道,这是任务执行时的上下文,在实现Job方法重写excute时,作为参数传入任务中

public class MyJob implements Job 

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException 
        //从context中获取job与trigger的名称  
        System.out.println("任务名为:"+context.getJobDetail().getKey()+"---触发器名为:"+context.getTrigger().getKey().getName());
    

  • 当scheduler调用一个job,就会将jobExecuteContext传递给job的execute方法

  • job能够通过该对象访问到quartz运行时候的环境以及job本身的明细数据

(6) jobDataMap

jobDataMap用于存储Job实例的状态信息,在excute执行中,可以通过JobExecuteContext去获取map中的值

  JobDetail jobDetail = JobBuilder.newJob(MyJob.class)
                .withIdentity("任务1","组1")
                //将map信息存放进jobDataMap中
                .usingJobData("jobKey","jobValue")
                .build();
        
  Trigger trigger = TriggerBuilder.newTrigger()
          .withIdentity("触发器1","组1")
          .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(1).repeatForever())
          //将map信息存放进jobDataMap中
          .usingJobData("triggerKey","triggerValue")
          .build();
public class MyJob implements Job 

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException 
        //从context中获取JobDataMap存储的信息
        System.out.println(context.getJobDetail().getJobDataMap().get("jobKey"));
        System.out.println(context.getTrigger().getJobDataMap().get("triggerKey"));
    

2. Job的并发执行

在Job任务每次执行时,JobDetail都会生成一个新的实例(包括内部的Job),例如间隔一秒执行一次任务,那么间隔一秒就会生成一个新的jobdetail实例

这是为了规避并发访问的问题,例比如一个任务要执行10秒,而调度算法是每秒钟触发1次,创建新的实例就是为了使每次调度都能执行,此时就有可能多个任务被并发执行

取消任务的并发执行

  • @DisallowConcurrentExecution 禁止并发地执行同一个JobDetail
  • @PersistJobDataAfterExecution 使JobDataMap跟随每一个任务执行而变动 (不使用的情况下新建jobdetail该Map也会新建)

这两个注解一般搭配使用

@DisallowConcurrentExecution
@PersistJobDataAfterExecution
public class MyJob implements Job 

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException 
        //用户判断是否是同一个对象
        System.out.println("jobDetail:"+System.identityHashCode(context.getJobDetail()));
        System.out.println("job:"+System.identityHashCode(context.getJobInstance()));
    

注意点: @DisallowConcurrentExecution是对JobDetail实例生效,也如果定义两个JobDetail,引用一个Job类,是可以并发执行的

3. Quartz的线程池

定时器要调度多个定时任务,就得有一个线程池来进行任务的并发处理

当执行schedulerFactory.getScheduler()时,会初始化一个线程池SimpleThreadPool


在源码中,默认创建的线程池是一个SimpleThreadPool

  • 此SimpleThreadPool是一个比较简单的线程池实现,只有线程数这一个属性,不像其他功能比较丰富的线程池有像核心线程数、最大线程数、队列大小等参数

  • 此SimpleThreadPool的默认线程数为10

修改quartz线程池 的线程数量

可以在配置文件或配置类中进行配置

 //代码配置
 Properties prop = new Properties();
 // 线程池配置
 prop.put("org.quartz.threadPool.threadCount", "20");
 SchedulerFactory schedulerFactory = new StdSchedulerFactory(prop);
 Scheduler scheduler = schedulerFactory.getScheduler();

4. 任务错过触发的情况

列出了以下几种常见的情况

  • 当job达到触发时间时,所有线程都被其他job占用,没有可用线程

  • 在job需要触发的时间点,scheduler停止了(可能是手动调用pasue等方法,但也可能是意外停止的)

  • job使用了@ DisallowConcurrentExecution注解,job不能并发执行,当达到下一个job执行点的时候,上一个任务还没有完成

  • job指定了过去的开始执行时间,例如当前时间是8点00分0O秒,指定开始时间为7点00分00秒

任务错过的解决措施

(1) 增加线程池数,防止线程数不够的情况
(2) 设置任务执行的阈值,在到时间点后一段时间内仍可以执行

  Properties prop = new Properties();
  //1.线程池配置
  prop.put("org.quartz.threadPool.threadCount", "20");
  //2.设置任务执行时间阈值  单位是毫秒
  prop.put("org.quartz.jobStore.misfireThreshold", "10000");
  SchedulerFactory schedulerFactory = new StdSchedulerFactory(prop);
  Scheduler scheduler = schedulerFactory.getScheduler();

(3) 设置错过的处理策略

具体的处理策略有很多,实际使用时可以自行百度

 Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("触发器1", "组1")
                .withSchedule(
                        SimpleScheduleBuilder.simpleSchedule()
                                .withIntervalInSeconds(3)
                                .repeatForever()
                                // 设置错失触发后的调度策略 
                                .withMisfireHandlingInstructionNowWithRemainingCount()
                )
                .build();

5. SpringBoot整合quartz

maven依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-quartz</artifactId>
</dependency>

简单使用方式

(1) 启动类上添加@EnableScheduling

(2) 配置定时任务@Scheduled

配置方式使用

(1) quartz配置

默认情况下,Quartz 会加载 classpath 下的 quartz.properties 作为配置文件。如果找不到,则会使用 quartz 框架自己 jar 包下 org/quartz 底下的 quartz.properties 文件


(2) quartz 配置类

/**
 * @author ruoxi
 * @createTime 2022/9/14 16:41
 */
@Configuration
public class QuartzConfig implements SchedulerFactoryBeanCustomizer 

    @Bean
    public Properties properties() throws IOException 
        PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
        // 对quartz.properties文件进行读取
        propertiesFactoryBean.setLocation(new ClassPathResource("/quartz.properties"));
        // 在quartz.properties中的属性被读取并注入后再初始化对象
        propertiesFactoryBean.afterPropertiesSet();
        return propertiesFactoryBean.getObject();
    

    @Bean
    public SchedulerFactoryBean schedulerFactoryBean() throws IOException 
        SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
        schedulerFactoryBean.setQuartzProperties(properties());
        return schedulerFactoryBean;
    

    /**
     * quartz初始化监听器
     */
    @Bean
    public QuartzInitializerListener executorListener() 
        return new QuartzInitializerListener();
    

    /**
     * 通过SchedulerFactoryBean获取Scheduler的实例
     */
    @Bean
    public Scheduler scheduler() throws IOException 
        return schedulerFactoryBean().getScheduler();
    

    /**
     * 使用阿里的druid作为数据库连接池
     */
    @Override
    public void customize(@NotNull SchedulerFactoryBean schedulerFactoryBean) 
        schedulerFactoryBean.setStartupDelay(2);
        schedulerFactoryBean.setAutoStartup(true);
        schedulerFactoryBean.setOverwriteExistingJobs(true);
    

(3)使用

/**
 * @author ruoxi
 * @createTime 2022/9/14 16:45
 */
@Service
@Log4j
public class QuartzService 

    @Autowired
    private Scheduler scheduler;

    /**
     * 新增定时任务
     *
     * @param jName 任务名称
     * @param jGroup 任务组
     * @param tName 触发器名称
     * @param tGroup 触发器组
     * @param cron cron表达式
     */
    public void addjob(String jName, String jGroup, String tName, String tGroup, String cron) 
        try 
            // 构建JobDetail
            JobDetail jobDetail = JobBuilder.newJob(HelloJob.class)
                    .withIdentity(jName, jGroup)
                    .build();
            // 按新的cronExpression表达式构建一个新的trigger
            CronTrigger trigger = TriggerBuilder.newTrigger()
                    .withIdentity(tName, tGroup)
                    .startNow()
                    .withSchedule(CronScheduleBuilder.cronSchedule(cron))
                    .build();
            // 启动调度器
            scheduler.start();
            scheduler.scheduleJob(jobDetail, trigger);
         catch (Exception e) 
            System.out.println("创建定时任务失败" + e);
        
    

    public void pausejob(String jName, String jGroup) throws SchedulerException 
        scheduler.pauseJob(JobKey.jobKey(jName, jGroup));
    

    public void resumejob(String jName, String jGroup) throws SchedulerException 
        scheduler.resumeJob(JobKey.jobKey(jName, jGroup));
    

    public void rescheduleJob(String jName, String jGroup, String cron) throws SchedulerException 
        TriggerKey triggerKey = TriggerKey.triggerKey(jName, jGroup);
        // 表达式调度构建器
        CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cron);
        CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
        // 按新的cronExpression表达式重新构建trigger
        trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();
        // 按新的trigger重新设置job执行,重启触发器
        scheduler.rescheduleJob(triggerKey, trigger);
    

    public void deletejob(String jName, String jGroup) throws SchedulerException 
        scheduler.pauseTrigger(TriggerKey.triggerKey(jName, jGroup));
        scheduler.unscheduleJob(TriggerKey.triggerKey(jName, jGroup));
        scheduler.deleteJob(JobKey.jobKey(jName, jGroup));
    

具体的cron表达式可以看这篇博客 Cron表达式

以上是关于quartz学习笔记的主要内容,如果未能解决你的问题,请参考以下文章

quartz学习笔记

quartz学习笔记

iOS学习笔记08-Quartz2D绘图

分布式定时任务调度框架 - Quartz学习及实战记录笔记

分布式定时任务调度框架 - Quartz学习及实战记录笔记

iOS学习笔记--Quartz2D