Quartz作业调度框架

2019-01-02 21:24:00     

简介

你如果想要在每次作业完成时发送一个电子邮件或者进行一次数据备份时,可以用quartz

Quartz是一个完全由java编写的开源作业调度框架。不要让作业调度这个术语吓着你。尽管Quartz框架整合了许多额外功能, 但就其简易形式看,你会发现它易用得简直让人受不了!

SimpleTrigger

quartz的两个相关jar包:quartz-2.2.3.jar   quartz-jobs-2.2.3.jar

每隔指定时间则触发一次。根据代码理解几个概念:触发器 Trigger: 什么时候工作;任务 Job:做什么工作;调度器 Scheduler: 搭配 Trigger和Job

package top.bounds.quartz;

import static org.quartz.JobBuilder.newJob;
import static org.quartz.SimpleScheduleBuilder.simpleSchedule;
import static org.quartz.TriggerBuilder.newTrigger;

import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.Trigger;
import org.quartz.impl.StdSchedulerFactory;

public class TestQuartz {
	public static void main(String[] args) throws Exception {
		// 创建调度器
		Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

		// 触发器 Trigger
		Trigger trigger = newTrigger().withIdentity("trigger1", "group1") // 定义名称和所属的租
				.startNow()
				.withSchedule(simpleSchedule().withIntervalInSeconds(3) // 每隔3秒执行一次
						.withRepeatCount(5)) // 总共执行6次(第一次执行不计数)
				.build();

		// 任务job
		JobDetail mailJob = newJob(MailJob.class) // 指定干活的类MailJob
				.withIdentity("mailjob1", "mailgroup") // 定义任务名称和分组
				.usingJobData("qqmail", "1455973223@qq.com") // 定义属性
				.build();

		//用JobDataMap 修改qqmail
		mailJob.getJobDataMap().put("qqmail", "bounds@qq.com");

		// 调度器 Scheduler 加入这个job
		scheduler.scheduleJob(mailJob, trigger);

		// 启动
		scheduler.start();

		// 等待20秒,让前面的任务都执行完了之后,再关闭调度器
		Thread.sleep(20000);
		scheduler.shutdown(true);
	}
}
  • mailgroup就是分组的意思。比如一个系统有3个job 是备份数据库的,有4个job 是发邮件的,那么对他们进行分组,可以方便管理,类似于一次性停止所有发邮件的这样的操作.
  • import static 这种写法叫做静态导入,指的是导入某个类的静态方法, 这样就可以直接使用了

MailJob实现了Job 接口,提供 execute()方法,干具体的活儿

package top.bounds.quartz;

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

import org.quartz.Job;
import org.quartz.JobDetail;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

public class MailJob implements Job {

	public void execute(JobExecutionContext context) throws JobExecutionException {
		JobDetail detail = context.getJobDetail();
		String email = detail.getJobDataMap().getString("qqmail");
		SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
		String now = sdf.format(new Date());
		System.out.printf("给邮件地址 %s 发出了一封定时邮件, 当前时间是: %s%n", email, now);
	}
}

结合控制台查看效果,每隔3秒钟执行一次execute()方法

CronTrigger

0/3 * * * * ? 这个Cron 表达式就表示每隔3秒执行一次。这里有个Cron表达式生成工具,可以参考一下: http://cron.qqe2.com/

每到指定时间则触发一次。Cron 是Linux下的一个定时器,功能很强大,但是表达式更为复杂CronTrigger 就是用 Cron 表达式来安排触发时间和次数的。

// 任务job
JobDetail mailJob = newJob(MailJob.class)// 指定干活的类MailJob
		.withIdentity("mailJob", "mailGroup")// 定义任务名称和分组
		.build();
// 触发器 Trigger
CronTrigger trigger = newTrigger()
		.withIdentity("trigger1", "group1")// 定义名称和所属的租
		.withSchedule(cronSchedule("0/3 * * * * ?"))// 每隔3秒执行一次
		.build();

JobListener监听

使用JobListener的方式强制使用松耦合有利于设计上做到更好。

在触发器 Trigger与调度器 Scheduler之间新增JobListener监听

// 增加JobListener监听
MailJobListener mailJobListener = new MailJobListener();
KeyMatcher<JobKey> keyMatcher = KeyMatcher.keyEquals(mailJob.getKey());
scheduler.getListenerManager().addJobListener(mailJobListener, keyMatcher);

创建MailJobListener类,结合控制台查看打印效果

package top.bounds.quartz;

import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobListener;

public class MailJobListener implements JobListener {

	@Override
	public String getName() {
		return "listener of qqmail job";
	}

	@Override
	public void jobExecutionVetoed(JobExecutionContext context) {
		System.out.println("取消执行:\t "+context.getJobDetail().getKey());
	}

	@Override
	public void jobToBeExecuted(JobExecutionContext context) {
		System.out.println("准备执行:\t "+context.getJobDetail().getKey());
	}

	@Override
	public void jobWasExecuted(JobExecutionContext context, JobExecutionException arg1) {
		System.out.println("执行结束:\t "+context.getJobDetail().getKey());
		System.out.println();
	}

}

Job并发

默认的情况下,无论上一次任务是否结束或者完成,只要规定的时间到了,那么下一次就开始

有时候会做长时间的任务,比如数据库备份,这个时候就希望上一次备份成功结束之后,才开始下一次备份,即便是规定时间到了,也不能开始,因为这样很有可能造成 数据库被锁死 (几个线程同时备份数据库,引发无法预计的混乱)。那么在这种情况下,给数据库备份任务增加一个注解就好了(@DisallowConcurrentExecution

package top.bounds.quartz;

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

import org.quartz.DisallowConcurrentExecution;
import org.quartz.Job;
import org.quartz.JobDetail;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

@DisallowConcurrentExecution
public class MailJob implements Job {

	public void execute(JobExecutionContext context) throws JobExecutionException {
		JobDetail detail = context.getJobDetail();
		String email = detail.getJobDataMap().getString("qqmail");
		SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
		String now = sdf.format(new Date());
		System.out.printf("给邮件地址 %s 发出了一封定时邮件, 当前时间是: %s%n", email, now);
	}
}

Job异常

任务里发生异常是很常见的。 异常处理办法通常是两种:

1. 当异常发生,那么就通知所有管理这个 Job 的调度,停止运行它

package top.bounds.quartz;

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

import org.quartz.DisallowConcurrentExecution;
import org.quartz.Job;
import org.quartz.JobDetail;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

@DisallowConcurrentExecution
public class MailJob implements Job {
	
	static int i = 0;
	public void execute(JobExecutionContext context) throws JobExecutionException {
        try {
            //故意发生异常
            System.out.println(100/i);
            JobDetail detail = context.getJobDetail();
    		String email = detail.getJobDataMap().getString("qqmail");
    		SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
    		String now = sdf.format(new Date());
    		System.out.printf("给邮件地址 %s 发出了一封定时邮件, 当前时间是: %s%n", email, now);
             
        } catch (Exception e) {
            System.out.println("发生了异常,取消这个Job 对应的所有调度");
            JobExecutionException je =new JobExecutionException(e);
            je.setUnscheduleAllTriggers(true);
            throw je;
        }
	}
}

2. 当异常发生,修改一下参数,马上重新运行

System.out.println("发生了异常,修改一下参数,立即重新执行");
i = 1;
JobExecutionException je =new JobExecutionException(e);
je.setRefireImmediately(true);

总结

在你的Job接口实现类里面,添加一些逻辑到execute()方法。一旦你配置好Job实现类并设定好调度时间表,Quartz将密切注意剩余时间。当调度程序确定该是通知你的作业的时候,Quartz框架将调用你Job实现类(作业类)上的execute()方法并允许做它该做的事情。无需报告任何东西给调度器或调用任何特定的东西。仅仅执行任务和结束任务即可。如果配置你的作业在随后再次被调用,Quartz框架将在恰当的时间再次调用它。

ps:本篇博客源码下载链接:https://pan.baidu.com/s/1AMNeoIJG20BVKsZCVPoaZA密码:byjt
Redis集成SSM框架

jedis是redis的java客户端,spring将redis连接池作为一个bean配置。redis连接池分为两种,一种是“redis.clients.jedis.ShardedJedisPool”,这是基于hash算法的一种分布式集群redis客户端连接池。另一种是“redis.clients.jedis.JedisPool”,这是单机环境适用的redis连接池。 Redis集成SSM框架实际上就是spring集成redis,这篇博客是基于《个人档案系统》 的改进版。jar包通过maven管理,加入单机版的redis

Quartz集群

这所谓的Quartz集群,是指在 基于数据库存储 Quartz调度信息 的基础上, 有多个一模一样的 Quartz 应用在运行。当某一个Quartz 应用重启或者发生问题的时候, 其他的Quartz 应用会 借助 数据库这个桥梁探知到它不行了,从而接手把该进行的Job调度工作进行下去。以这种方式保证任务调度的高可用性,即在发生异常重启等情况下,调度信息依然连贯性地进行下去,就好像 Quartz 应用从来没有中断过似的。

 发表评论