由于本文不是一篇介绍quartz集群原理的文章,而是实例展示,所以在此不给大家详细讲解quartz集群,大家只需要知道以下几点:
1 大家都清楚quartz最基本的概念就是job,在job内调用具体service完成具体功能,quartz需要把每个job存储起来,方便调度,quartz存储job方式就分三种,我们最常用的也是quartz默认的是RAMJobStore,RAMJobStore顾名思义就是把job的相关信息存储在内存里,如果用spring配置quartz的job信息的话,所有信息是配置在xml里,当spirng context启动的时候就把xml里的job信息装入内存。这一性质就决定了一旦JVM挂掉或者容器挂掉,内存中的job信息就随之消失,无法持久化。另外两种方式是JobStoreTX和JobStoreCMT,暂时不讨论这两者的区别,使用这两种JobStore,quartz就会通过jdbc直连或者应用服务器jndi连接数据库,读取配置在数据库里的job初始化信息,并且把job通过java序列化到数据库里,这样就使得每个job信息得到了持久化,即使在jvm或者容器挂掉的情况下,也能通过数据库感知到其他job的状态和信息。
2 quartz集群各节点之间是通过同一个数据库实例(准确的说是同一个数据库实例的同一套表)来感知彼此的。
下面进入主题,简单介绍一下本例用到的一些技术。本例架构分为两部分,服务器端和客户端。服务器端部署在weblogic(version 9.2)中,并且配置了一个jndi数据库连接池,以便客户端访问。通过hessian发布一个远程service,该service接受一个从客服端传入的参数(该参数标示当前是哪个客户端在调用service);客户端是两个Java application,这两个app通过quartz做了集群,作为集群的两个note,当其中一个note crash了,另外一个可以接着执行,实现了HA. 其中hessian和quartz都是用spring做集成。
下面讲解一下主要代码,因为主要是讲解quartz集群,所以对于spring,hessian等技术就不做详细介绍。
服务器端:
1 工程目录:
2 remote-servlet.xml: 通过hessian把spirng context中service暴露给客户端
- <beans>
- <bean id="defaultHandlerMapping"
- class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping" />
- <import resource="classpath:/applicationContext.xml"/>
- <bean name="/remoteService"
- class="org.springframework.remoting.caucho.HessianServiceExporter">
- <property name="service" ref="remoteService"/>
- <property name="serviceInterface" value="org.remote.service.intf.RemoteService"/>
- </bean>
- </beans>
<beans>
<bean id="defaultHandlerMapping"
class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping" />
<import resource="classpath:/applicationContext.xml"/>
<bean name="/remoteService"
class="org.springframework.remoting.caucho.HessianServiceExporter">
<property name="service" ref="remoteService"/>
<property name="serviceInterface" value="org.remote.service.intf.RemoteService"/>
</bean>
</beans>
3 RemoteServiceImpl.java: 远程service,show方法接受一个来自客户端的参数,标示当前执行任务的客户端。
- public class RemoteServiceImpl implements RemoteService {
- private static final long serialVersionUID = -7663872071855739276L;
- public String show(String message) {
- SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
- String date = format.format(new Date());
- return date + " 当前 " + message + " 正在执行";
- }
- }
public class RemoteServiceImpl implements RemoteService {
private static final long serialVersionUID = -7663872071855739276L;
public String show(String message) {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String date = format.format(new Date());
return date + " 当前 " + message + " 正在执行";
}
}
4 webloigc jndi: 配置应用服务器jndi数据库连接池--jdbc/remoteService/defaultDS
客户端:
连个客户端工程AppNote1,AppNote2,通过spring集成hessian访问服务器service,并且通过weblogic jndi连接数据库做quartz集群。
1 applicationContext.xml: 客户端访问远程服务
- <beans xmlns="http://www.springframework.org/schema/beans"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
- <bean id="remoteService"
- class="org.springframework.remoting.caucho.HessianProxyFactoryBean">
- <property name="serviceUrl"
- value="http://192.168.1.100:7001/remote/remoteService"/>
- <property name="serviceInterface"
- value="org.remote.service.intf.RemoteService"/>
- </bean>
- </beans>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
<bean id="remoteService"
class="org.springframework.remoting.caucho.HessianProxyFactoryBean">
<property name="serviceUrl"
value="http://192.168.1.100:7001/remote/remoteService"/>
<property name="serviceInterface"
value="org.remote.service.intf.RemoteService"/>
</bean>
</beans>
2 context-scheduler.xml: 专门为quartz service写一个配置文件,初始化quartz scheduler,导入quartz.properties文件。
- <beans xmlns="http://www.springframework.org/schema/beans"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
- <bean id="scheduler"
- class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
- <property name="configLocation"
- value="classpath:quartz.properties"/>
- </bean>
- </beans>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
<bean id="scheduler"
class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="configLocation"
value="classpath:quartz.properties"/>
</bean>
</beans>
3 quartz.properties: quartz配置文件,配置quartz所有配置信息,包括集群配置。
- org.quartz.scheduler.instanceName = scheduler
- org.quartz.scheduler.instanceId = AUTO
- org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
- org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.oracle.weblogic.WebLogicOracleDelegate
- org.quartz.jobStore.dataSource = myXADS
- org.quartz.jobStore.tablePrefix = QRTZ_
- org.quartz.jobStore.isClustered = true
- org.quartz.dataSource.myXADS.jndiURL = jdbc/remoteService/defaultDS
- org.quartz.dataSource.myXADS.jndiAlwaysLookup = DB_JNDI_ALWAYS_LOOKUP
- org.quartz.dataSource.myXADS.java.naming.factory.initial = weblogic.jndi.WLInitialContextFactory
- org.quartz.dataSource.myXADS.java.naming.provider.url = t3://localhost:7001
- org.quartz.dataSource.myXADS.java.naming.security.principal = weblogic
- org.quartz.dataSource.myXADS.java.naming.security.credentials = weblogic
org.quartz.scheduler.instanceName = scheduler
org.quartz.scheduler.instanceId = AUTO
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.oracle.weblogic.WebLogicOracleDelegate
org.quartz.jobStore.dataSource = myXADS
org.quartz.jobStore.tablePrefix = QRTZ_
org.quartz.jobStore.isClustered = true
org.quartz.dataSource.myXADS.jndiURL = jdbc/remoteService/defaultDS
org.quartz.dataSource.myXADS.jndiAlwaysLookup = DB_JNDI_ALWAYS_LOOKUP
org.quartz.dataSource.myXADS.java.naming.factory.initial = weblogic.jndi.WLInitialContextFactory
org.quartz.dataSource.myXADS.java.naming.provider.url = t3://localhost:7001
org.quartz.dataSource.myXADS.java.naming.security.principal = weblogic
org.quartz.dataSource.myXADS.java.naming.security.credentials = weblogic
org.quartz.scheduler.instanceName:集群名称,属于同一个集群的不同note的应该为同一名称
org.quartz.scheduler.instanceId:集群中每一个note的标示,如果设置成AUTO,quartz会自动生成一个以当前物理机名加上当前时间的名称,并且插入数据库表中,集群中的note应该设置成AUTO
org.quartz.jobStore.class:quartz jobStroe类型,要做集群需要做用JobStoreTX或JobStoreCMT
org.quartz.jobStore.driverDelegateClass:数据库连接代理类,不同的应用服务器和数据库连接需要不同的代理类,本例使用weblogic的Jndi连接oracle数据库
org.quartz.jobStore.tablePrefix = QRTZ_: 集群所需要的数据库表的前缀,与数据库表的前缀一致
org.quartz.jobStore.isClustered = true: 当前应用是否加入集群
org.quartz.jobStore.dataSource = myXADS:数据库连接
以下是应用服务器jndi数据库连接池配置,这里通过远程调用访问webloigc中的jndi数据库连接池
org.quartz.dataSource.myXADS.jndiURL = jdbc/remoteService/defaultDS
org.quartz.dataSource.myXADS.jndiAlwaysLookup = DB_JNDI_ALWAYS_LOOKUP
org.quartz.dataSource.myXADS.java.naming.factory.initial = weblogic.jndi.WLInitialContextFactory
org.quartz.dataSource.myXADS.java.naming.provider.url = t3://localhost:7001
org.quartz.dataSource.myXADS.java.naming.security.principal = weblogic
org.quartz.dataSource.myXADS.java.naming.security.credentials = weblogic
4 Task.java: 具体的job实现类,在quartz job接口的execute方法中调用远程service,不同的note传入不同参数标示本应用.
- public class Task extends Base implements Job {
- public void execute(JobExecutionContext arg) throws JobExecutionException {
- RemoteService remote = (RemoteService) getContext().getBean(
- "remoteService");
- System.out.println(remote.show("APP NOTE 1"));
- }
- }
public class Task extends Base implements Job {
public void execute(JobExecutionContext arg) throws JobExecutionException {
RemoteService remote = (RemoteService) getContext().getBean(
"remoteService");
System.out.println(remote.show("APP NOTE 1"));
}
}
5 Client.java: 客户端调用类
- public class Client {
- public static void main(String[] args) {
- ApplicationContext context = new ClassPathXmlApplicationContext(
- "classpath:/context-scheduler.xml");
- Scheduler scheduler = (Scheduler) context.getBean("scheduler");
- try {
- scheduler.start();
- } catch (SchedulerException e) {
- e.printStackTrace();
- }
- }
- }
public class Client {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext(
"classpath:/context-scheduler.xml");
Scheduler scheduler = (Scheduler) context.getBean("scheduler");
try {
scheduler.start();
} catch (SchedulerException e) {
e.printStackTrace();
}
}
}
数据库:
要实现quartz集群,必须要在数据库中建立相应的表,针对不同的数据库quartz官方提供了不同脚本,这些脚本就在quartz的压缩包里的,本例用的是quartz-1.6.6.zip,解压后在/docs/dbTables目录下有相应的脚本,本例用的tables_oracle.sql,在这个脚本中总共有12张表,下面介绍一下基本的也是最重要的5张表。
1 qrtz_locks: 存储quartz权限信息,tables_oracle.sql里有相应的dml初始化
2 qrtz_job_details: 保存job详细信息,该表需要用户根据实际情况初始化
job_name:集群中job的名字,该名字用户自己可以随意定制,无强行要求
job_group:集群中job的所属组的名字,该名字用户自己随意定制,无强行要求
job_class_name:集群中个note job实现类的完全包名,quartz就是根据这个路径到classpath找到该job类
is_durable:是否持久化,把该属性设置为1,quartz会把job持久化到数据库中
job_data:一个blob字段,存放持久化job对象
3 qrtz_triggers: 保存trigger信息
trigger_name: trigger的名字,该名字用户自己可以随意定制,无强行要求
trigger_group:trigger所属组的名字,该名字用户自己随意定制,无强行要求
job_name: qrtz_job_details表job_name的外键
job_group: qrtz_job_details表job_group的外键
trigger_state:当前trigger状态,设置为ACQUIRED,如果设置为WAITING,则job不会触发
trigger_cron:触发器类型,使用cron表达式
4 qrtz_cron_triggers:存储cron表达式表
trigger_name: qrtz_triggers表trigger_name的外键
trigger_group: qrtz_triggers表trigger_group的外键
cron_expression:cron表达式
5 qrtz_scheduler_state:存储集群中note实例信息,quartz会定时读取该表的信息判断集群中每个实例的当前状态
instance_name:之前配置文件中org.quartz.scheduler.instanceId配置的名字,就会写入该字段,如果设置为AUTO,quartz会根据物理机名和当前时间产生一个名字
last_checkin_time:上次检查时间
checkin_interval:检查间隔时间
一切准备就需以后,就开始我们的测试,首先启动webloigc,发布远程服务和jndi数据库连接池。
非集群:
首先,测试没有集群的情况,把集群中每个note的quartz.properties文件的isClustered属性设置为false.
先启动AppNote1,启动信息如下,启动成功,Scheduler scheduler_$_NON_CLUSTERED started表面应用没有加入集群,系统每隔10秒调用一次远程service,打印相关信息。
2010-04-05 21:37:00 当前 APP NOTE 1 正在执行
2010-04-05 21:37:10 当前 APP NOTE 1 正在执行
2010-04-05 21:37:20 当前 APP NOTE 1 正在执行
接着启动AppNote2,启动信息和之前一样,表明AppNote2也是没有加入集群,不过AppNote2并不会执行,因为quartz只要用集群的方式配置,不管Note是否加入集群,同一个job在同一时间只能有由一个Note执行.
接下来我们把AppNote1停掉,因为没有加入集群,所以AppNote2应该不会有任何反应,不会接过AppNote1正在执行的任务继续执行。结果也如我们想的一样,AppNote2没有任何反应。
我们看qrtz_scheduler_state表,没有任何集群实例信息被保存。
集群:
下面我们来测试加入集群的情况,把集群中每个note的quartz.properties文件的isClustered属性改为为true,然后启动AppNote1和AppNote2,启动信息中产生了一个集群实例kenny1270476046453
2010-04-05 22:00:50 当前 APP NOTE 1 正在执行
2010-04-05 22:01:00 当前 APP NOTE 1 正在执行
2010-04-05 22:01:10 当前 APP NOTE 1 正在执行
再来看qrtz_scheduler_state表,两个集群实例已经被保存下来,并且时刻每个7500毫秒检测一次。
这次我们再次停掉AppNote1,再次看AppNote2,奇迹发生了,等待几秒AppNote2就会接着AppNote1的任务执行,至此,集群配置成功。
以上就是quartz集群配置,附件是三个工程的压缩包。终于写完了!累啊