前些日子有业务需要定时任务,经过了解选择了quartz
quartz是一个用java实现的开源调度任务框架,有这么几个好处
1.配置方便,支持多任务
2.业务-定时可控,灵活配置,随时更改
3.支持分布式集群
下面是核心元素的关系
在quartz中,Scheduler调度线程主要有两个:regular Scheduler Thread(执行常规调度)和Misfire Scheduler Thread(执行错失的任务)。其中Regular Thread 轮询Trigger,如果有将要触发的Trigger,则从任务线程池中获取一个空闲线程,然后执行与改Trigger关联的job;Misfire Thraed则是扫描所有的trigger,查看是否有错失的,如果有的话,根据一定的策略进行处理。
先举个例子做定时任务配置
引用maven
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>4.3.7.RELEASE</version>
</dependency>
先建一个job.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd"
default-autowire="byName">
<description>job配置</description>
<!--采用xml配置可读性比较强 -->
<!--清空当月报表然后重新生成当月报表 -->
<bean name="test" class="cn.xxx.schedule.web.job.Test" />
<bean id="testJob" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
<property name="jobClass">
<value>cn.xxx.schedule.web.util.MyDetailQuartzJobBean</value>
</property>
<property name="jobDataAsMap">
<map>
<entry key="targetObject" value="test" />
<entry key="targetMethod" value="run" />
</map>
</property>
</bean>
<bean id="testTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<property name="jobDetail">
<ref bean="testJob" />
</property>
<property name="cronExpression">
<value>0 * * * * ?</value>
</property>
</bean>
<!-- Scheduler集合 工厂bean-->
<bean id="mapScheduler"
class="org.springframework.scheduling.quartz.SchedulerFactoryBean"
autowire="no" lazy-init="false">
<property name="triggers">
<list>
<ref local="testTrigger" />
</list>
</property>
<property name="applicationContextSchedulerContextKey" value="applicationContext" />
<property name="configLocation" value="classpath:quartz.properties" />
<property name="startupDelay" value="10" /><!-- 延迟加载10秒,即启动后10秒再执行 -->
</bean>
</beans>
首先解决JobDetailFactoryBean的问题,在不修改Spring源码的情况下,可以避免使用这个类,直接调用JobDetail。但是使用JobDetail实现,需要自己实现MothodInvoking的逻辑,可以使用JobDetail的jobClass和JobDataAsMap属性来自定义一个Factory(Manager)来实现同样的目的。例如,本示例中新建了一个MyDetailQuartzJobBean来实现这个功能。
import java.lang.reflect.Method;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.scheduling.quartz.QuartzJobBean;
public class MyDetailQuartzJobBean extends QuartzJobBean {
private static final Logger logger = LoggerFactory.getLogger(MyDetailQuartzJobBean.class);
private String targetObject;
private String targetMethod;
private ApplicationContext ctx;
@Override
protected void executeInternal(JobExecutionContext context)
throws JobExecutionException {
try {
logger.info("execute [" + targetObject + "] at once>>>>>>");
Object otargetObject = ctx.getBean(targetObject);
Method m = null;
try {
m = otargetObject.getClass().getMethod(targetMethod, new Class[] {JobExecutionContext.class});
m.invoke(otargetObject, new Object[] {context});
} catch (SecurityException e) {
logger.error(e.toString());
} catch (NoSuchMethodException e) {
logger.error(e.toString());
}
} catch (Exception e) {
throw new JobExecutionException(e);
}
}
public void setApplicationContext(ApplicationContext applicationContext) {
this.ctx = applicationContext;
}
public void setTargetObject(String targetObject) {
this.targetObject = targetObject;
}
public void setTargetMethod(String targetMethod) {
this.targetMethod = targetMethod;
}
}
JobDetailFactoryBean是job工厂,指定要执行的job类和方法名
Quartz调度一次任务,会干如下的事:
JobClass jobClass=JobDetail.getJobClass()
Job jobInstance=jobClass.newInstance()。所以Job实现类,必须有一个public的无参构建方法。
jobInstance.execute(JobExecutionContext context)。JobExecutionContext是Job运行的上下文,可以获得Trigger、Scheduler、JobDetail的信息
CronTriggerFactoryBean:基于时间刻度(可以设置具体时间,具体设置时间规则下面会说)
jobDetail:job的详细配置,依赖job工厂
cronExpression:表达式
lazy-init='false'那么容器启动就会执行调度程序
triggers:依赖要执行的任务名
通过applicationContextSchedulerContextKey属性配置spring上下文
configLocation:本地配置如下给出:
#表示如果某个任务到达执行时间,而此时线程池中没有可用线程时,任务等待的最大时间,如果等待时间超过下面配置的值(毫秒),本次就不在执行,而等待下一次执行时间的到来,可根据任务量和负责程度来调整
org.quartz.jobStore.misfireThreshold=60000
#实现集群时,任务的存储实现方式,org.quartz.impl.jdbcjobstore.JobStoreTX表示数据库存储,无需修改
org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
#quartz存储任务相关数据的表的前缀,无需修改
org.quartz.jobStore.tablePrefix=QRTZ_
#连接数据库数据源名称,与下面配置中org.quartz.dataSource.myDS的myDS一致即可,可以无需修改
org.quartz.jobStore.dataSource=myDS
#是否启用集群,启用,改为true,注意:启用集群后,必须配置下面的数据源,否则quartz调度器会初始化失败
org.quartz.jobStore.isClustered=true
#集群中服务器相互检测间隔,每台服务器都会按照下面配置的时间间隔往服务器中更新自己的状态,如果某台服务器超过以下时间没有checkin,调度器就会认为该台服务器已经down掉,不会再分配任务给该台服务器
org.quartz.jobStore.clusterCheckinInterval=10000
#==============================================================
#Non-Managed Configure Datasource
#==============================================================
#配置连接数据库的实现类,可以参照IAM数据库配置文件中的配置
org.quartz.dataSource.myDS.driver=com.mysql.jdbc.Driver
#配置连接数据库连接,可以参照IAM数据库配置文件中的配置
org.quartz.dataSource.myDS.URL=jdbc:mysql://xxx:3306/quartz
#配置连接数据库用户名
org.quartz.dataSource.myDS.user=aa
#配置连接数据库密码
org.quartz.dataSource.myDS.password=bbbbb
#配置连接数据库连接池大小,一般为上面配置的线程池的2倍
artz.dataSource.myDS.maxConnections=10
下面是指定执行业务的任务,有什么话就来这里说吧
@Slf4j
public class Test{
public void run(JobExecutionContext context){
log.info("开始执行定时任务");
log.info("完成定时任务!");
}
}
下面是表结构
表名 | 描述 |
QRTZ_CALENDARS | 以 Blob 类型存储 Quartz 的 Calendar 信息 |
QRTZ_CRON_TRIGGERS | 存储 Cron Trigger,包括 Cron 表达式和时区信息 |
QRTZ_FIRED_TRIGGERS | 存储与已触发的 Trigger 相关的状态信息,以及相联 Job 的执行信息 |
QRTZ_PAUSED_TRIGGER_GRPS | 存储已暂停的 Trigger 组的信息 |
QRTZ_SCHEDULER_STATE | 存储少量的有关 Scheduler 的状态信息,和别的 Scheduler 实例(假如是用于一个集群中) |
QRTZ_LOCKS | 存储程序的非观锁的信息(假如使用了悲观锁) |
QRTZ_JOB_DETAILS | 存储每一个已配置的 Job 的详细信息 |
QRTZ_JOB_LISTENERS | 存储有关已配置的 JobListener 的信息 |
QRTZ_SIMPLE_TRIGGERS | 存储简单的 Trigger,包括重复次数,间隔,以及已触的次数 |
QRTZ_BLOG_TRIGGERS | Trigger 作为 Blob 类型存储(用于 Quartz 用户用 JDBC 创建他们自己定制的 Trigger 类型,JobStore 并不知道如何存储实例的时候) |
QRTZ_TRIGGER_LISTENERS | 存储已配置的 TriggerListener 的信息 |
QRTZ_TRIGGERS | 存储已配置的 Trigger 的信息 |
可以在qrtz_cron_triggers这个表定时每天执行时间,比如说这个
CRON_EXPRESSION这个字段,是按秒分时天月的顺序设置时间的,TRIGGER_NAME对应对应的任务
第一个含义:每天下午6点执行,第二个含义每天晚上两点半执行
qrtz_triggers这个表是详细对应关系,如果要改动下次执行时间,可以更改NEXT_FIRE_TIME