Quartz定时任务

Quartz简介

Quartz代码

   		try {
            //创建调度程序实体类
            Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
            scheduler.start();
            //任务详情
            JobDetail job = JobBuilder.newJob(HelloJob.class)
                    .withIdentity("job1","group1")
                    .build();
            //触发器1
            Trigger trigger1 = TriggerBuilder.newTrigger()
                    .withIdentity("trigger1","group1")
                    .startNow()
                    //设置调度策略
                    .withSchedule(SimpleScheduleBuilder
                            .simpleSchedule()
                            .withIntervalInSeconds(1)
                            .repeatForever())
                    .build();

            //调度任务
            scheduler.scheduleJob(job,trigger1);

            TimeUnit.SECONDS.sleep(3);
            //关闭调度任务
            scheduler.shutdown();
        } catch (SchedulerException | InterruptedException e) {
            e.printStackTrace();
        }

在这里插入图片描述
一般都会创建两个触发器去执行一个任务详情,一个自动触发,一个手动触发

触发器类型

CalendarIntervalTriggerImpl.class
CronTriggerImpl.class
DailyTimeIntervalTriggerImpl.class
SimpleTriggerImpl.class

CronTrigger

 Trigger trigger1 = TriggerBuilder.newTrigger()
                    .withIdentity("trigger1","group1")
                    .startNow()
                    //设置调度策略
                    .withSchedule(
                            CronScheduleBuilder.cronSchedule("14/15 * * * * ? *")
                            )
                    .build();

cron表达式

在这里插入图片描述

, :代表允许多个值 (1,20 * * * * ?*) 每分钟第1秒 第20秒的时候执行
- :代表区间执行 (1-20 * * * * ?*)每分钟第1秒至第20秒区间执行
* :代表每单位执行一次 (* * * * * ?*)每秒执行一次
/ :代表每多长时间执行一次 (0/15 * * * * ?*)在0秒的时候执行并且每15秒一次 (3/15 * * * * ?*)在第3秒的时候执行并且每15秒一次
? :只在日与星期中使用,日期确定,则星期必须?,相反亦是
L : 
	最后一个(* * * L * ?*)每个月的最后一天的每分每秒 (* * * ?8 3L *):代表8月的最后一个星期二
	(* * * L-3 8 * *) 8月的倒数第三天
W :日位置: 15w表示距离15号最近的工作日    1w 如果1是周六,则1最近的工作日只能为未来日期,不能是上个月的工作日    
LW:组合表示月最后一个工作日
# : 6#3表示第三个星期五

获取参数

1、将key-value放入jobDataMap中

 			 //任务详情
            JobDetail job = JobBuilder.newJob(HelloJob.class)
                    //usingJobData往 jobDataMap中添加参数
                    .usingJobData("job-key1", "job-value1")
                    .withIdentity("job1", "group1")
                    .build();
            Trigger trigger1 = TriggerBuilder.newTrigger()
                    //usingJobData往 jobDataMap中添加参数
                    .usingJobData("trigger-key1", "trigger-value1")
                    .withIdentity("trigger1", "group1")
                    .startNow()
                    //设置调度策略
                    .withSchedule(
                            SimpleScheduleBuilder
                                    .simpleSchedule()
                                    .withIntervalInSeconds(3)
                                    .repeatForever()
                    )
                    .build();

2、获取在执行的任务类中获取JobDataMap中的key-value

public class HelloJob implements Job {
	
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        //获取jobDetail中的JobDataMap中的参数
        JobDetail jobDetail = context.getJobDetail();
        System.out.println(jobDetail.getJobDataMap().get("job-key1"));
        //获取trigger中的JobDataMap中的参数
        Trigger trigger = context.getTrigger();
        System.out.println(trigger.getJobDataMap().get("trigger-key1"));
        //context获取的是合并后的jobDataMap,如果jobDetail与trigger中map的key有冲突,则trigger中的值覆盖jobDetail中的值
        JobDataMap jobDataMap = context.getMergedJobDataMap();
        System.out.println(jobDataMap.get("both-key"));

        System.out.println("Hello.Execute:" + DFUtil.Date(new Date())+":"+context.getTrigger().getKey().getName());
    }
}

如果有set方法的成员变量的名称与map中的key相同,则启动定时任务,会自动将map中的value值注入到成员变量中

public class HelloJob implements Job {
    private String bothKey;

    public void setBothKey(String bothKey) {
        this.bothKey = bothKey;
    }

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
       
        //如果有set方法的成员变量的名称与map中的key相同,则启动定时任务,会自动将map中的value值注入到成员变量中
        System.out.println(bothKey);
        System.out.println("Hello.Execute:" + DFUtil.Date(new Date())+":"+context.getTrigger().getKey().getName());
    }
}

quartz.properties


org.quartz.scheduler.instanceName: DefaultQuartzScheduler
org.quartz.scheduler.rmi.export: false
org.quartz.scheduler.rmi.proxy: false
org.quartz.scheduler.wrapJobExecutionInUserTransaction: false

