Spring整合Quartz案例使用JDBC存储方式

目录

创建数据库表

配置数据库连接池

创建工作类

工作类中调用的业务类

 工作类

配置JobDetail和Trigger,并交给Scheduler注册

测试

Quartz集群中如何工作

Quartz集群环境下配置

quartz.properties

工作类

SchedulerFactory的xml配置

JobDetail的xml配置

测试集群

quartz水平集群和垂直集群的说明


创建数据库表

JDBC存储策略依赖数据库表,数据库表的执行脚本可以在官网下载后解压后的docs/dbTables文件夹中找到。

我们需要是jdbc的执行脚本,将它在数据库中执行,就会创建存jobDetail和Trigger等信息的表,这些表的前缀名是QRTZ_

配置数据库连接池

jdbc.url=jdbc\:mysql\://localhost\:3306/test?useUnicode\=true&characterEncoding\=utf8&autoReconnect\=true
jdbc.username=root
jdbc.password=root
jdbc.driverClassName=com.mysql.jdbc.Driver
<context:property-placeholder location="classpath:jdbc.properties" />
	<!-- 扫描包 -->
	<context:component-scan base-package="cn.bing.service"></context:component-scan>
	<!-- 数据源 -->
	 <!-- 数据源定义,使用c3p0 连接池 -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
        destroy-method="close">
        <property name="driverClass" value="${jdbc.driverClassName}" />
        <property name="jdbcUrl" value="${jdbc.url}" />
        <property name="user" value="${jdbc.username}" />
        <property name="password" value="${jdbc.password}" />
        <property name="initialPoolSize" value="2" />
        <property name="minPoolSize" value="10" />
        <property name="maxPoolSize" value="20" />
        <property name="acquireIncrement" value="2" />
        <property name="maxIdleTime" value="1800" />
    </bean>

创建工作类

工作类中调用的业务类

会在工作类中通过获取spring上下文,获取bean对象调用

package cn.bing.service;

import java.text.SimpleDateFormat;
import java.util.Date;

import org.springframework.stereotype.Service;
@Service("helloService")
public class HelloService {
	public void hello(){
		System.out.println("北京时间:["+new SimpleDateFormat("yyyyMMdd HH:mm:ss").format(new Date())+"] hello world! ..... ");
	}
}

 工作类

package cn.bing.job;

import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.SchedulerException;
import org.springframework.context.ApplicationContext;
import org.springframework.scheduling.quartz.QuartzJobBean;

import cn.bing.service.HelloService;

public class HelloJob extends QuartzJobBean{
	@Override
	protected void executeInternal(JobExecutionContext context)
			throws JobExecutionException {
		//获取spring容器
		try {
			ApplicationContext ac = (ApplicationContext) context.getScheduler().getContext().get("applicationContextKey");
			HelloService service =  ac.getBean("helloService", HelloService.class);
			service.hello();
		} catch (SchedulerException e) {
			e.printStackTrace();
		}
	}
}

配置JobDetail和Trigger,并交给Scheduler注册

 <bean id="jobDetail1" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
        <property name="jobClass" value="cn.bing.job.HelloJob"></property>    
        <!-- 必须设置为true,如果为false,当没有活动的触发器与之关联时会在调度器中会删除该任务  -->  
        <property name="durability" value="true" />    
       	<!-- 当Quartz服务被终止后,再次启动或者集群中的其他机器接受任务时候会尝试恢复之前失败的任务 -->
        <property name="requestsRecovery" value="true" />        
    </bean>
    <bean id="trigger1" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
        <property name="jobDetail" ref="jobDetail1" />
        <!-- 间隔10秒执行一次 -->
        <property name="cronExpression" value="*/10 * * * * ?" />
    </bean>        
    <!-- 将触发器注册到任务上 -->
    <bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
    	<!-- 数据源 -->
    	<property name="dataSource" ref="dataSource"></property>
    	<property name="applicationContextSchedulerContextKey" value="applicationContextKey"></property>
    	<property name="triggers">
    		<list>
    			<ref bean="trigger1"/>
    		</list>
    	</property>
    </bean>

这里和前面RAM存储方式不同点,配置了dataSource属性

测试

package cn.bing.test;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class QuartzTest {
	public static void main(String[] args) {
		  ApplicationContext springContext =
				  new ClassPathXmlApplicationContext
				  (new String[]{"classpath:applicationContext.xml","classpath:quartz-config.xml"});
	}
}

运行结果

北京时间:[20180802 18:00:10] hello world! ..... 
北京时间:[20180802 18:00:20] hello world! ..... 
北京时间:[20180802 18:00:30] hello world! ..... 
北京时间:[20180802 18:00:40] hello world! ..... 
北京时间:[20180802 18:00:50] hello world! ..... 
北京时间:[20180802 18:01:00] hello world! ..... 

Quartz集群中如何工作

一个Quartz节点是一个独立的应用,却又管理其他节点。对于每个节点你必须分别启动和停止。和其他服务器集群不同,Quartz节点并不和其他节点进行通信,而是通过数据感知其他节点的存在。

Quartz集群环境下配置

主要是配置quartz.properties和工作类(并发环境下的配置) 

quartz.properties

org.quartz.scheduler.instanceName = TestScheduler1  
org.quartz.scheduler.instanceId = AUTO 
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount = 10
org.quartz.threadPool.threadPriority = 5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true
#触发器超时时间(ms),超过这个时间,按照触发器的misfire策略处理
org.quartz.jobStore.misfireThreshold = 60000
#任务的存储策略是jdbc
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
#表前缀
org.quartz.jobStore.tablePrefix = QRTZ_
org.quartz.jobStore.maxMisfiresToHandleAtATime=10
org.quartz.jobStore.isClustered = true 
org.quartz.jobStore.clusterCheckinInterval = 20000

