公司的定时任务越来越多,boss想把它们都放到一个服务器里管理,让我研究一下,弄一套方案出来。网上看了很多资料,相关的资料不多,读了源码,加上自己测试,总算弄出来,拿出来分享给大家。如果你想在服务器不重启的情况下,完成对定时任务的增删改操作,并且时时监测定时任务的状态,就耐心的读下去。
先看一下项目结构:
图片有点大,主要想给大家看的就是quartz.properties,我们就从这个配置文件开始说起。(记住,quartz.properties文件放在src目录下)
- quartz.properties
直接上代码:
#调度器属性
org.quartz.scheduler.instanceName:MyQuartzScheduler
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:20
org.quartz.threadPool.threadPriority:5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread:true
#最长等待时间,跟任务的misfire有关
org.quartz.jobStore.misfireThreshold:60000
#存储属性
#org.quartz.jobStore.class:org.quartz.simpl.RAMJobStore
#持久化到数据库
org.quartz.jobStore.class= org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass== org.quartz.impl.jdbcjobstore.oracle.OracleDelegate
#表名前缀
org.quartz.jobStore.tablePrefix= QRTZ_
#数据源
org.quartz.jobStore.dataSource= myDS
#数据库信息
org.quartz.dataSource.myDS.driver= oracle.jdbc.driver.OracleDriver
org.quartz.dataSource.myDS.URL= jdbc:oracle:thin:@10.7.160.82:1521:cover
org.quartz.dataSource.myDS.user= cover_uat
org.quartz.dataSource.myDS.password= cover_uat123
org.quartz.dataSource.myDS.maxConnections= 25
这段代码大家在网上都能搜到详细的解释,如果想深入了解就去看看,如果只是想使用,看看下面我说的就可以了。
这里面特别要注意的几个字段
org.quartz.jobStore.class= org.quartz.impl.jdbcjobstore.JobStoreTX的意思是将定时任务持久化到数据库,如果是这样写org.quartz.jobStore.class:org.quartz.simpl.RAMJobStore就是将定时任务存在内存中。看到RAM三个字应该也理解一点了。持久化数据库有持久化数据库的好处,存在内存中也有优劣。但是要实现增删改还是要存在数据库中。需要改动的只有数据源和数据库配置信息。
2.持久化到数据库
既然选择了存到数据库中,当然要配置一下数据库的信息。表结构在你的quartz\quartz-2.1.7\docs\dbTables包里有脚本,这里我用的Oracle数据库,所以用的是Oracle数据库的脚本。数据源就是你配置weblogic的时候的jndi名称。按照你的数据库配置好以后。配置文件就搞定了,对于quartz的使用,我前面已经介绍过一个quartzManager方法,不知道的可以去quartz的使用查看。调用里面的添加方法,你可以在你的定时任务里打印一些信息到控制台。当定时任务执行后,去你的数据库看看,如果表QRTZ_CRON_TRIGGERS,QRTZ_JOB_DETAILS,QRTZ_TRIGGERS有相关信息的话,说明你已经成功将定时任务持久化到数据库中了。
3.动态添加定时任务
此时,添加定时任务就是让quartz可以从数据库中读到相关的数据,并且找到相关的类去执行。所以,我们先要准备相关的类,跟一开始一样,编辑好后,编辑成二进制文件,放在服务器的.class 文件下面,这样第一步就完成了,服务器已经能找到这个类了。然后就是怎么样让数据库多出这些数据,一开始我有想过,手动配这三张表。试了一下,不仅麻烦,而且还不能用。后来看了源码,才知道调度器好像自己维护了一张调度表。光数据库中有还是不管用的。为了解决问题,我往数据库里又加入了一张表。
create table QRTZ_JOB
(
JOB_NAME VARCHAR2(200) not null,
JOB_GROUP VARCHAR2(200) not null,
DESCRIPTION VARCHAR2(250),
JOB_CLASS_NAME VARCHAR2(250) not null,
CRON VARCHAR2(120) not null
)
就是这样,我又开了一个定时任务,定时读取这张表,如果有数据,就根据数据启动定时任务,然后将表中数据删除。这样如果有新任务来就会被定时器读到,然后添加进去。
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
// 查找数据库中是否有新增的任务
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
String sql = "select * from qrtz_job";
String sql2 = "delete from qrtz_job";
try {
// 动态加载log4j2.xml文件
LoggerContext logContext = (LoggerContext) LogManager.getContext(false);
String path = AddNewJob.class.getResource("/log4j2.xml").getPath();
File conFile = new File(path);
logContext.setConfigLocation(conFile.toURI());
logContext.reconfigure();
conn = DBUtil.getConnection();
ps = conn.prepareStatement(sql);
rs = ps.executeQuery();
// 如果有,调用方法添加启动任务,将任务列表清空
while (rs.next()) {
System.out.println("添加新任务");
System.out.println(rs.getString("JOB_NAME") + "-" + rs.getString("CRON") + "-" + rs.getString("JOB_CLASS_NAME"));
QuartzManager.addJob(rs.getString("JOB_NAME"), rs.getString("JOB_GROUP"), rs.getString("JOB_NAME"), rs.getString("JOB_GROUP"), Class.forName(rs.getString("JOB_CLASS_NAME")),
rs.getString("CRON"));
log.info("添加新任务:" + rs.getString("JOB_NAME") + "-" + rs.getString("CRON") + "-" + rs.getString("JOB_CLASS_NAME"));
}
ps = conn.prepareStatement(sql2);
ps.executeUpdate();
} catch (Exception e) {
log.error("动态添加新任务时出错:", e);
e.printStackTrace();
} finally {
DBUtil.close(rs, ps, conn);
}
}
很简单的逻辑。到此为止,你的定时任务已经可以动态添加了。再不用为了一个定时任务重启服务器。
4.动态修改定时任务
定时修改定时任务就两步,1、改QRTZ_CRON_TRIGGERS表中的字段CRON_EXPRESSION。2、根据刚才的修改,修改表QRTZ_TRIGGERS的字段NEXT_FIRE_TIME。要求:下次执行时间-上次执行时间=CRON_EXPRESSION。多次修改,我发现要在定时任务未执行的时候修改,因为两个字段要同时修改才能生效。所以改一半的话任务一执行,另一个就还原了。所以改的时候找准定时任务两次执行的间隔修改。
5.动态删除定时任务
这个说不说大家应该都知道了,对,删除数据库关于这个定时任务的相关信息。读不到就不会执行了。
到这里,整个项目也算结束了,可是这时boss看完又提了一个需求,因为定时任务的错误很难定位,要求每一个定时任务都有一个属于自己的日志文件,这样谁的文件里有信息了,就表示谁发生了错误,再根据打印的错误修改即可。这里涉及到log4j的内容,我在另一个文章中说。文章链接:log4j动态加载