org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount: 10
org.quartz.threadPool.threadPriority: 5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true

org.quartz.jobStore.misfireThreshold: 60000
org.quartz.jobStore.class: org.quartz.simpl.RAMJobStore

如果需要配置properties,则只需要新建quartz.properties文件,修改配置即可生效

可通过api测试配置文件是否生效

//创建调度程序实体类
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
        scheduler.start();
        System.out.println(scheduler.getSchedulerName());
        System.out.println(scheduler.getMetaData().getThreadPoolSize());

小知识点:

如果想 scheduler.scheduleJob(trigger)只传参trigger,需要将job设置为 storeDurably(),并且将job加入到scheduler

 //任务详情
            JobDetail job = JobBuilder.newJob(HelloJob.class)
                    //usingJobData往 jobDataMap中添加参数
                    .withIdentity("job", "group1")
                    .storeDurably()
                    .build();
            Trigger trigger = TriggerBuilder.newTrigger()
                    //usingJobData往 jobDataMap中添加参数
                    .withIdentity("trigger", "group1")
                    .startNow()
                    .forJob(job)
                    //设置调度策略
                    .withSchedule(
                            SimpleScheduleBuilder
                                    .simpleSchedule()
                                    .withIntervalInSeconds(3)
                                    .repeatForever()
                    )
                    .build();
            //调度任务
            scheduler.addJob(job,false);
            scheduler.scheduleJob(trigger);

spring集成Quartz

使用Quartz

1、初始化 Scheduler 对象

@Component
public class InitJob {
	//将Scheduler 注入容器,可在spring项目中任意位置获取
    @Autowired
    private Scheduler scheduler;

    @PostConstruct
    public void initJob() throws SchedulerException {

        JobDetail jobDetail = JobBuilder.newJob(SpringBootJob.class)
                .withIdentity("job")
                .build();

        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("trigger")
                .startNow()
                .withSchedule(SimpleScheduleBuilder
                        .simpleSchedule()
                        .withIntervalInSeconds(3)
                        .repeatForever())
                .build();

        scheduler.scheduleJob(jobDetail, trigger);
    }
}

2、执行任务继承 QuartzJobBean 类,此类是spring实现quartz中的job接口的实现类

public class SpringBootJob extends QuartzJobBean {
    @Override
    protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println("我是SpringBoot"+sdf.format(new Date()));
    }
}

3、启动springBootApplication即可

持久化

quartz默认是使用内存存储定时任务数据的,如果宕机或者长时间运行多个job,可能会造成内存溢出;项目中都会将quartz进行持久化,将数据存到数据库中的quartz表中

springboot持久化

第一种:quartz表与业务表在一个数据库中

配置datasource参数,quartz参数

spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://192.168.168.132:3306/db01?useUnicode=true&characterEncoding=utf-8
    username: root
    password: root
    type: com.alibaba.druid.pool.DruidDataSource

  quartz: #springboot集成quartz的数据库持久化
    jdbc:
      initialize-schema: always #每次重启总是重新渐离quartz的表
    job-store-type: jdbc #设置quartz数据持久化到jdbc数据库中
    properties: #此属性下可以配置quartz其他未被springboot设置的属性
      org.quartz.threadPool.threadCount: 10

启动之后在数据库中会自动新建quartz的几张表

在这里插入图片描述
第二种:quartz与业务数据分开配置数据库

只需要在配置中配置一个DataSource的bean对象,并在对象上加上注解@QuartzDataSource

@Configuration
public class BeanConfig {
    @Bean
    @QuartzDataSource
    public DataSource getDataSource(){
        MysqlDataSource dataSource = new MysqlDataSource();
        dataSource.setUser("root");
        dataSource.setPassword("root");
        dataSource.setURL("jdbc:mysql://192.168.168.132:3306/db02?useUnicode=true&characterEncoding=utf-8");
        return dataSource;
    }
}

yml配置不变

spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://192.168.168.132:3306/db01?useUnicode=true&characterEncoding=utf-8
    username: root
    password: root
    type: com.alibaba.druid.pool.DruidDataSource

  quartz: #springboot集成quartz的数据库持久化
    jdbc:
      initialize-schema: always #每次重启总是重新渐离quartz的表
    job-store-type: jdbc #设置quartz数据持久化到jdbc数据库中
    properties: #此属性下可以配置quartz其他未被springboot设置的属性
      org.quartz.threadPool.threadCount: 10

启动之后,quartz会在第二个数据库中生成quartz的表

集群

quartz集群的两种方式:

1、只让集群中的一台去跑定时任务,其他机器性能浪费
2、集群中的每台服务器都会去执行定时任务,但是一个任务只会分配到集群中的一台服务器中执行

只要几台服务器连接的都是同样的quartz数据库,则这几台机器执行定时任务都是从数据库中拿数据,默认轮询执行任务

