Quartz

142 阅读5分钟

Quartz

理论知识

原理

分层时间轮:分为月轮,天轮等等。 每一个任务有精确的时间坐标

原生Quartz

核心组件

定时任务

对应JobDetail类 包含任务 可以传递数据给任务

触发器

对应Trigger类 包含时间安排 可以传递数据给任务

  • SimpleSchedule作为内核

    包含第一次执行的时间,执行间隔,重复次数,结束时间(如果算出下一次的执行时间大于等于结束时间那就没有下一次了,如果指定的次数都执行完了还没有到结束时间也不会有下一次了。)。

  • CronSchedule作为内核

    对于CronSchedule,包含第一次执行的时间,执行间隔,(没有重复次数,也就是无限次。),结束时间。 Cron表达式在线生成详见链接cron.qqe2.com

小结:定时任务和触发器是一对多的关系

调度器

将定时任务和触发器绑定在一起,并根据触发器来派发任务。 对应Scheduler类 一个调度器完全可以绑定所有的定时任务和触发器了 调度的任务是在另一个线程执行的

SpringBoot集成Quartz

集成之后可以选择将任务等存放到数据库 有了数据库之后就可以集群了 集群思路:刚开始把数据库建好,把任务都存进去,然后再启动集群来执行里面的任务。 集群执行任务的原则:尽可能平均分配任务,同一任务的不同执行可能在不同应用上。 不管集群与否,每个应用启动都先根据配置决定启动时是否初始化数据库(运行sql文件)。

代码部分

原生Quartz

依赖

<dependency>
    <groupId>org.quartz-scheduler</groupId>
    <artifactId>quartz</artifactId>
    <version>2.3.2</version>
</dependency>

任务类

package com.example.quartz.Job;

import org.quartz.*;

import java.util.Date;

//这个注解使得JobDataMap这个map中的数据在多次调用之间不变
//没有的话则每次调用的时候JobDataMap这个map中的数据会被重置为最初设置的
@PersistJobDataAfterExecution
//禁止并发运行多个这个任务,必须等上一个完成了才能进行下一个(但Job对象每次还是会创建新的).
@DisallowConcurrentExecution
public class MyJob implements Job{
    public MyJob(){
        System.out.println("+++++++++++++++++");
    }
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException{
        try{
            Thread.sleep(2000);
        }
        catch(InterruptedException e){
            e.printStackTrace();
        }
        System.out.println("现在时间是:"+System.currentTimeMillis());
        //这两个变量和设置这个任务的两个变量是相等的
        //因此可以来获取之前为它们设置的各种属性
        JobDetail jobDetail=context.getJobDetail();
        Trigger trigger=context.getTrigger();
        System.out.println(trigger.getKey().getName());
        //System.out.println("_________________");
        //System.out.println(trigger.getStartTime());
        //System.out.println(trigger.getEndTime());
        //System.out.println(new Date());
        //System.out.println(trigger.getNextFireTime());
    }
}

需要添加定时任务的时候这么写

package com.example.quartz;

import com.example.quartz.Job.MyJob;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

public class QuartzTest{
    public static void main(String[] args) throws Exception{
        System.out.println(Long.MAX_VALUE);
        //JobDataMap是一个Map,它可以被传递给任务使用.
        JobDataMap jobDataMap1=new JobDataMap();
        jobDataMap1.put("jobDetail-param1",0);
        jobDataMap1.put("jobDetail-param2","value2");
        //构造JobDetail,核心是任务,其他的可以不设置.
        JobDetail jobDetail=JobBuilder.newJob(MyJob.class)
                                      //设置任务名字和组名
                                      .withIdentity("我的第一个任务","我的第一个任务组")
                                      //给任务传的参数放在jobDataMap1中
                                      .usingJobData(jobDataMap1)
                                      .build();
        //JobDataMap是一个Map,它可以被传递给任务使用.
        JobDataMap jobDataMap2=new JobDataMap();
        jobDataMap2.put("trigger-param1","value1");
        jobDataMap2.put("trigger-param2","value2");
        //构造下面要用的startTime
        Calendar calendar=Calendar.getInstance();
        calendar.add(Calendar.SECOND,3);
        Date startTime=calendar.getTime();
        //构造下面要用的endTime
        calendar.add(Calendar.SECOND,3);
        Date endTime=calendar.getTime();
        //构造Trigger,核心是[第一次执行的时间,执行间隔,重复次数,结束时间(如果算出下一次的执行时间大于等于结束时间那就没有下一次了,如果指定的次数都执行完了还没有到结束时间也不会有下一次了.).],
        // 其他的可以不设置.
        Trigger trigger=TriggerBuilder.newTrigger()
                                      //设置触发器名字和组名
                                      .withIdentity("我的第一个触发器","我的第一个触发器组")
                                      //给任务传的参数放在jobDataMap2中
                                      .usingJobData(jobDataMap2)
                                      ////使用SimpleSchedule设置时间间隔和重复次数,默认无数次.
                                      //.withSchedule(SimpleScheduleBuilder.repeatSecondlyForTotalCount(3,2))
                                      //使用CronSchedule设置时间间隔,重复次数为无数次. 下面是Cron表达式
                                      .withSchedule(CronScheduleBuilder.cronSchedule("* * * * * ? *"))
                                      //开始时间
                                      .startAt(startTime)
                                      //结束时间
                                      .endAt(endTime)
                                      .build();
        //调度器(用来绑定任务和触发器,并根据触发器来派发任务.)
        Scheduler scheduler=StdSchedulerFactory.getDefaultScheduler();
        scheduler.scheduleJob(jobDetail,trigger);
        //启动调度器
        scheduler.start();
        //执行2秒
        Thread.sleep(2000);
        //暂停调度器
        scheduler.standby();
        //暂停2秒
        Thread.sleep(2000);
        //再启动调度器
        scheduler.start();
        //关闭调度器
        scheduler.shutdown();
    }
}

