Introduction to Quartz(2)

7 JobStore
Quartz 为所有类型的 Job 存储提供了一个接口。这个接口位于 org.quartz.spi 包中,叫做 JobStore。JobStore 用于对 Job、Trigger、Calendar、Listener和 Scheduler 状态进行存储。Quartz使用者通常不用直接访问JobStore接口的方法,它们在运行时被Scheduler访问。如果按照存储类型分类,Quartz提供了两种类型的JobStore,分别是非持久性JobStore(non-persistent JobStore)和持久性JobStore(persistent JobStore)。

7.1 Non-persistent JobStore
Quartz的默认JobStore是RAMJobStore,它使用内存作为存储介质,因此它是非持久性的。RAMJobStore的配置十分简单,只需要在配置文件中添加如下一行即可:
org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
RAMJobStore 是优点是速度快。但是由于RAMJobStore是非持久性的,因此Job 的易失性对于RAMJobStore不起作用,也就是说Job不会在应用关闭时被持久化保存。

7.2 Persistent JobStore
Quartz 所带的所有的持久性的 JobStore 都扩展自 org.quartz.impl.jdbcjobstore.JobStoreSupport 类。JobStoreSupport是个抽象类,实现了 JobStore 接口。这个类的一个更好的名字本应该是 JDBCJobStoreSupport,因为这个类专门是为基于 JDBC 存储方案而设置的。Quartz 提供了两种不同类型的具体化的 JobStore,每一个设计为针对特定的数据库环境和配置:
org.quartz.impl.jdbcjobstore.JobStoreTX。JobStoreTX 类设计为用于独立环境中,而不是与容器的事务集成。这并不意味着你不能在一个容器中使用 JobStoreTX,只不过它不是设计成接受容器的事务管理。
org.quartz.impl.jdbcjobstore.JobStoreCMT。JobStoreCMT 类设计成接受容器的事务管理。它的名字来源于容器管理的事务(Container Managed Transactions (CMT))。
由于持久性的JobStore是基于 JDBC 的,因此需要在数据库中创建相关的表。你能在 <quartz_home>/docs/dbTables 目录下找到那些用于创建表的 SQL 脚本。Quartz 需要创建 如下12 张表:
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。存储SimpleTriggers,包括重复次数、间隔以及已触发的次数。
QRTZ_BLOB_TRIGGERS。Trigger 作为 Blob 类型存储(用于 Quartz 用户用 JDBC 创建他们自己定制的 Trigger 类型,JobStore 并不知道如何存储实例的时候)。
QRTZ_TRIGGER_LISTENERS。存储已配置的 TriggerListener 的信息。QRTZ_TRIGGERS 存储已配置的 Trigger 的信息。
以上所有的表都是以默前缀 QRTZ_ 开始。可以通过在 quartz.properties 文件中提供一个指定的前缀来改变它。
JDBC API 依赖于专属于某个数据库平台的 JDBC 驱动,同样的,Quartz 依赖于某个 DriverDelegate 来与给定数据库进行通信。顾名思义, Scheduler 通过 JobStore 对数据库的调用是委托给一个预配置的 DriverDelegate 实例。这个代理承担起所有与 JDBC driver 也就是数据库的通信。所有的 DriverDelegate 类都继承自 org.quartz.impl.jdbcjobstore.StdDriverDelegate 类。StdDriverDelegte 只有所有代理可用的,平台无关性的基本功能。然而,在不同的数据库平台间还是存在太多的差异,因此可能需要为某个平台创建特定的代理。
当使用持久性 JobStore 时,Quartz 需要一个数据源。Java中所有的数据源要实现 java.sql.Datasource 接口。Quartz 默认使用DBCP,也可以通过 JNDI 查找应用服务器中定义的 DataSource。
以下是一个JDBC持久化job的例子:

org.quartz.scheduler.instanceName = DefaultQuartzScheduler
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 = 10
org.quartz.threadPool.threadPriority = 5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true

org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.tablePrefix=QRTZ_
org.quartz.jobStore.dataSource=qzDS

org.quartz.dataSource.qzDS.driver=com.mysql.jdbc.Driver
org.quartz.dataSource.qzDS.URL=jdbc:mysql://localhost:3306/quartz
org.quartz.dataSource.qzDS.user=root
org.quartz.dataSource.qzDS.password=root

首先在类路劲下创建如上quartz.properties文件,假设使用MySQL innodb引擎,找到Quartz完整发布包下的docs/dbTables,执行tables_mysql_innodb.sql。然后运行SimpleTriggerRunner:

public class SimpleTriggerRunner {

public static void main(String[] args) {
try {
JobDetail jobDetail = new JobDetail("job1_1", "jgroup1", SimpleJob.class);
SimpleTrigger simpleTrigger = new SimpleTrigger("trigger1_1", "tgroup1");
simpleTrigger.setStartTime(new Date());
simpleTrigger.setRepeatInterval(2000);
simpleTrigger.setRepeatCount(100);

SchedulerFactory factory = new StdSchedulerFactory();
Scheduler scheduler = factory.getScheduler();
scheduler.scheduleJob(jobDetail, simpleTrigger);
scheduler.start();
} catch (Exception e) {
e.printStackTrace();
}
}
}