例如:a、b、c三台服务器,定时任务每一秒打印一次数据库名称,启动三台服务器;
a服务器在第一秒打印执行任务,数据持久化到数据库中;
b服务器从数据库中获取数据,领取第2秒时的任务,b在第2秒打印,数据持久化到数据库;
c服务器同样 在数据库中获取到数据,领取第3秒的任务,在第3秒的时候打印;
在a后台出现的就是1秒打印 4秒打印 7秒打印
在b后台出现的就是2秒打印 5秒打印 8秒打印
在c后台出现的就是3秒打印 6秒打印 9秒打印

一台主机模拟两个服务器运行
1、新建两个yml
在这里插入图片描述
2、配置yml 集群属性

spring:
  quartz:
    properties:
      org.quartz.scheduler.instanceName: OrderService  #集群名称
      org.quartz.scheduler.instanceId: Order-1   #集群中的标识
      org.quartz.jobStore.isClustered: true  #开启集群模式

3、配置启动环境
在这里插入图片描述
4、启动1、2、服务器实例,后台打印显示是集群模式

在这里插入图片描述
如果是集群模式的话,初始化任务的时候可能会报错

解决方案:
1、使用Controller去初始化任务到数据库中推荐

@Component
public class InitClusterJob {
    @Autowired
    private Scheduler scheduler;
    @Autowired
    private QuartzMapper mapper;

    @PostConstruct
    public void initJob() throws SchedulerException {
        /**
         * 任务初始化,如果是集群的情况,可能会造成任务初始化的时候出现重复任务报错
         *提供一种思路:将这段代码放到controller中去执行,发送请求调用controller将任务初始化到数据库中init_202008Controller()
         * 每次增加定时任务,都可以将新增的定时任务,写道一个新的controller中去进行初始化
         */
        startSpringJob("job_1","trigger_1");
        startSpringJob("job_2","trigger_2");
        startSpringJob("job_3","trigger_3");

    }
    private void startSpringJob(String jobName,String triggerName)throws  SchedulerException{
        JobDetail jobDetail = JobBuilder.newJob(SpringBootJob.class)
                .withIdentity(jobName)
                .build();

        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity(triggerName)
                .startNow()
                .withSchedule(SimpleScheduleBuilder
                        .simpleSchedule()
                        .withIntervalInSeconds(3)
                        .repeatForever())
                .build();
        scheduler.scheduleJob(jobDetail, trigger);
    }
}

2、使用 scheduler.scheduleJob(jobDetail,set,true) 方法 不推荐

		//参数Set集合中放Triger,true表示当数据库中的任务与当前任务名有冲突时,覆盖掉数据库里的任务
		Set<Trigger> set =new HashSet<>();
        set.add(trigger);
        scheduler.scheduleJob(jobDetail,set,true);

场景:如果有20台订单业务服务器,一共10000个定时任务,为了分担quartz数据库的压力,将quartz分成两个数据库,每10台订单服务器组成一个集群,即:集群1 集群2
在这里插入图片描述
设计思路:
1、配置两个集群的配置文件

spring:
  quartz:
    properties:
      org.quartz.scheduler.instanceName: OrderService-1  #集群名称
      org.quartz.scheduler.instanceId: Order-1   #集群中的标识
      org.quartz.jobStore.isClustered: true  #开启集群模式
spring:
  quartz:
    properties:
      org.quartz.scheduler.instanceName: OrderService-2  #集群名称
      org.quartz.scheduler.instanceId: Order-1   #集群中的标识
      org.quartz.jobStore.isClustered: true  #开启集群模式

2、在代码中获取yml中集群名称,与任务要放入的集群名称相匹配,如果一致则加载任务

@Component
public class InitClusterJob {
    @Autowired
    private Scheduler scheduler;

    @Value("${spring.quartz.properties.org.quartz.scheduler.instanceName}")
    private String instanceName;

    @PostConstruct
    public void initJob() throws SchedulerException, ParseException {
        //OrderService-1 要哪个集群去执行
        startSpringJob("job_1", "trigger_1","OrderService-1");
        startSpringJob("job_2", "trigger_2","OrderService-2");
        startSpringJob("job_3", "trigger_3","OrderService-3");

    }

    private void startSpringJob(String jobName, String triggerName, String partition) throws SchedulerException {
        //判断当前服务器属于哪个集群,根据集群判断是否加载任务;
        if (!instanceName.equals(partition)) {
            //如果当前集群名称与任务要求执行集群的名称一致,则加载任务
            JobDetail jobDetail = JobBuilder.newJob(SpringBootJob.class)
                    .withIdentity(jobName)
                    .build();

            Trigger trigger = TriggerBuilder.newTrigger()
                    .withIdentity(triggerName)
                    .startNow()
                    .withSchedule(SimpleScheduleBuilder
                            .simpleSchedule()
                            .withIntervalInSeconds(3)
                            .repeatForever())
                    .build();
            scheduler.scheduleJob(jobDetail, trigger);
        }
    }
}

分布式

就是多个独立的业务组件,链接一个quartz数据库,没什么特殊配置,每个组件设置自己的集群名称即可

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值