org.quartz.scheduler.instanceName属性可为任何值,用在 JDBC JobStore 中来唯一标识实例,但是所有集群节点中必须相同。
org.quartz.scheduler.instanceId 属性为 AUTO即可,基于主机名和时间戳来产生实例 ID。
org.quartz.jobStore.class属性为 JobStoreTX,将任务持久化到数据中。因为集群中节点依赖于数据库来传播 Scheduler 实例的状态,你只能在使用 JDBC JobStore 时应用 Quartz 集群。这意味着你必须使用 JobStoreTX 或是 JobStoreCMT 作为 Job 存储;你不能在集群中使用 RAMJobStore。
org.quartz.jobStore.isClustered 属性为 true,你就告诉了 Scheduler 实例要它参与到一个集群当中。这一属性会贯穿于调度框架的始终,用于修改集群环境中操作的默认行为。
org.quartz.jobStore.clusterCheckinInterval 属性定义了Scheduler 实例检入到数据库中的频率(单位:毫秒)。Scheduler 检查是否其他的实例到了它们应当检入的时候未检入;这能指出一个失败的 Scheduler 实例,且当前 Scheduler 会以此来接管任何执行失败并可恢复的 Job。通过检入操作,Scheduler 也会更新自身的状态记录。clusterChedkinInterval 越小,Scheduler 节点检查失败的 Scheduler 实例就越频繁。默认值是 15000 (即15 秒)。

工作类

还是上面的工作类,加上注解,不允许并发执行。

package cn.bing.job;

import org.quartz.DisallowConcurrentExecution;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.PersistJobDataAfterExecution;
import org.quartz.SchedulerException;
import org.springframework.context.ApplicationContext;
import org.springframework.scheduling.quartz.QuartzJobBean;

import cn.bing.service.HelloService;
@PersistJobDataAfterExecution
@DisallowConcurrentExecution//不允许并发执行
public class HelloJob extends QuartzJobBean{
	@Override
	protected void executeInternal(JobExecutionContext context)
			throws JobExecutionException {
		//获取spring容器
		try {
			ApplicationContext ac = (ApplicationContext) context.getScheduler().getContext().get("applicationContextKey");
			HelloService service =  ac.getBean("helloService", HelloService.class);
			service.hello();
		} catch (SchedulerException e) {
			e.printStackTrace();
		}
	}
}

SchedulerFactory的xml配置

和上面没有什么不同,只是加上configLocation属性,指定配置集群的quartz.properties的文件位置。

<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
    	<!-- 数据源 -->
    	<property name="dataSource" ref="dataSource"></property>
    	<!-- Quartz.properties配置文件位置 -->
    	<property name="configLocation" value="classpath:quartz.properties"></property>
    	<property name="applicationContextSchedulerContextKey" value="applicationContextKey"></property>
    	<property name="triggers">
    		<list>
    			<ref bean="trigger1"/>
    		</list>
    	</property>
    </bean>

JobDetail的xml配置

和上面的配置的一样,只是需要注意的requestsRecovery必须配置为true,这样当一个节点没有执行,其他节点通过数据库感知到

那个节点没有在指定的时刻,执行任务,就会跑一次。

<bean id="jobDetail1" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
        <property name="jobClass" value="cn.bing.job.HelloJob"></property>    
        <!-- 必须设置为true,如果为false,当没有活动的触发器与之关联时会在调度器中会删除该任务  -->  
        <property name="durability" value="true" />    
       	<!-- 当Quartz服务被终止后,再次启动或者集群中的其他机器接受任务时候会尝试恢复之前失败的任务 -->
        <property name="requestsRecovery" value="true" />        
    </bean>

测试集群

在不同或者相同的机器上,执行下面的代码测试

我是在开启两个eclipse,执行下面的代码,停止一个后,另一个另一个将没有跑完的任务继续执行。

package cn.bing.test;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class QuartzTest {
	public static void main(String[] args) {
		  ApplicationContext springContext =
				  new ClassPathXmlApplicationContext
				  (new String[]{"classpath:applicationContext.xml","classpath:quartz-config.xml"});
	}
}

quartz水平集群和垂直集群的说明

Quartz 实际并不关心你是在相同的还是不同的机器上运行节点。当集群是放置在不同的机器上时,通常称之为水平集群。节点是跑在同一台机器是,称之为垂直集群。对于垂直集群,存在着单点故障的问题。这对高可用性的应用来说是个坏消息,因为一旦机器崩溃了,所有的节点也就被有效的终止了。

运行水平集群时候,时钟必须同步,以免出现离奇且不可预知的行为。假如时钟没能够同步,Scheduler 实例将对其他节点的状态产生混乱。有几种简单的方法来保证时钟何持同步,而且也没有理由不这么做。最简单的同步计算机时钟的方式是使用某一个 Internet 时间服务器(Internet Time Server ITS)。

没什么会阻止你在相同环境中使用集群的和非集群的 Quartz 应用。唯一要注意的是这两个环境不要混用在相同的数据库表。意思是非集群环境不要使用与集群应用相同的一套数据库表;否则将得到希奇古怪的结果,集群和非集群的 Job 都会遇到问题。

假如你让一个非集群的 Quartz 应用与集群节点并行着运行,设法使用 JobInitializationPlugin和 RAMJobStore。

转载:http://sundoctor.iteye.com/blog/486055?page=2#comments

源码下载:https://download.csdn.net/download/ditto_zhou/10580657

猜你喜欢

转载自blog.csdn.net/Ditto_zhou/article/details/81364736