public class SimpleJob implements Job{

@Override
public void execute(JobExecutionContext context)
throws JobExecutionException {
System.out.println("go!!!");
}
}

SimpleTriggerRunner执行一段时间后,停止,模拟程序退出。由于配置了JobStoreTX,trigger以及jobdetail的运行信息都会保存在数据库中。我们可以到数据库中查看qrtz_simple_triggers表:
[img]http://dl.iteye.com/upload/attachment/0083/8547/962f61fe-8e18-36b9-a6eb-4bc561b86a1b.jpg[/img]
REPEAT_COUNT表示需要运行的总次数,TIMES_TRIGGERED表示已运行的次数。
我们可以通过JDBCJobStoreRunner,根据记录在数据库中的任务数据,恢复任务的调度:

import org.quartz.Scheduler;
import org.quartz.SchedulerFactory;
import org.quartz.SimpleTrigger;
import org.quartz.Trigger;
import org.quartz.impl.StdSchedulerFactory;

public class JDBCJobStoreRunner {

public static void main(String[] args) {
try {
SchedulerFactory factory = new StdSchedulerFactory();
Scheduler scheduler = factory.getScheduler();
String[] triggerGroupNames = scheduler.getTriggerGroupNames();
for(int i = 0; i < triggerGroupNames.length; i ++){
String[] triggers = scheduler.getTriggerNames(triggerGroupNames[i]);
for(int j = 0; j < triggers.length; j++){
Trigger trigger = scheduler.getTrigger(triggers[j], triggerGroupNames[i]);
if((trigger instanceof SimpleTrigger) && (trigger.getFullName().equals("tgroup1.trigger1_1"))){
scheduler.rescheduleJob(triggers[j], triggerGroupNames[i], trigger);
}
}
}
scheduler.start();
} catch (Exception e) {
e.printStackTrace();
}
}
}

运营一段时间后退出,这时qrtz_simple_triggers表中的数据如下:
[img]http://dl.iteye.com/upload/attachment/0083/8549/3db82174-d5d3-32d0-9432-24366164e4e2.jpg[/img]
首先,Quartz会将原REPEAT_COUNT-TIMES_TRIGGERED得到新的REPEAT_COUNT值,并记录一运行的次数(重新从0开始计算)。
重新启动JDBCJobStoreRunner后,数据又将发生变化:
[img]http://dl.iteye.com/upload/attachment/0083/8551/ba2b5c79-ea07-3e4c-95d9-698fde7a3e27.jpg[/img]
TIMES_TRIGGERED重新从0开始计数,而REPEAT_COUNT在原有基础上重新调整。
继续运行JDBCJobStoreRunner,直至完成所有剩余次数,然后查看qrtz_simple_triggers表:
[img]http://dl.iteye.com/upload/attachment/0083/8553/101310a8-c699-3e08-a88d-7d09eeedfd97.jpg[/img]
这时,该表中的数据已经变为空。
需要注意的是,如果使用JDBC保存任务调度数据,运行SimpleTriggerRunner,然后退出,当再次运营时,会抛异常:

org.quartz.ObjectAlreadyExistsException: Unable to store Job with name: 'job1_1' and group: 'jgroup1', because one already exists with this identification.

这是因为每次调用scheduler.scheduleJob方法时,Quartz都会将JobDetail和Trigger的信息保存到数据库中,如果在数据库中有同名的JobDetail和Trigger,异常就产生了。
最后提醒大家,如果按照以上步骤操作,报数据库字段异常时,可能是由于Quartz的发布包中tables_mysql_innodb.sql脚本少字段或建表有问题,可以考虑使用附件的脚本。当初笔者在调试时,发现tables_mysql_innodb脚本中,数据不完整,进行了部分修改。另外,笔者建议,在做这块的时候,将log4j打开,方便调试。

8 Features
8.1 Remoting
借助于 RMI, Quartz允许在不同的JVM中部署和调度Job。Quartz的服务器端和客户端需要不同的配置,下面是服务器端的配置文件quartz_rmi_server.properties的例子:

#==============================================================
# Configure Main Scheduler Properties
#==============================================================
org.quartz.scheduler.instanceName = RMIScheduler
org.quartz.scheduler.instanceId = AUTO

#==============================================================
# Configure RMI Properties
#==============================================================
org.quartz.scheduler.rmi.export = true
org.quartz.scheduler.rmi.registryHost = localhost
org.quartz.scheduler.rmi.registryPort = 1099
org.quartz.scheduler.rmi.serverPort = 0
org.quartz.scheduler.rmi.createRegistry = true

