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表:
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表中的数据如下:
首先,Quartz会将原REPEAT_COUNT-TIMES_TRIGGERED得到新的REPEAT_COUNT值,并记录一运行的次数(重新从0开始计算)。
重新启动JDBCJobStoreRunner后,数据又将发生变化:
TIMES_TRIGGERED重新从0开始计数,而REPEAT_COUNT在原有基础上重新调整。
继续运行JDBCJobStoreRunner,直至完成所有剩余次数,然后查看qrtz_simple_triggers表:
这时,该表中的数据已经变为空。
需要注意的是,如果使用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仍然会被执行。
分享到:
相关推荐
8.Introduction to Table Views 9.Navigation Controllers and Table Views 10.iPad Interface 11.Application Settings and User Defaults 12.Basic Data Persistence 13.Background Processing 14.Drawing with ...
Introduction to Table Views Navigation Controllers and Table Views Application Settings and User Defaults Basic Data Persistence Drawing with Quartz and OpenGL Taps, Touches, and Gestures Where ...
Exploring the iPhone SDK, the best-selling, the second edition of Apress’s highly acclaimed introduction to the iPhone and iPod touch by developers Dave Mark and Mark LaMarche. This book is the game...
Exploring the iPhone SDK, the best-selling, the second edition of Apress’s highly acclaimed introduction to the iPhone and iPod touch by developers Dave Mark and Mark LaMarche. This book is the game...
Exploring the iPhone SDK, the best-selling, the second edition of Apress’s highly acclaimed introduction to the iPhone and iPod touch by developers Dave Mark and Mark LaMarche. This book is the game...
Exploring the iPhone SDK, the best-selling, the second edition of Apress’s highly acclaimed introduction to the iPhone and iPod touch by developers Dave Mark and Mark LaMarche. This book is the game...
#### Introduction to Core Animation Core Animation is an advanced graphics and animation framework developed by Apple. It plays a crucial role in the visual effects and animations that users interact...
Introduction to Table Views Navigation Controllers and Table Views iPad Considerations Application Settings and User Defaults Basic Data Persistence Get Your App in the iCloud Grand Central Dispatch, ...
#### Introduction to Python for Finance In the realm of financial technology (FinTech), Python has emerged as a powerful tool for analyzing big financial data. The book "Python for Finance" by Yves ...
"Introduction-to-iOS-Graphics-APIs-Part-1.pdf"这份文档可能详细介绍了上述概念,并通过实例展示了如何在代码中实现。而"IOSGetStarted01_code.zip"则可能包含了一些示例项目,帮助读者实践Quartz 2D的绘图操作。 ...
1.Quartz: Quartz is an optional jar only needed if you wish to adapt existing quartz calendars. It can be downloaded from http://www.quartz-scheduler.org/. Run the examples ---------------- It's ...
- Chapter 8: Introduction to TableViews - 介绍了表格视图(TableView)的使用,这是一种在iOS中展示列表数据的常用方式。 - Chapter 9: Navigation Controllers and TableViews - 结合导航控制器和表格视图,讨论...
第八章:介绍表格视图(Introduction to Table Views) 表格视图是iOS应用中非常重要的界面元素。本章将详细介绍如何创建和管理表格视图,以及如何从数据源加载表格内容。 第九章:导航控制器和表格视图...
8. **表视图入门(Chapter 8: Introduction to Table Views)** - **章节概述**:这一章详细讲解了如何使用表视图(UITableView)来展示数据列表。 - **知识点**: - 表视图的基本结构。 - 数据源和代理协议的...
Chapter 2 AComprehensiveLiteratureReviewonNature-InspiredSoftComputingandAlgorithms:Tabular andGraphicalAnalyses...............................................................