Quartz是一个主流的schedule job 框架。以下讲的是原生的Quartz框架,不是Spring封装的Spring Quartz。
当我们的schedule job有写数据库mysql的操作的时候,就需要配置。其实只要想job需要持久化到mysql数据库,多个BET可以同时去执行schedule job的话,就需要如下配置
- org.quartz.jobStore.class= org.quartz.impl.jdbcjobstore.JobStoreTX
Quartz默认是仅仅把job存在内存中的。这个不可能用于产品和线上,仅仅可以做Demo。
- org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
当配置的Quartz是使用数据库mysql持久化的时候,Quartz会自动在数据库里新建相关的呃表:
- SELECT * FROM qrtz_blob_triggers;
- SELECT * FROM qrtz_calendars;
- SELECT * FROM qrtz_cron_triggers;
- SELECT * FROM qrtz_fired_triggers;
- SELECT * FROM qrtz_job_details;
- SELECT * FROM qrtz_locks;
- SELECT * FROM qrtz_paused_trigger_grps;
- SELECT * FROM qrtz_scheduler_state;
- SELECT * FROM qrtz_simple_triggers;
- SELECT * FROM qrtz_simprop_triggers;
- SELECT * FROM qrtz_triggers;
以下部署使用Quartz:
这篇单单记录一下个人配置使用quartz的JDBC JobStoreTX的过程以及其中遇到的问题,这里的quartz是version2.2.1,数据库使用的MySQL。
JDBCJobStore储存是速度比较慢的,但是也不至于很坏,通过JDBCJobStore储存于数据库的方式适用于Oracle,PostgreSQL, MySQL, MS SQLServer, HSQLDB, DB2等数据库。
1) 建表
在下载的文件的docs/dbTables目录下有对应建表语句,如果没有对应于应用的就自己改动来适应。这些个表都有"QRTZ"前缀,可以作为区别于别的命名。
2) 选定事务
如果你不需要绑定其他事务处理,你可以选择quartz的事务,其通过JobStoreTX来管理,这也是常用的选择,当然如果你要和你的应用容器一起管理,那你可以使用quartz的
JobStoreCMT,quartz通过JobStoreCMT来的使用来让你的应用容器管理quartz的事务。
3) 创建数据源
一个是提供一个connection,让quartz可以连接到数据库,另一个是提供的JNDI的方式,让quartz可以从所在容器中获取到。
使用JDBC连接方式(假设你使用的是StdSchedulerFactory):
首先:
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
或者
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreCMT
这里当然选择JobStoreTX。
其次:
选定JDBC代理类,quartz里提供了StdJDBCDelegate,如果这个不能正常工作的,你可以选用其他代理类(在org.quar.impl.jdbcjobstore package或者其子包中可以找到),包括DB2v6Delegate (for DB2 version 6 and earlier), HSQLDBDelegate (for HSQLDB),MSSQLDelegate (for Microsoft SQLServer), PostgreSQLDelegate (for PostgreSQL),
WeblogicDelegate (for using JDBC drivers made by WebLogic), OracleDelegate (for using Oracle)等等。
并这样配置:
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
第三:
指定前缀
org.quartz.jobStore.tablePrefix = QRTZ_
第四:
指定数据源名称:
org.quartz.jobStore.dataSource = myDS
第五:定义ConnectionProvider的实现类
这里我找了一下,quartz提供了一个org.quartz.utils.PoolingConnectionProvider,于是,我就有了如下配置:
org.quartz.dataSource.myDS.connectionProvider.class:org.quartz.utils.PoolingConnectionProvider
第六:配置数据源属性
org.quartz.dataSource.myDS.driver: com.mysql.jdbc.Driver
org.quartz.dataSource.myDS.url: jdbc:mysql://localhost:3306/quartz?useUnicode=true&characterEncoding=utf-8
org.quartz.dataSource.myDS.user: root
org.quartz.dataSource.myDS.password: root
org.quartz.dataSource.myDS.maxConnections = 30
定义好后,来试试,果然顶用。以下是我的可运行代码:
quartz.properties (这个文件应该放在classpath所指的路径下)
- # 调度器属性 :设置调度器的实例名(instanceName) 属性 org.quartz.scheduler.instanceName 可以是任何字符串。它用来在用到多个调度器区分特定的调度器实例。多个调度器通常用在集群环境中。如果没有配置,默认值是QuartzScheduler。
- # 调度器属性 :设置调度器的实例 ID (instanceId), org.quartz.scheduler.instanceId。和 instaneName 属性一样,instanceId 属性也允许任何字符串。这个值必须是在所有调度器实例中是唯一的,尤其是在一个集群当中。假如你想 Quartz 帮你生成这个值的话,可以设置为 AUTO。如果 Quartz 框架是运行在非集群环境中,那么自动产生的值将会是 NON_CLUSTERED。假如是在集群环境下使用 Quartz,这个值将会是主机名加上当前的日期和时间。大多情况下,设置为 AUTO 即可。
- org.quartz,scheduler.instanceId: AUTO
- org.quartz.scheduler.instanceName: DefaultQuartzScheduler
- org.quartz.scheduler.rmi.export: false
- org.quartz.scheduler.rmi.proxy: false
- org.quartz.scheduler.wrapJobExecutionInUserTransaction: false
-
- # 线程池属性:这些线程在 Quartz 中是运行在后台的。threadCount 属性控制了多少个worker线程被创建用来处理 Job。原则上是,要处理的 Job 越多,那么需要的工作者线程也就越多。threadCount 的数值至少为 1。Quartz 没有限定你设置worker线程的最大值。这项没有默认值,所以你必须为这个属性设定一个值。threadPriority 属性设置worker线程的优先级。优先级别高的线程比级别低的线程更优先得到执行。threadPriority 属性的最大值是常量java.lang.Thread.MAX_PRIORITY,等于10。最小值为常量 java.lang.Thread.MIN_PRIORITY,为1。这个属性的正常值是 Thread.NORM_PRIORITY,为5。大多情况下,把它设置为5,这也是没指定该属性的默认值。org.quartz.threadPool.class,这个值是一个实现了 org.quartz.spi.ThreadPool 接口的类的全限名称。Quartz 自带的线程池实现类是 org.quartz.smpl.SimpleThreadPool,它能够满足大多数用户的需求。这个线程池实现具备简单的行为,并经很好的测试过。它在调度器的生命周期中提供固定大小的线程池。你能根据需求创建自己的线程池实现,如果你想要一个随需可伸缩的线程池时也许需要这么做。这个属性没有默认值,你必须为其指定值。
- org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool
- org.quartz.threadPool.threadCount: 10
- org.quartz.threadPool.threadPriority: 5
- org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: false
-
- org.quartz.jobStore.misfireThreshold: 60000
-
-
- # 作业存储设置:作业存储部分的设置描述了在调度器实例的生命周期中,Job 和 Trigger 信息是如何被存储的。如果把调度器信息存储在内存中非常的快也易于配置。当调度器进程一旦被终止,所有的 Job 和 Trigger 的状态就丢失了。要使 Job 存储在内存中需通过设置 org.quartz.jobStrore.class 属性为 org.quartz.simpl.RAMJobStore。假如我们不希望在 JVM 退出之后丢失调度器的状态信息的话,我们可以使用mysql数据库来存储这些信息。这需要另一个作业存储(JobStore) 实现。
- org.quartz.jobStore.class: org.quartz.impl.jdbcjobstore.JobStoreTX
- org.quartz.jobStore.driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
- org.quartz.jobStore.tablePrefix: QRTZ_
- org.quartz.jobStore.dataSource: myDS
-
- org.quartz.dataSource.myDS.connectionProvider.class: org.quartz.examples.example17.MyPoolingconnectionProvider
- org.quartz.dataSource.myDS.driver: com.mysql.jdbc.Driver
- org.quartz.dataSource.myDS.url: jdbc:mysql://localhost:3306/quartz?useUnicode=true&characterEncoding=utf-8
- org.quartz.dataSource.myDS.user: root
- org.quartz.dataSource.myDS.password: root
- org.quartz.dataSource.myDS.maxConnections: 30
MyPoolingconnectionProvider.java
SimpleExample.java
- package org.quartz.examples.example17;
-
- import java.util.Date;
-
- import org.quartz.DateBuilder;
- import org.quartz.JobBuilder;
- import org.quartz.JobDetail;
- import org.quartz.Scheduler;
- import org.quartz.SchedulerFactory;
- import org.quartz.TriggerBuilder;
- import org.quartz.impl.StdSchedulerFactory;
- import org.quartz.impl.triggers.SimpleTriggerImpl;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
-
- public class SimpleExample
- {
- public void run()
- throws Exception
- {
- Logger log = LoggerFactory.getLogger(SimpleExample.class);
-
- log.info("------- Initializing ----------------------");
- //通过调度器工厂获取调度器,初始化工程时须指定其使用我们自己的配置文件
- SchedulerFactory sf = new StdSchedulerFactory("org/quartz/examples/example17/quartz.properties");
- Scheduler sched = sf.getScheduler();
-
- //这儿clear一下,因为使用数据库储存方式时,shutdown的时候没有清除,第二次运行会报Job is already exist
- sched.clear();
-
- log.info("------- Initialization Complete -----------");
-
- Date runTime = DateBuilder.evenMinuteDate(new Date());
-
- log.info("------- Scheduling Job -------------------");
-
- //创建任务详情
- JobDetail job = JobBuilder.newJob(HelloJob.class).withIdentity("job1", "group1").build();
- //创建触发器
- SimpleTriggerImpl trigger = (SimpleTriggerImpl)TriggerBuilder.newTrigger().withIdentity("trigger1", "group1").startAt(new Date()).build();
- trigger.setRepeatCount(5);
- trigger.setRepeatInterval(3000);
- log.info("------- Starttime = "+trigger.getStartTime()+" -----------------");
-
- //调度器、触发器、任务,三者关联
- sched.scheduleJob(job, trigger);
- log.info(job.getKey() + " will run at: " + runTime);
- //调度启动
- sched.start();
- log.info("------- Started Scheduler -----------------");
-
- log.info("------- Waiting 1 minute... -------------");
- try
- {
- Thread.sleep(60000L);
- }
- catch (Exception e)
- {
- }
-
- log.info("------- Shutting Down ---------------------");
- //调度关闭
- sched.shutdown(true);
- log.info("------- Shutdown Complete -----------------");
- }
-
- public static void main(String[] args) throws Exception
- {
- SimpleExample example = new SimpleExample();
- example.run();
- }
- }
当然,运行example之前,得先添加mysql的driver的包,然后在mysql里建立一个叫“quartz”的database,并将
docs/dbTables下的tables_mysql.sql脚本运行一下以建表。
注意:
1) org.quartz.jobStore.class: org.quartz.impl.jdbcjobstore.JobStoreTX,这个配置在这儿当然是用这个,
而org.quartz.jobStore.driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate则根据你的数据,通常使用当前这个,如果这个不适用就去quartz里org.quar.impl.jdbcjobstore包或其子包下找找,如还是未能满足需求,就自己实现一个。
2) org.quartz.jobStore.tablePrefix: QRTZ_,这个是quartz默认的,tables_mysql.sql以及一些其他操作(比如clear等)的代码都这样写了,所以就不要去修改了。
3) org.quartz.jobStore.dataSource: myDS这个的“键”必须是org.quartz.jobStore.dataSource,而“值”随你取,但必须有。
4) dataSource的属性配置,如下几个(可以自己添加其他的对应修改provider):
- org.quartz.dataSource.myDS.connectionProvider.class: org.quartz.examples.example17.MyPoolingconnectionProvider
- org.quartz.dataSource.myDS.driver: com.mysql.jdbc.Driver
- org.quartz.dataSource.myDS.url: jdbc:mysql://localhost:3306/quartz?useUnicode=true&characterEncoding=utf-8
- org.quartz.dataSource.myDS.user: root
- org.quartz.dataSource.myDS.password: root
- org.quartz.dataSource.myDS.maxConnections: 30
它们的“键”必须是
- org.quartz.dataSource."+yourdatasourcename+"."+yourProvider#datamembername
以上是一个最简单的Quartz的例子,但是如果每个job都是用java代码写出来并执行,那就太悲哀了。我们需要可配置的job。恰好Quartz提供了这样的Plugin,让我们可以把job配置在xml中,启动quartz的时候会自动扫描这个xml文件。这就非常好用了。以下讲一下如何使用Quartz Plugin。
插件配置
在quartz.properties中加入:
org.quartz.plugin.triggHistory.class = org.quartz.plugins.history.LoggingJobHistoryPlugin
org.quartz.plugin.jobInitializer.class = org.quartz.plugins.xml.JobInitializationPlugin
org.quartz.plugin.jobInitializer.fileNames = jobs.xml
org.quartz.plugin.jobInitializer.overWriteExistingJobs = true
org.quartz.plugin.jobInitializer.failOnFileNotFound = true
org.quartz.plugin.jobInitializer.scanInterval = 10
org.quartz.plugin.jobInitializer.wrapInUserTransaction = false
在上面配置中的 quartz.properties 文件中就是 Quart 插件的配置。
一个声明式schedule job 框架的方法就是通过新加实现了 org.quartz.spi.SchedulerPlugin 接口的类。SchedulerPlugin 接口中有给调度器调用的三个方法。
要在我们的例子中声明式配置调度器信息,我们会用到一个 Quartz 自带的叫做 org.quartz.plugins.xml.JobInitializationPlugin 的插件。
默认时,这个插件会在 classpath 中搜索名为quartz_jobs.xml 的文件并从中加载 Job 和 Trigger 信息。在下下面中讨论 quartz_jobs.xml 文件,这是我们所参考的非正式的 Job 定义文件。JobInitializationPlugin 找寻 quartz_jobs.xml 来获得声明的 Job 信息。如果想改变这个文件名,你需要修改 quartz.properties 来告诉插件去加载那个文件。我们添加了属性 org.quartz.plugin.jobInitializer.fileName 并设置该属性值为我们想要的文件名。这个文件名要对 classloader 可见,也就是说要在 classpath 下。
当 Quartz 启动后读取 quartz.properties 文件,然后初始化插件。它会传递上面配置的所有属性给插件,这时候插件也就得到通知去搜寻不同的文件。
注: 在
quartz_jobs.xml
中 <start-time> ,其中T隔开日期和时间,格式是:
<start-time>2008-09-03T14:43:00</start-time>
<job> 元素描述了一个要注册到调度器上的 Job,相当于我们在前面章节中使用 scheduleJob() 方法那样。你所看到的<job-detail> 和 <trigger> 这两个元素就是我们在代码中以编程式传递给方法 schedulerJob() 的参数。前面本质上是与这里一样的,只是现在用的是一种较流行声明的方式。<trigger>元素也是非常直观的:它使用前面同样的属性,但更简单的建立一个 SimpleTrigger。因此仅仅是一种不同的(可论证的且更好的)方式做了上一篇代码 中同样的事情。显然,你也可以支持多个 Job。
以下是quartz_jobs.xml的例子:
<job-scheduling-data
xmlns="http://www.quartz-scheduler.org/xml/JobSchedulingData"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.quartz-scheduler.org/xml/JobSchedulingData http://www.quartz-scheduler.org/xml/job_scheduling_data_1_8.xsd"
version="1.8">
<pre-processing-commands>
<delete-triggers-in-group>*</delete-triggers-in-group>
</pre-processing-commands>
<processing-directives>
<overwrite-existing-data>true</overwrite-existing-data>
<ignore-duplicates>false</ignore-duplicates>
</processing-directives>
<schedule>
<job>
<name>CacheClearJob</name>
<group>Cache</group>
<description>Clear Cache Job</description>
<job-class>com.webinfor.timer.TimerCleaner</job-class>
<volatility>false</volatility>
<durability>true</durability>
<recover>false</recover>
</job>
<trigger>
<simple>
<name>CacheClearJob</name>
<group>Cache</group>
<job-name>CacheClearJob</job-name>
<job-group>Cache</job-group>
<repeat-count>-1</repeat-count>
<repeat-interval>300000</repeat-interval>
</simple>
</trigger>
<job>
<name>RefrushDemo</name>
<group>Demo</group>
<description>RefrushDemoJob</description>
<job-class>com.webinfor.demo.job.RefrushDataJob</job-class>
<volatility>false</volatility>
<durability>true</durability>
<recover>false</recover>
</job>
<trigger>
<cron>
<name>RefrushDemo</name>
<group>Demo</group>
<job-name>RefrushDemo</job-name>
<job-group>Demo</job-group>
<misfire-instruction>MISFIRE_INSTRUCTION_FIRE_ONCE_NOW</misfire-instruction>
<cron-expression>0 0 1 * * ?</cron-expression>
</cron>
</trigger>
</schedule>
</job-scheduling-data>
TimerCleaner.java :
public class TimerCleaner implements Job {
@Override
public void execute(JobExecutionContext arg0) throws JobExecutionException {
System.out.println("timer clear job start.");
ReportLogger.setNews("", "TimerCleaner", "execute",
"timer clear job start.");
HourGlass.timeOut();
ReportLogger.setNews("", "TimerCleaner", "execute",
"timer clear job end.");
System.out.println("timer clear job end.");
}
}
最后启动Quartz即可,Quartz启动后会自动的去读取quartz_jobs.xml里面的任务并把job持久化到mysql数据库中。(以下启动Quartz的代码,如果在web项目中,应该在启动tomcat服务的时候就自动启动,因此需要把这段代码加到启动服务的逻辑中。如果使用Spring Quartz,有相关的spring配置会更加方便)
import java.util.Date;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.TriggerUtils;
import org.quartz.impl.StdSchedulerFactory;
public class SimpleScheduler
{
static Log logger = LogFactory.getLog(SimpleScheduler.class);
public static void main(String[] args)
{
SimpleScheduler simple = new SimpleScheduler();
try
{
// Create a Scheduler and schedule the Job
Scheduler scheduler = simple.createScheduler();
// Jobs can be scheduled after Scheduler is running
scheduler.start();
logger.info("Scheduler started at " + new Date());
}
catch (SchedulerException ex)
{
logger.error(ex);
}
}
public Scheduler createScheduler() throws SchedulerException
{//创建调度器
return StdSchedulerFactory.getDefaultScheduler();
}
}
附录: cronExpression
- 字段 允许值 允许的特殊字符
- 秒 0-59 , - * /
- 分 0-59 , - * /
- 小时 0-23 , - * /
- 日期 1-31 , - * ? / L W C
- 月份 1-12 或者 JAN-DEC , - * /
- 星期 1-7 或者 SUN-SAT , - * ? / L C #
- 年(可选) 留空, 1970-2099 , - * /
“*”字符被用来指定所有的值。如:”*“在分钟的字段域里表示“每分钟”。
“?”字符只在日期域和星期域中使用。它被用来指定“非明确的值”。当你需要通过在这两个域中的一个来指定一些东西的时候,它是有用的。
月份中的日期和星期中的日期这两个元素时互斥的一起应该通过设置一个问号(?)来表明不想设置那个字段
“-”字符被用来指定一个范围。如:“10-12”在小时域意味着“10点、11点、12点”。
“,”字符被用来指定另外的值。如:“MON,WED,FRI”在星期域里表示”星期一、星期三、星期五”.