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表:

  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仍然会被执行。

猜你喜欢

转载自technoboy.iteye.com/blog/1856984