Quartz定时任务框架

Quartz框架的核心对象

Scheduler – 核心调度器
Job – 任务
JobDetail – 任务描述
Trigger -- 触发器

在这里插入图片描述

在这里插入图片描述
一个Job可以被多个Trigger调度,但一个只能对应一个Job。
JobDetail和Trigger均有name和group来唯一确定,name和group都可以不指定,若不指定group,使用默认的group(名为DEFAULT的group)。
Triiger主要有SimpleTrigger和CronTrigger

.withSchedule(CronScheduleBuilder.cronSchedule("0/5 * * * * ?")) .build();

在这里插入图片描述
w:只能出现在日期字段里,是对前导日期的修饰,表示离该日期最近的工作日。例如15W表示离该月15号最近的工作日,如果该月15号是星期六,则匹配14号星期五;如果15日是星期日,则匹配16号星期一;如果15号是星期二,那结果就是15号星期二。但必须注意关联的匹配日期不能够跨月.。
传递参数
JobDetail和Triiger都可以通过JobDataMap向Job传递参数。
JobDataMap实现了JDK的Map接口,可以以Key-Value的形式存储数据。
Job执行execute()方法的时候,JobExecutionContext可以获取到JobExecutionContext中的信息:

JobDetail jobDetail = JobBuilder.newJob(PrintWordsJob.class)                        .usingJobData("jobDetail1", "这个Job用来测试的")
                  .withIdentity("job1", "group1").build();

 Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "triggerGroup1")
      .usingJobData("trigger1", "这是jobDetail1的trigger")
      .startNow()//立即生效
      .withSchedule(SimpleScheduleBuilder.simpleSchedule()
      .withIntervalInSeconds(1)//每隔1s执行一次
      .repeatForever()).build();//一直执行
@Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        System.out.println(jobExecutionContext.getJobDetail().getJobDataMap().get("jobDetail1"));
        System.out.println(jobExecutionContext.getTrigger().getJobDataMap().get("trigger1"));
        String printTime = new SimpleDateFormat("yy-MM-dd HH-mm-ss").format(new Date());
        System.out.println("PrintWordsJob start at:" + printTime + ", prints: Hello Job-" + new Random().nextInt(100));
    }

当jobExecutionContext.getMergedJobDataMap()时,并且JobDetail和Triiger有同名的键,则以Trigger为主。
另一种接收参数的方式 :
在Job中声明属性和其set方法,当jobDataMap里有同名的key时,会自动调用set方法注入。
与springboot整合:
导入依赖spring-boot-starter-quartz,会自动生成Scheduler对象。
写Job的话只需要继承QuartzJobBean,并实现executeInternal()方法。

public class SimpleSpringJob extends QuartzJobBean {

    private MonitoringService monitoringService;//依赖注入
    private String name;//from job data property
    public void setMonitoringService(MonitoringService monitoringService) {
        this.monitoringService = monitoringService;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Override
    protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        System.out.println("woda");
        System.out.println(monitoringService);
    }
}

依赖注入需要用@Autowired,set注入无效
另一种方式:

@Configuration
public class JobConfig {
    @Bean
    public JobDetail springJobDetail345() {
        return JobBuilder.newJob(HelloJob.class)
                .withIdentity("job1", "group1")
                //.storeDurably()
                .build();
    }
    @Bean
    public Trigger springTrigger123() {
        return newTrigger()
                .withIdentity("trigger1","group1")
                .forJob("job1", "group1")
                .startNow()
                .withSchedule(simpleSchedule().withIntervalInSeconds(5)
                                .repeatForever())
                .build();
    }
}

持久化
默认使用内存中的job,但是如果程序中有可用的datasource bean,并且配置了spring.quartz.job-store-type=jdbc,则可以实现quartz的持久化。
集成springboot之后quartz.properties文件失效。
原生quartz共有三种配置数据源的方式。
spring.quartz.jdbc.initialize=always:每次启动删掉表,重新创建。
持久化相关表:
qrtz_blob_triggers、qrtz_calendars、qrtz_cron_triggers、qrtz_fired_triggers、qrtz_job_details、qrtz_locks、qrtz_paused_trigger_grps、qrtz_scheduler_state、qrtz_simple_triggers、qrtz_simprop_triggers、qrtz_triggers
集成了springboot之后,spring关于quartz的配置不能完全覆盖quartz的原生配置,可以在application.yml中这样配置原生属性:

spring.quartz.properties.org.quartz.threadpool.threadcount=3

springboot中为quartz单独配置数据源:
1.application.yml通过原生属性配置
2.声明一个datasource bean,并用@QuartzDataSource注释@Bean方法
集群环境:
基本解决思路为:
1.只让集群中的一台服务器去跑定时任务
2.集群中的每台服务器都会执行定时任务,但是每一次触发只会分配到集群的一台机器上(quartz支持的方式)
quartz关于集群的处理的前提是,必须持久化,这样才能完成故障转移和负载均衡,实现高可用性和可扩展性。对于繁忙的调度程序,负载均衡机制几乎是随机的,但对于少量触发器的
调度程序,则倾向于使用同一节点。
启用集群:
集群中的每个实例都应配置org.quartz.jobStore.isClustered=true,且各个实例应该有相同的instanceName;有不同的instanceId,可以设置该值为AUTO。
集群情况下,任务只需持久化一次
任务的并发
同一个任务是可以被并发调度的,比如设置了每隔一秒执行,但是任务的耗时远超一秒;此时任务仍然会每隔一秒执行一次,因为每一次任务的执行,创建的都是新的job实例。
@DisallowConcurrentExecution :禁止并发执行多个相同定义的JobDetail, 这个注解是加在Job类上的, 但意思并不是不能同时执行多个Job, 而是不能并发执行同一个Job Definition(由JobDetail定义), 但是可以同时执行多个不同的JobDetail, 举例说明,我们有一个Job类,叫做SayHelloJob, 并在这个Job上加了这个注解, 然后在这个Job上定义了很多个JobDetail, 如sayHelloToJoeJobDetail, sayHelloToMikeJobDetail, 那么当scheduler启动时, 不会并发执行多个sayHelloToJoeJobDetail或者sayHelloToMikeJobDetail, 但可以同时执行sayHelloToJoeJobDetail跟sayHelloToMikeJobDetail。
默认情况下在任务中对dataMap的修改,并不会起作用
PersistJobDataAfterExecution加在Job类上可动态修改dataMap的值,该注解被建议与@DisallowConcurrentExecution一起使用
misfire
意思是失火,在quartz中的含义是到了任务触发时间,任务没有触发:原因有可能是:
1.使用了@DisallowConcurrentExection注解,而且任务的执行时间大于任务间隔;
2.线程池满了,没有资源执行任务;
3.机器宕机
异常处理
当任务出现异常,不会影响后面任务的调度,但是如果知道异常的原因,并且可以提供正确执行的方案,能否在不影响原有调度规则的前提下,重新触发一次任务?还有一个场景,就是任务出现异常以后,能不能让其不再调度?
问题一的解决方式一:
在这里插入图片描述
运行结果是执行失败了两次之后,才执行成功,也有可能执行任意次之后才成功,因为当第一个任务彻底运行完了之后,后面的任务才会拿到jobDataMap的新值。
解决方式二:
在这里插入图片描述
注意事项:这种方式用的是同一个context,在哪里放的数据就要从哪儿拿,在mergeDataMap拿不到更新后的值,因为context只初始化了一次。
如何在有异常时终止调度?

jobExecutionContext.getScheduler().unscheduleJobs(
                    jobExecutionContext.getScheduler().getTriggersOfJob(
                            jobExecutionContext.getJobDetail().getKey()
                    ).stream().map(Trigger::getKey).collect(Collectors.toList())
            );

或者:

JobExecutionException jobExecutionException = new JobExecutionException(e);
        jobExecutionException.setUnscheduleAllTriggers(true);
        throw jobExecutionException;

监听器
功能:审计、功能解耦、功能扩展。
JobListener、TriggerListener、SchedulerListener

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值