SpringBoot集成Quartz

依赖

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

公共配置文件 application.yml

spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driverClassName: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/quartz?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
    username: root
    password: mysql
  quartz:
    #Quartz相关数据存储类型
    job-store-type: jdbc
    jdbc:
      #启动时初始化数据库(运行sql文件)
      initialize-schema: always
    properties:
      org:
        quartz:
          jobStore:
            #集群
            isClustered: true

应用1的特定配置文件 application-1.yml

spring:
  quartz:
    properties:
      org:
        quartz:
          scheduler:
            #实例名(集群名,根据集群名来区分任务.)
            instanceName: test
            #实例id
            instanceId: test1

应用2的特定配置文件 application-2.yml

spring:
  quartz:
    properties:
      org:
        quartz:
          scheduler:
            #实例名(集群名,根据集群名来区分任务.)
            instanceName: test
            #实例id
            instanceId: test2

Service

package com.example.quartz.service;

import org.springframework.stereotype.Service;

@Service
public class MyService{
    public void testService(){
        System.out.println("MyService.testService()");
    }
}

任务类

package com.example.quartz.job;

import com.example.quartz.service.MyService;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.quartz.QuartzJobBean;
import org.springframework.stereotype.Component;

import java.text.SimpleDateFormat;
import java.util.Date;

@Component
public class SpringMyJob extends QuartzJobBean{
    //引入依赖后自动有
    @Autowired
    private Scheduler scheduler;
    //可以直接注入
    @Autowired
    private MyService myService;
    @Override
    protected void executeInternal(JobExecutionContext context) throws JobExecutionException{
        System.out.println("===========================");
        System.out.print("现在在SpringMyJob中:");
        myService.testService();
        System.out.println("现在时间: "+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
        try{
            System.out.println("实例名:"+scheduler.getSchedulerName());
            System.out.println("实例id:"+scheduler.getSchedulerInstanceId());
        }
        catch(SchedulerException e){
            e.printStackTrace();
        }
        System.out.println("===========================");
    }
}

需要添加定时任务的时候这么写

@RestController
class QuartzApplicationTest{
    //引入依赖后自动有
    @Autowired
    private Scheduler scheduler;
    @GetMapping("/test")
    public String test() throws Exception{
        JobDataMap jobDataMap1=new JobDataMap();
        jobDataMap1.put("jobDetail-param1",0);
        jobDataMap1.put("jobDetail-param2","value2");
        //构造JobDetail,核心是任务,其他的可以不设置.
        JobDetail jobDetail=JobBuilder.newJob(SpringMyJob.class)
                                      //设置任务名字和组名
                                      .withIdentity("我的第一个任务","我的第一个任务组")
                                      //给任务传的参数放在jobDataMap1中
                                      .usingJobData(jobDataMap1)
                                      .build();
        Trigger trigger=TriggerBuilder.newTrigger()
                                      //设置触发器名字和组名
                                      .withIdentity("我的第一个触发器","我的第一个触发器组")
                                      //使用CronSchedule设置时间间隔,重复次数为无数次. 下面是Cron表达式
                                      .withSchedule(CronScheduleBuilder.cronSchedule("* * * * * ? *"))
                                      .build();
        Trigger trigger2=TriggerBuilder.newTrigger()
                                       //设置触发器名字和组名
                                       .withIdentity("我的第二个触发器","我的第一个触发器组")
                                       //使用CronSchedule设置时间间隔,重复次数为无数次. 下面是Cron表达式
                                       .withSchedule(CronScheduleBuilder.cronSchedule("* * * * * ? *"))
                                       .forJob(jobDetail)
                                       .build();
        //绑定定时任务和触发器(添加定时任务)
        scheduler.scheduleJob(jobDetail,trigger);
        scheduler.scheduleJob(trigger2);
        return "成功";
    }
}