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

z1340954953發表於2018-08-02

目錄

建立資料庫表

配置資料庫連線池

建立工作類

工作類中呼叫的業務類

 工作類

配置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

相關文章