Quartz任务调度框架初探
Quartz任务调度框架初探什么是Quartz?Quartz 是一个完全由 Java 编写的开源作业调度框架,为在 Java 应用程序中进行作业调度提供了简单却强大的机制。Quartz 可以与 J2EE 与 J2SE 应用程序相结合也可以单独使用。Quartz 允许程序开发人员根据时间的间隔来调度作业。Quartz 实现了作业和触发器的多对多的关系,还能把多个作业与不同的触...
Quartz任务调度框架初探
什么是Quartz?
Quartz 是一个完全由 Java 编写的开源作业调度框架,为在 Java 应用程序中进行作业调度提供了简单却强大的机制。
Quartz 可以与 J2EE 与 J2SE 应用程序相结合也可以单独使用。
Quartz 允许程序开发人员根据时间的间隔来调度作业。
Quartz 实现了作业和触发器的多对多的关系,还能把多个作业与不同的触发器关联。
Quartz特点
- 强大的调度功能,例如支持丰富多样的调度方法,可以满足各种常规及特殊需求;
- 灵活的应用方式,例如支持任务和调度的多种组合方式,支持调度数据的多种存储方式;
- 分布式和集群能力,提供任务持久化以及分布部署任务调度;
定时任务调度框架对比
定时任务框架 | Cron表达式 | 固定间隔执行 | 固定频率执行 | 任务持久化 |
---|---|---|---|---|
JDK TimerTask | 不支持 | 支持 | 支持 | 不支持 |
Spring Schedule | 支持 | 支持 | 支持 | 不支持 |
Quartz | 支持 | 支持 | 支持 | 支持 |
关于任务持久化,当应用程序停止运行时,所有调度信息不被丢失,当你重新启动时,调度信息还存在,这就是持久化任务。个人理解是对任务执行日志等相关信息进行持久化存储。任务持久化不一定需要通过框架进行操作,只是Quartz集成度较高。
Quartz分布式集群配置(了解)
Quartz使用持久的JobStore才能完成Quartz集群配置,关于JobStore是基于JDBC的,需要对任务调度Scheduler信息进行持久化。
由此Quartz自带的11张表就是做此事的。
关于这11张表:实例化采用数据库存储,基于数据库引擎及 High-Available 的策略自动协调每个节点。
表下载地址:http://www.quartz-scheduler.org/downloads/
解压缩之后:quartz-2.2.3\docs\dbTables\ 根据数据库类型选择SQL脚本文件即可
需求简述
- 管理数据表中的定时任务记录(CURD)
- 执行、停止定时任务
在这里我们首先考虑的是需求2:定时任务的执行和停止
依赖导入
数据库驱动:业务数据库为SqlServer
<dependency>
<groupId>com.microsoft.sqlserver</groupId>
<artifactId>sqljdbc4</artifactId>
<version>4.0</version>
<!--<scope>test</scope>-->
</dependency>
Quartz:定时任务框架依赖
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz-jobs</artifactId>
<version>2.3.0</version>
</dependency>
context-support:注意,该依赖必须导入,Spring提供对schedule的支持
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
</dependency>
开始前需要知道的几点(以下内容来源网络)
-
Job:是一个接口,只有一个方法void execute(JobExecutionContext context),开发者实现该接口定义运行任务,JobExecutionContext类提供了调度上下文的各种信息。Job运行时的信息保存在JobDataMap实例中;
-
JobDetail:Quartz在每次执行Job时,都重新创建一个Job实例,所以它不直接接受一个Job的实例,相反它接收一个Job实现类,以便运行时通过newInstance()的反射机制实例化Job。因此需要通过一个类来描述Job的实现类及其它相关的静态信息,如Job名字、描述、关联监听器等信息,JobDetail承担了这一角色。
-
Trigger:是一个类,描述触发Job执行的时间触发规则。主要有SimpleTrigger和CronTrigger这两个子类。当仅需触发一次或者以固定时间间隔周期执行,SimpleTrigger是最适合的选择;而CronTrigger则可以通过Cron表达式定义出各种复杂时间规则的调度方案:如每早晨9:00执行,周一、周三、周五下午5:00执行等;
-
Scheduler:代表一个Quartz的独立运行容器,Trigger和JobDetail可以注册到Scheduler中,两者在Scheduler中拥有各自的组及名称,组及名称是Scheduler查找定位容器中某一对象的依据,Trigger的组及名称必须唯一,JobDetail的组和名称也必须唯一(但可以和Trigger的组和名称相同,因为它们是不同类型的)。Scheduler定义了多个接口方法,允许外部通过组及名称访问和控制容器中Trigger和JobDetail。
核心成员组织图:
JobDetail是任务的定义,而Job是任务的执行逻辑。在JobDetail里会引用一个Job Class定义。
关于SpringBoot与Quartz整合
目前项目中采用的是SpringBoot 1.0 版本,并未集成Quartz。在SpringBoot 2.0版本中,Quartz已经被集成进来了。
由于底层框架采用SpringBoot,因此XML的配置方式被舍弃,可以通过配置类的方式进行配置。
QuartzConfigration.java:通过SchedulerFactoryBean,生成Schedule。
@Configuration
public class QuartzConfigration {
@Autowired
private JobFactory jobFactory;
@Bean
public SchedulerFactoryBean schedulerFactoryBean() {
SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
schedulerFactoryBean.setJobFactory(jobFactory);
// 用于quartz集群,QuartzScheduler 启动时更新己存在的Job
schedulerFactoryBean.setOverwriteExistingJobs(true);
schedulerFactoryBean.setStartupDelay(1);
return schedulerFactoryBean;
}
@Bean
public Scheduler scheduler() {
return schedulerFactoryBean().getScheduler();
}
}
AutowireCapableBeanFactory,通过看到的博客得知这个类会限制继承Job类的任务类中进行依赖注入。
这个并没有实测,文章 https://blog.csdn.net/qq_28483283/article/details/80623417 的作者构造了如下类进行该问题的解决:
@Component
public class JobFactory extends AdaptableJobFactory {
@Autowired
private AutowireCapableBeanFactory capableBeanFactory;
@Override
protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
// 调用父类的方法
Object jobInstance = super.createJobInstance(bundle);
// 进行注入
capableBeanFactory.autowireBean(jobInstance);
return jobInstance;
}
}
具体实施:DynamicScheduledTask + TaskManager
DynamicScheduledTask.java:动态定时任务
/**
* description: 动态定时任务
* author:jiangyanfei
* date:2018/11/6
* time:17:34
*/
public class DynamicScheduledTask implements Job {
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");
private static final Logger LOGGER = LoggerFactory.getLogger(DynamicScheduledTask.class);
// 上述继承Job类的任务类依赖注入问题指的就是这里,配置上述类后,可以使用@Autowired注解进行依赖注入
@Override
@DataSource(name = DSEnum.DATA_SOURCE_OPS)
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
// 通过Job上下文对象进行JobDetail、Trigger获取相关信息,下面是获取了Trigger中的参数URL
String url = (String) jobExecutionContext.getTrigger().getJobDataMap().get("url");
// 定时任务请求业务逻辑
LOGGER.info("当前时间:" + dateFormat.format(new Date()) + " 任务信息:" +
jobExecutionContext.getJobDetail().getKey().getName() + " URL:" + url);
}
}
TaskManager:任务管理器,负责任务的执行、停止逻辑,以及任务调度器Schedule的组装。
/**
* description: 任务管理器
* author:jiangyanfei
* date:2018/11/7
* time:15:26
*/
@Component
public class TaskManager {
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");
private static final Logger LOGGER = LoggerFactory.getLogger(TaskManager.class);
// 这里需要说明,采用标准的定时器工厂类进行schedule的生成,作为全局对象等待被调用
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
// 1. taskConfig方法:组装Schedule
// 2. 任务执行
// 3. 任务停止
}
-
taskConfig方法:Schedule的组装,这个方法是改造过的,传入了相关的业务参数OpsScriptInfo。
private void taskConfig(Scheduler scheduler, OpsScriptInfo opsScriptInfo) throws SchedulerException { // 后续业务请求的URL测试拼装,结合传递过来的OpsScriptInfo对象 String url = "/" + opsScriptInfo.getId() + opsScriptInfo.getPath() + "/" + opsScriptInfo.getFileName(); // Scheduler组件1: JobDetail // 构建新任务,指定动态定时任务类:DynamicScheduledTask JobDetail jobDetail = JobBuilder.newJob(DynamicScheduledTask.class) .withIdentity("Timing-task" + opsScriptInfo.getId(), "TimingTask") .build(); // Scheduler组件2: Trigger(SimpleTrigger、CronTrigger) CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity("Trigger-task-trigger" + opsScriptInfo.getId(), "TimingTaskTrigger") // 传入OpsScriptInfo对象携带的Cron表达式 .withSchedule(CronScheduleBuilder.cronSchedule(opsScriptInfo.getJobCron())) .usingJobData("url", url) // 在触发器中携带参数传递 .build(); // 将JobDetail、CronTrigger进行Scheduler进行组装 scheduler.scheduleJob(jobDetail, cronTrigger); }
-
任务执行:
public void startTask(OpsScriptInfo opsScriptInfo) throws SchedulerException { // 全局schedule工厂获取scheduler对象,start()方法启动,并进行任务配置taskConfig Scheduler scheduler = schedulerFactory.getScheduler(); scheduler.start(); taskConfig(scheduler, opsScriptInfo); }
-
任务停止:
public void stopTask(Long scriptId) throws SchedulerException { Scheduler scheduler = schedulerFactory.getScheduler(); // 通过传递参数任务ID,结合JobKey拼装Key,从JobKey中获取对应Id的Key JobKey key = JobKey.jobKey("Timing-task" + scriptId, "TimingTask"); // 判断该Key下,任务调度器中任务是否存在,若存在,将该Key对应任务从调度器中移除 if (scheduler.checkExists(key)) { scheduler.deleteJob(key); LOGGER.info("当前时间:" + dateFormat.format(new Date()) + " 定时任务:Timing-task" + scriptId + " 已停止"); } }
控制层中注入任务管理器,在对应的请求处理方法中进行调用即可
@Autowired
private TaskManager taskManager;
quartz.properties
#===============================================================
#Configure Main Scheduler Properties 调度器属性
#===============================================================
org.quartz.scheduler.instanceName = DefaultQuartzScheduler
org.quartz.scheduler.instanceId = AUTO
org.quartz.scheduler.rmi.export: false
org.quartz.scheduler.rmi.proxy: false
org.quartz.scheduler.wrapJobExecutionInUserTransaction: false
#===============================================================
#Configure ThreadPool 线程池属性
#===============================================================
org.quartz.threadPool.threadCount = 10
org.quartz.threadPool.threadPriority = 5
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
#===============================================================
#Configure JobStore 作业存储设置
#===============================================================
org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
#===============================================================
#Configure Plugins 插件配置
#===============================================================
org.quartz.plugin.jobInitializer.class = org.quartz.plugins.xml.JobInitializationPlugin
org.quartz.plugin.jobInitializer.overWriteExistingJobs = true
org.quartz.plugin.jobInitializer.failOnFileNotFound = true
org.quartz.plugin.jobInitializer.validating=false
主要内容为上述,quartz的默认配置文件中主要指定了线程池属性,默认的起了一个拥有10个线程的线程池。
org.quartz.threadPool.class 是要使用的ThreadPool实现的名称。Quartz附带的线程池是"org.quartz.simpl.SimpleThreadPool",并且应该能够满足几乎每个用户的需求。它有非常简单的行为,并经过很好的测试。它提供了一个固定大小的线程池,可以计划程序的生命周期。
org.quartz.threadPool.threadCount 可以是任何正整数,这是可用于并发执行作业的线程数。如果你只有几个工作每天执行几次,那么1个线程已经足够。如果你有成千上万的工作,每分钟都有很多工作,那么你可能希望一个线程数可能更多的是50或100(这很重要,取决于你的工作所执行的工作的性质,以及你的系统资源!)。
org.quartz.threadPool.threadPriority 可以是Thread.MIN_PRIORITY(即1)和Thread.MAX_PRIORITY(即10)之间的任何int。默认值为Thread.NORM_PRIORITY(5)。
请求测试:12个定时任务,Cron表达式均为:0/10 * * * * ?
可以看出,并发的定时任务中在执行时间是还是存在差异的,毫秒级的执行时间间隔,可以忽略。
线程池中的工作线程数默认为10,且线程池不关闭,并发任务循环使用线程池中的工作线程资源。
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)