`
kenny_liu
  • 浏览: 14655 次
  • 性别: Icon_minigender_1
  • 来自: 成都
文章分类
社区版块
存档分类
最新评论

quartz集群完整应用实例

阅读更多

  由于工作中几乎所有J2EE项目都是部署在weblogic上,并且两台manage server都做了cluster,所以一直没有关注过quartz的集群,前段时间一个项目正好用到quartz做定时发送邮件,一个开发人员无意中问了我一个问题:如果把定时功能与发邮件的功能分别部署到不同地方,发邮件功能作为后台service部署到weblogic中,而定时功能作为外部调用放在容器之外,这样定时功能就无法享受到weblogic的集群,那如何保证邮件任务的高可用性呢?虽然目前项目不会采用这种做法,但我对这个问题还是比较感兴趣,因为如果能把定时功能和发邮件功能分别部署到不同地方,并且如果都能用集群的方式部署,那么这样系统的可用性就会大大提高,对于邮件发送成功率要求很高,不允许出现半点差错的项目,确实是非常有用的。于是下来查阅了很多资料,终于了解到quartz本身也支持集群。 
  由于本文不是一篇介绍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>

       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 + " 正在执行";
	}
}

       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>

    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>

    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:集群名称,属于同一个集群的不同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"));
	}
}

   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();
		}
	}
}

    数据库:

    要实现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集群配置,附件是三个工程的压缩包。终于写完了!累啊

  • 大小: 18.7 KB
  • 大小: 3.3 KB
  • 大小: 10.8 KB
  • 大小: 11.7 KB
  • 大小: 2.3 KB
  • 大小: 3.7 KB
  • 大小: 5.1 KB
  • 大小: 2 KB
  • 大小: 9.2 KB
  • 大小: 8.2 KB
  • 大小: 3.9 KB
  • 大小: 11.5 KB
  • 大小: 4.9 KB
  • 大小: 13.3 KB
分享到:
评论
4 楼 1308706231 2014-10-21  
数据库有吗我用mysql+tomact能运行但不执行Job
3 楼 randy_jin 2013-06-04  
严重: ClusterManager: Error managing cluster: Failed to obtain DB connection from data source 'myXADS': java.sql.SQLException: Could not retrieve datasource via JNDI url 'jdbc/remoteService/defaultDS' weblogic.rmi.extensions.RemoteRuntimeException: Unexpected Exception
org.quartz.JobPersistenceException: Failed to obtain DB connection from data source 'myXADS': java.sql.SQLException: Could not retrieve datasource via JNDI url 'jdbc/remoteService/defaultDS' weblogic.rmi.extensions.RemoteRuntimeException: Unexpected Exception [See nested exception: java.sql.SQLException: Could not retrieve datasource via JNDI url 'jdbc/remoteService/defaultDS' weblogic.rmi.extensions.RemoteRuntimeException: Unexpected Exception]
	at org.quartz.impl.jdbcjobstore.JobStoreSupport.getConnection(JobStoreSupport.java:689)
	at org.quartz.impl.jdbcjobstore.JobStoreTX.getNonManagedTXConnection(JobStoreTX.java:72)
	at org.quartz.impl.jdbcjobstore.JobStoreSupport.doCheckin(JobStoreSupport.java:3167)
	at org.quartz.impl.jdbcjobstore.JobStoreSupport$ClusterManager.manage(JobStoreSupport.java:3798)
	at org.quartz.impl.jdbcjobstore.JobStoreSupport$ClusterManager.initialize(JobStoreSupport.java:3785)
	at org.quartz.impl.jdbcjobstore.JobStoreSupport.schedulerStarted(JobStoreSupport.java:621)
	at org.quartz.core.QuartzScheduler.start(QuartzScheduler.java:455)
	at org.quartz.impl.StdScheduler.start(StdScheduler.java:146)
	at org.springframework.scheduling.quartz.SchedulerFactoryBean.startScheduler(SchedulerFactoryBean.java:627)
	at org.springframework.scheduling.quartz.SchedulerFactoryBean.afterPropertiesSet(SchedulerFactoryBean.java:487)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1369)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1335)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:473)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory$1.run(AbstractAutowireCapableBeanFactory.java:409)
	at java.security.AccessController.doPrivileged(Native Method)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:380)
	at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:264)
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:261)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:185)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:164)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:423)
	at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:728)
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:380)
	at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:139)
	at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:83)
	at org.quartz.job.Client.main(Client.java:10)
Caused by: java.sql.SQLException: Could not retrieve datasource via JNDI url 'jdbc/remoteService/defaultDS' weblogic.rmi.extensions.RemoteRuntimeException: Unexpected Exception
	at org.quartz.utils.JNDIConnectionProvider.getConnection(JNDIConnectionProvider.java:166)
	at org.quartz.utils.DBConnectionManager.getConnection(DBConnectionManager.java:112)
	at org.quartz.impl.jdbcjobstore.JobStoreSupport.getConnection(JobStoreSupport.java:686)
	... 26 more

我按照以上代码来操作的,但是报如下错误,求原因,谢谢!
2 楼 cloudfile 2012-09-12  
谢谢分享!!
1 楼 jiminsc 2012-04-06  

相关推荐

    quartz 集群解决方法

    为了便于监控和管理Quartz集群,可以使用Web管理界面如`QuartzAdmin`或者通过API接口获取集群状态和任务信息。 9. **源码阅读**: 对于深入理解Quartz集群的工作原理,阅读源码是一个很好的途径。了解`Cluster...

    spring quartz集群配置

    在Quartz集群中,多个Quartz服务器共享同一个“作业存储”(Job Store),这个存储可以是关系数据库或者分布式的存储系统。当一个服务器触发一个作业时,其他服务器会看到这个作业已经被触发,因此不会重复执行。这...

    spring quartz 集群模式

    Quartz集群通过共享内存中的状态来实现任务的分配和执行,当一个节点失败时,其他节点能够接管未完成的任务。实现Quartz集群,需要配置多个Quartz服务器共享同一份数据库存储,用于保存Job、Trigger以及运行状态等...

    spring集成quartz集群配置

    在IT行业中,Spring框架是Java应用开发中的基石,而Quartz则是广泛...通过上述步骤,你已经掌握了Spring集成Quartz集群配置的基本概念。在实践中,根据具体需求调整配置,可以实现高效、稳定且可扩展的任务调度系统。

    Spring+Quartz集群部署案例

    最近项目中使用了spring+Quartz定时任务、但是项目最近要集群部署、多个APP下如何利用Quartz 协调处理任务。 大家可以思考一下、现在有 A、B、C三个应用同时作为集群服务器对外统一提供服务、每个应用下各有一个...

    quartz集群各种数据库建表脚本

    集群中的每个节点都是一个独立的Quartz实例,它们共享同一个作业和触发器的存储,这就是所谓的Quartz集群。 集群的关键在于保持所有节点的任务状态同步,这就需要用到数据库来存储任务信息。"quartz集群各种数据库...

    Spring+Quartz 集群

    5. **集群配置**:为了实现高可用的Quartz集群,你需要配置多个节点共享同一个数据库存储的作业和触发器信息。Quartz支持多种持久化策略,如JDBC、RAMJobStore等,选择数据库存储能保证在集群中的任务状态一致性。 ...

    spring + quartz 集群配置

    java + quartz实现定时任务,实现集群配置,在集群环境下多节点运行定时Quartz定任务,就会存在重复处理任务的现象,为解决这一问题,下面我将介绍使用 Quartz 的 TASK ( 12 张表)实例化到数据库,基于数据库自动...

    quartz 集群配置

    当需要在分布式环境中运行多个Quartz实例以实现高可用性和负载均衡时,就需要进行Quartz集群配置。 在Spring集成Quartz的过程中,主要涉及以下几个核心概念和步骤: 1. **Job与Trigger**:Job是Quartz中的任务接口...

    Quartz集群配置和示例源码

    在提供的压缩包中,可能包含了一个简单的Quartz集群应用示例,源码通常会包括以下部分: - **Job类**:实现`org.quartz.Job`接口,定义具体的任务逻辑。 ```java public class MyJob implements Job { @Override ...

    quartz+spring分布式集群调度

    3. **监控和管理**: 实现对Quartz集群的监控,如任务状态、运行时性能等,以便及时发现和解决问题。 总结,Quartz+Spring的分布式集群调度方案能够帮助开发者构建稳定且可扩展的定时任务系统。通过合理配置和实践,...

    quartz在集群环境下的最终解决方案

    本解决方案通过将 Quartz 的任务实例化至数据库、合理配置 Quartz 属性文件以及利用 Spring 框架进行重构等手段,有效地解决了集群环境下 Quartz 部署和管理的问题。不仅可以避免任务的重复执行,还能确保系统的高...

    Quartz集群+spring data

    在Quartz集群中,多个Quartz实例可以协同工作,提高任务调度的可用性和容错性。当一个节点失败时,其他节点能够接管其任务,确保业务连续性。实现Quartz集群的关键在于共享调度信息,如JobStore,它通常使用数据库来...

    quartz集群 分布式

    Quartz集群的基本思想是多个Quartz Scheduler实例分布在不同的服务器上,它们共享同一个数据库存储的任务和触发器信息。当一个Scheduler实例创建或修改了作业和触发器时,这些变更会被写入数据库,其他实例会通过...

    spring4.0.6+quartz 2.2.3 集群示例

    8. **监控和管理**: 可以使用Quartz提供的Web管理界面`Admin Console`或自定义监控工具来查看集群状态,管理和调试作业。 集群的关键在于数据一致性,每个节点需要同步作业和触发器的状态,这通常通过共享存储(如...

Global site tag (gtag.js) - Google Analytics