#==============================================================
# Configure ThreadPool
#==============================================================
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount = 10
org.quartz.threadPool.threadPriority = 5

#==============================================================
# Configure JobStore
#==============================================================
org.quartz.jobStore.misfireThreshold = 60000
org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.useProperties=false
org.quartz.jobStore.dataSource=mysql
org.quartz.jobStore.tablePrefix=QRTZ_
org.quartz.dataSource.mysql.driver=com.mysql.jdbc.Driver
org.quartz.dataSource.mysql.URL=jdbc:mysql://localhost:3306/quartz
org.quartz.dataSource.mysql.user=root
org.quartz.dataSource.mysql.password=123456
org.quartz.dataSource.mysql.maxConnections=5

其中org.quartz.scheduler.rmi相关属性的含义如下:
org.quartz.scheduler.rmi.export。要使 Quartz 调度器作为一个可用的 RMI 对象,这个标记必须设置为 true,默认值是false。
org.quartz.scheduler.rmi.registryHost。运行 RMI 注册表所在的主机。
org.quartz.scheduler.rmi.registryPort。 RMI 注册服务监听所用的端口号(通常是1099)。
org.quartz.scheduler.rmi.createRegistry。Quartz 是否会创建 RMI 注册服务。如果你不希望 Quartz 创建注册服务就设置为 false 或 never。如果是希望 Quartz 首先尝试去使用已存在的注册服务,如果失败的话自行创建一个就设置为 true 或 as_needed。
org.quartz.scheduler.rmi.serverPort。Quartz 调度器服务所绑定的端口号,在其中监听到来的连接。默认情况下,RMI 服务会随机选择一个端口号作为调度器绑定到 RMI 注册服务的端口。
下面是Quartz服务器端程序的例子:

public class QuartzRmiServer {

public static void main(String[] args) throws Exception {
System.out.println("starting QuartzRmiServer...");

//
System.setProperty("org.quartz.properties", "quartz_rmi_server.properties");
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
scheduler.start();

//
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
while (true) {
String line = br.readLine();
if (line.equalsIgnoreCase("exit") || line.equalsIgnoreCase("quit")) {
break;
}
}
br.close();
scheduler.shutdown(true);
}
}


下面是服务器端的配置文件quartz_rmi_client.properties的例子:

#=============================================================
# Configure Main Scheduler Properties
#=============================================================
org.quartz.scheduler.instanceName = RMIScheduler
org.quartz.scheduler.instanceId = AUTO

#==============================================================
#Configure RMI Properties
#==============================================================
org.quartz.scheduler.rmi.registryHost=localhost
org.quartz.scheduler.rmi.registryPort=1099
org.quartz.scheduler.rmi.proxy= true

其中org.quartz.scheduler.rmi相关属性的含义如下:
org.quartz.scheduler.rmi.registryHost。运行 RMI 注册服务所在的主机,需要同服务器端的配置相同。
org.quartz.scheduler.rmi.registryPort。运行 RMI 注册服务所监听的端口(通常是 1099),需要同服务器端的配置相同。
org.quartz.scheduler.rmi.proxy。如果希望连接到远程服务端的调度器,这个属性必须是true。
此外,属性 org.quartz.scheduler.instanceName 在 RMI 客户端和服务端必须一致。不然,客户将无法在注册服务中查找到服务对象,会收一个客户端无法获取到远程调度器句柄的异常。
下面是Quartz客户端程序的例子:

public class QuartzRmiClient {

public static void main(String[] args) throws Exception {
System.out.println("starting QuartzRmiClient...");

//
System.setProperty("org.quartz.properties", "quartz_rmi_client.properties");
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

//
Random random = new Random();
int id = Math.abs(random.nextInt());
String name = "simpleJob" + id;
JobDetail jd = new JobDetail(name, "group1", SimpleStatefulJob.class);
jd.setVolatility(false);

//
CronTrigger trigger = new CronTrigger("cronTrigger" + id, "group1");
CronExpression cronExpression = new CronExpression("0/5 * * * * ?");
trigger.setCronExpression(cronExpression);
trigger.setVolatility(false);
scheduler.scheduleJob(jd, trigger);
}
}

首先启动服务端程序,然后运行客户端程序。由于客户端的配置文件中org.quartz.scheduler.rmi.proxy 的值是 true,因此在客户端程序启动后,从StdSchedulerFactory的到的Scheduler是服务器端Scheduler的远程代理,因此在客户端部署的Job实际上会被远程的Scheduler执行。启动客户端部署一个Job后就会退出。如果一切正常,在服务器端的控制台就能看到客户端部署的Job所打印的消息。你也可以再次启动客户端以便部署更多的Job。如果在服务器端的控制台输入exit或者quit,那么服务器端的程序会退出。由于服务器端使用了持久化的JobStore,而且客户端部署的Job和Trigger的volatility属性值是false,因此如果再次启动服务器端程序,之前部署的Job仍然会被执行。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值