Quartz應用和集群原理分析:
使用的環境版本:spring4.x+quartz2.2.x
****1.1 如何在spring中集成quartz集群****
1.1.1 基于maven項目,需要在pom.xml引入的j依賴為:
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
</dependency>
1.1.2 Quartz集群的基本配置信息:命名為quartz.properties
#調度標識名 集群中每一個實例都必須使用相同的名稱
org.quartz.scheduler.instanceName: DefaultQuartzScheduler
#遠程管理相關的配置,全部關閉
org.quartz.scheduler.rmi.export: false
org.quartz.scheduler.rmi.proxy: false
org.quartz.scheduler.wrapJobExecutionInUserTransaction: false
ThreadPool 實現的類名
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.misfireThreshold: 60000
#ID設置為自動獲取 每一個必須不同
org.quartz.scheduler.instanceId: AUTO
#數據保存方式為持久化
org.quartz.jobStore.class: org.quartz.impl.jdbcjobstore.JobStoreTX
#加入集群
org.quartz.jobStore.isClustered: true
#調度實例失效的檢查時間間隔
org.quartz.jobStore.clusterCheckinInterval: 10000
1.1.3 在項目中加入Quartz的初始化信息: 命名spring-quartz.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<bean id="quartzScheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<!-- 自定義的bean注入類,解決job里面無法注入spring的service的問題 -->
<property name="jobFactory">
<bean class="com.fc.sales.control.statistics.job.SpringBeanJobFactory" />
</property>
<!-- quartz的數據源 -->
<property name="dataSource" ref="quartz" />
<!-- quartz的基本配置信息引入 -->
<property name="configLocation" value="classpath:quartz.properties"/>
<!-- 調度標識名 -->
<property name="schedulerName" value="DefaultQuartzScheduler" />
<!--必須的,QuartzScheduler 延時啟動,應用啟動完后 QuartzScheduler 再啟動 -->
<property name="startupDelay" value="30" />
<!-- 通過applicationContextSchedulerContextKey屬性配置spring上下文 -->
<property name="applicationContextSchedulerContextKey" value="applicationContextKey" />
<!--可選,QuartzScheduler 啟動時更新己存在的Job,這樣就不用每次修改targetObject后刪除qrtz_job_details表對應記錄了 -->
<property name="overwriteExistingJobs" value="true" />
<!-- 設置自動啟動 -->
<property name="autoStartup" value="true" />
<!-- 注冊觸發器 -->
<property name="triggers">
<list>
<ref bean="orderSyncScannerTrigger" />
</list>
</property>
<!-- 注冊jobDetail -->
<property name="jobDetails">
<list>
<ref bean="orderSyncDetail" />
</list>
</property>
</bean>
<!--配置調度具體執行的方法-->
<bean id="orderSyncDetail"
class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
<property name="jobClass" value="com.fc.sales.control.statistics.job.OrderSyncJob"/>
<property name="durability" value="true" />
<property name="requestsRecovery" value="true" />
</bean>
<!--配置調度執行的觸發的時間-->
<bean id="orderSyncScannerTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<property name="jobDetail" ref="orderSyncDetail" />
<property name="cronExpression">
<!-- 每天上午00:30點執行任務調度 -->
<value>0 30 00 * * ?</value>
</property>
</bean>
- 1.1.4 在web.xml啟動項中加入spring-quartz.xml文件*
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:/spring/spring-quartz.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
- 1.1.5 附上對應的解決無法注入的jobFactory的代碼*:
import org.quartz.spi.TriggerFiredBundle;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.scheduling.quartz.AdaptableJobFactory;
import org.springframework.stereotype.Component;
/**
* Created by lyndon on 16/9/13.
*/
@Component
public class jobFactory extends AdaptableJobFactory {
//這個對象Spring會幫我們自動注入進來,也屬于Spring技術范疇.
@Autowired
private AutowireCapableBeanFactory capableBeanFactory;
protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
//調用父類的方法
Object jobInstance = super.createJobInstance(bundle);
//進行注入,這屬于Spring的技術,不清楚的可以查看Spring的API.
capableBeanFactory.autowireBean(jobInstance);
return jobInstance;
}
}
****1.2 quartz框架實現分布式定時任務的原理****;
Quartz集群中每個節點都是一個單獨的Quartz應用,它又管理著其他的節點。這個集群需要每個節點單獨的啟動或停止;和我們的應用服務器集群不同,獨立的Quratz節點之間是不需要 通信的。不同節點之間是通過數據庫表來感知另一個應用。只有使用持久的JobStore才能完成Quartz集群。
- 1.2.1 既然Quartz分布式集群是利用數據庫鎖機制來實現集群環境下的并發控制,我們就需要了解Quratz的數據庫表:可以去官方現在對于版本的sql文件導入。
1.2.2 Quartz線程模型:
Quartz中有兩類線程:Scheduler調度線程和任務執行線程。任務執行線程: Quartz不會在主線程(QuartzSchedulerThread)中處理用戶job。Quratz是將線程管理的職責委托給ThreadPool,一般的設置使用SimpleThreadPool,SimpleThreadPool創建一定數量的工作線程(WorkerThread),當然這樣就意味所有的線程都是異步操作的,所以我們在工作線程的job里面實現業務的時候是沒必要重新去創建一個新的線程的,在Quartz創建工作線程的時候已經完成了異步任務的創建。
Scheduler調度線程:QuartzScheduler被創建的時候會創建一個QuratzSchedulerThread實例。
1.2.3 Quartz源碼分析:
QuartzSchedulerThreand包含有決定何時下一個Job將被觸發的處理循環,主要的邏輯在其的run()方法中,如下圖:
由此可知,QuartzSchedulerThread不斷的在獲取trigger,觸發trigger,釋放trigger。
那么具體又是如何獲取trigger的呢,可以從上面的源碼中可以發現:qsRsrcs.getJobStore()返回對象是JobStore ,具體的集群配置參考1.1.2. org.quartz.jobStore.class:org.quartz.impl.jdbcjobstore.JobStoreTX
JobStoreTx繼承自JobStoreSupport,而JobStoreSupport的acquireNextTrigger,triggerFired,releaseAcquiredTrigger方法負責具體trigger相關操作,都必須獲得TRIGGER-ACCESS鎖。核心邏輯在executeInNonManagedTxLock方法中。
由上代碼可知Quartz集群基于數據庫鎖的同步操作流程如下圖所示: