Quartz集群原理以及配置應(yīng)用的方法詳解
1、Quartz任務(wù)調(diào)度的基本實現(xiàn)原理
Quartz是OpenSymphony開源組織在任務(wù)調(diào)度領(lǐng)域的一個開源項目,完全基于Java實現(xiàn)。作為一個優(yōu)秀的開源調(diào)度框架,Quartz具有以下特點:
?。?)強(qiáng)大的調(diào)度功能,例如支持豐富多樣的調(diào)度方法,可以滿足各種常規(guī)及特殊需求;
?。?)靈活的應(yīng)用方式,例如支持任務(wù)和調(diào)度的多種組合方式,支持調(diào)度數(shù)據(jù)的多種存儲方式;
?。?)分布式和集群能力,Terracotta收購后在原來功能基礎(chǔ)上作了進(jìn)一步提升。本文將對該部分相加闡述。
1.1 Quartz 核心元素
Quartz任務(wù)調(diào)度的核心元素為:Scheduler——任務(wù)調(diào)度器、Trigger——觸發(fā)器、Job——任務(wù)。其中trigger和job是任務(wù)調(diào)度的元數(shù)據(jù),scheduler是實際執(zhí)行調(diào)度的控制器。
Trigger是用于定義調(diào)度時間的元素,即按照什么時間規(guī)則去執(zhí)行任務(wù)。Quartz中主要提供了四種類型的trigger:SimpleTrigger,CronTirgger,DateIntervalTrigger,和NthIncludedDayTrigger。這四種trigger可以滿足企業(yè)應(yīng)用中的絕大部分需求。
Job用于表示被調(diào)度的任務(wù)。主要有兩種類型的job:無狀態(tài)的(stateless)和有狀態(tài)的(stateful)。對于同一個trigger來說,有狀態(tài)的job不能被并行執(zhí)行,只有上一次觸發(fā)的任務(wù)被執(zhí)行完之后,才能觸發(fā)下一次執(zhí)行。Job主要有兩種屬性:volatility和durability,其中volatility表示任務(wù)是否被持久化到數(shù)據(jù)庫存儲,而durability表示在沒有trigger關(guān)聯(lián)的時候任務(wù)是否被保留。兩者都是在值為true的時候任務(wù)被持久化或保留。一個job可以被多個trigger關(guān)聯(lián),但是一個trigger只能關(guān)聯(lián)一個job。
Scheduler由scheduler工廠創(chuàng)建:DirectSchedulerFactory或者StdSchedulerFactory。第二種工廠StdSchedulerFactory使用較多,因為DirectSchedulerFactory使用起來不夠方便,需要作許多詳細(xì)的手工編碼設(shè)置。Scheduler主要有三種:RemoteMBeanScheduler,RemoteScheduler和StdScheduler。
Quartz核心元素之間的關(guān)系如圖1.1所示:

圖1.1 核心元素關(guān)系圖
1.2 Quartz 線程視圖
在Quartz中,有兩類線程,Scheduler調(diào)度線程和任務(wù)執(zhí)行線程,其中任務(wù)執(zhí)行線程通常使用一個線程池維護(hù)一組線程。

圖1.2 Quartz線程視圖
Scheduler調(diào)度線程主要有兩個:執(zhí)行常規(guī)調(diào)度的線程,和執(zhí)行misfiredtrigger的線程。常規(guī)調(diào)度線程輪詢存儲的所有trigger,如果有需要觸發(fā)的trigger,即到達(dá)了下一次觸發(fā)的時間,則從任務(wù)執(zhí)行線程池獲取一個空閑線程,執(zhí)行與該trigger關(guān)聯(lián)的任務(wù)。Misfire線程是掃描所有的trigger,查看是否有misfiredtrigger,如果有的話根據(jù)misfire的策略分別處理(fire now OR wait for the next fire)。
1.3 Quartz Job數(shù)據(jù)存儲
Quartz中的trigger和job需要存儲下來才能被使用。Quartz中有兩種存儲方式:RAMJobStore,JobStoreSupport,其中RAMJobStore是將trigger和job存儲在內(nèi)存中,而JobStoreSupport是基于jdbc將trigger和job存儲到數(shù)據(jù)庫中。RAMJobStore的存取速度非???,但是由于其在系統(tǒng)被停止后所有的數(shù)據(jù)都會丟失,所以在集群應(yīng)用中,必須使用JobStoreSupport。
2、Quartz集群原理2.1 Quartz 集群架構(gòu)
一個Quartz集群中的每個節(jié)點是一個獨立的Quartz應(yīng)用,它又管理著其他的節(jié)點。這就意味著你必須對每個節(jié)點分別啟動或停止。Quartz集群中,獨立的Quartz節(jié)點并不與另一其的節(jié)點或是管理節(jié)點通信,而是通過相同的數(shù)據(jù)庫表來感知到另一Quartz應(yīng)用的,如圖2.1所示。

圖2.1 Quartz集群架構(gòu)
2.2 Quartz集群相關(guān)數(shù)據(jù)庫表
因為Quartz集群依賴于數(shù)據(jù)庫,所以必須首先創(chuàng)建Quartz數(shù)據(jù)庫表,Quartz發(fā)布包中包括了所有被支持的數(shù)據(jù)庫平臺的SQL腳本。這些SQL腳本存放于<quartz_home>/docs/dbTables 目錄下。這里采用的Quartz 1.8.4版本,總共12張表,不同版本,表個數(shù)可能不同。數(shù)據(jù)庫為mysql,用tables_mysql.sql創(chuàng)建數(shù)據(jù)庫表。全部表如圖2.2所示,對這些表的簡要介紹如圖2.3所示。

圖2.2 Quartz 1.8.4在mysql數(shù)據(jù)庫中生成的表

圖2.3 Quartz數(shù)據(jù)表簡介
2.2.1 調(diào)度器狀態(tài)表(QRTZ_SCHEDULER_STATE)
說明:集群中節(jié)點實例信息,Quartz定時讀取該表的信息判斷集群中每個實例的當(dāng)前狀態(tài)。
instance_name:配置文件中org.quartz.scheduler.instanceId配置的名字,如果設(shè)置為AUTO,quartz會根據(jù)物理機(jī)名和當(dāng)前時間產(chǎn)生一個名字。
last_checkin_time:上次檢入時間
checkin_interval:檢入間隔時間
2.2.2 觸發(fā)器與任務(wù)關(guān)聯(lián)表(qrtz_fired_triggers)
存儲與已觸發(fā)的Trigger相關(guān)的狀態(tài)信息,以及相聯(lián)Job的執(zhí)行信息。
2.2.3 觸發(fā)器信息表(qrtz_triggers)
trigger_name:trigger的名字,該名字用戶自己可以隨意定制,無強(qiáng)行要求
trigger_group:trigger所屬組的名字,該名字用戶自己隨意定制,無強(qiáng)行要求
job_name:qrtz_job_details表job_name的外鍵
job_group:qrtz_job_details表job_group的外鍵
trigger_state:當(dāng)前trigger狀態(tài)設(shè)置為ACQUIRED,如果設(shè)為WAITING,則job不會觸發(fā)
trigger_cron:觸發(fā)器類型,使用cron表達(dá)式
2.2.4 任務(wù)詳細(xì)信息表(qrtz_job_details)
說明:保存job詳細(xì)信息,該表需要用戶根據(jù)實際情況初始化
job_name:集群中job的名字,該名字用戶自己可以隨意定制,無強(qiáng)行要求。
job_group:集群中job的所屬組的名字,該名字用戶自己隨意定制,無強(qiáng)行要求。
job_class_name:集群中job實現(xiàn)類的完全包名,quartz就是根據(jù)這個路徑到classpath找到該job類的。
is_durable:是否持久化,把該屬性設(shè)置為1,quartz會把job持久化到數(shù)據(jù)庫中
job_data:一個blob字段,存放持久化job對象。
2.2.5權(quán)限信息表(qrtz_locks)
說明:tables_oracle.sql里有相應(yīng)的dml初始化,如圖2.4所示。

圖2.4 Quartz權(quán)限信息表中的初始化信息
2.3 Quartz Scheduler在集群中的啟動流程
Quartz Scheduler自身是察覺不到被集群的,只有配置給Scheduler的JDBC JobStore才知道。當(dāng)Quartz Scheduler啟動時,它調(diào)用JobStore的schedulerStarted()方法,它告訴JobStore Scheduler已經(jīng)啟動了。schedulerStarted() 方法是在JobStoreSupport類中實現(xiàn)的。JobStoreSupport類會根據(jù)quartz.properties文件中的設(shè)置來確定Scheduler實例是否參與到集群中。假如配置了集群,一個新的ClusterManager類的實例就被創(chuàng)建、初始化并啟動。ClusterManager是在JobStoreSupport類中的一個內(nèi)嵌類,繼承了java.lang.Thread,它會定期運行,并對Scheduler實例執(zhí)行檢入的功能。Scheduler也要查看是否有任何一個別的集群節(jié)點失敗了。檢入操作執(zhí)行周期在quartz.properties中配置。
2.4 偵測失敗的Scheduler節(jié)點
當(dāng)一個Scheduler實例執(zhí)行檢入時,它會查看是否有其他的Scheduler實例在到達(dá)他們所預(yù)期的時間還未檢入。這是通過檢查SCHEDULER_STATE表中Scheduler記錄在LAST_CHEDK_TIME列的值是否早于org.quartz.jobStore.clusterCheckinInterval來確定的。如果一個或多個節(jié)點到了預(yù)定時間還沒有檢入,那么運行中的Scheduler就假定它(們) 失敗了。
2.5 從故障實例中恢復(fù)Job
當(dāng)一個Sheduler實例在執(zhí)行某個Job時失敗了,有可能由另一正常工作的Scheduler實例接過這個Job重新運行。要實現(xiàn)這種行為,配置給JobDetail對象的Job可恢復(fù)屬性必須設(shè)置為true(job.setRequestsRecovery(true))。如果可恢復(fù)屬性被設(shè)置為false(默認(rèn)為false),當(dāng)某個Scheduler在運行該job失敗時,它將不會重新運行;而是由另一個Scheduler實例在下一次觸發(fā)時間觸發(fā)。Scheduler實例出現(xiàn)故障后多快能被偵測到取決于每個Scheduler的檢入間隔(即2.3中提到的org.quartz.jobStore.clusterCheckinInterval)。
3、Quartz集群實例(Quartz+Spring)
3.1 Spring不兼容Quartz問題
Spring從2.0.2開始便不再支持Quartz。具體表現(xiàn)在Quartz+Spring把Quartz的Task實例化進(jìn)入數(shù)據(jù)庫時,會產(chǎn)生:Serializable的錯誤:
<bean id="jobtask" class="org.springframework.scheduling.quartz. MethodInvokingJobDetailFactoryBean "> <property name="targetObject"> <ref bean="quartzJob"/> </property> <property name="targetMethod"> <value>execute</value> </property> </bean>
這個MethodInvokingJobDetailFactoryBean類中的methodInvoking方法,是不支持序列化的,因此在把QUARTZ的TASK序列化進(jìn)入數(shù)據(jù)庫時就會拋錯。
首先解決MethodInvokingJobDetailFactoryBean的問題,在不修改Spring源碼的情況下,可以避免使用這個類,直接調(diào)用JobDetail。但是使用JobDetail實現(xiàn),需要自己實現(xiàn)MothodInvoking的邏輯,可以使用JobDetail的jobClass和JobDataAsMap屬性來自定義一個Factory(Manager)來實現(xiàn)同樣的目的。例如,本示例中新建了一個MyDetailQuartzJobBean來實現(xiàn)這個功能。
3.2 MyDetailQuartzJobBean.java文件
package org.lxh.mvc.jobbean;
import java.lang.reflect.Method;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.context.ApplicationContext;
import org.springframework.scheduling.quartz.QuartzJobBean;
public class MyDetailQuartzJobBean extends QuartzJobBean {
protected final Log logger = LogFactory.getLog(getClass());
private String targetObject;
private String targetMethod;
private ApplicationContext ctx;
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
try {
logger.info("execute [" + targetObject + "] at once>>>>>>");
Object otargetObject = ctx.getBean(targetObject);
Method m = null;
try {
m = otargetObject.getClass().getMethod(targetMethod, new Class[] {});
m.invoke(otargetObject, new Object[] {});
} catch (SecurityException e) {
logger.error(e);
} catch (NoSuchMethodException e) {
logger.error(e);
}
} catch (Exception e) {
throw new JobExecutionException(e);
}
}
public void setApplicationContext(ApplicationContext applicationContext){
this.ctx=applicationContext;
}
public void setTargetObject(String targetObject) {
this.targetObject = targetObject;
}
public void setTargetMethod(String targetMethod) {
this.targetMethod = targetMethod;
}
}
3.3真正的Job實現(xiàn)類
在Test類中,只是簡單實現(xiàn)了打印系統(tǒng)當(dāng)前時間的功能。
package org.lxh.mvc.job;
import java.io.Serializable;
import java.util.Date;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class Test implements Serializable{
private Log logger = LogFactory.getLog(Test.class);
private static final long serialVersionUID = -2073310586499744415L;
public void execute () {
Date date=new Date();
System.out.println(date.toLocaleString());
}
}
3.4 配置quartz.xml文件
<bean id="Test" class="org.lxh.mvc.job.Test" scope="prototype">
</bean>
<bean id="TestjobTask" class="org.springframework.scheduling.quartz.JobDetailBean">
<property name="jobClass">
<value>org.lxh.mvc.jobbean.MyDetailQuartzJobBean</value>
</property>
<property name="jobDataAsMap">
<map>
<entry key="targetObject" value="Test" />
<entry key="targetMethod" value="execute" />
</map>
</property>
</bean>
<bean name="TestTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean">
<property name="jobDetail" ref="TestjobTask" />
<property name="cronExpression" value="0/1 * * * * ?" />
</bean>
<bean id="quartzScheduler"
class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="configLocation" value="classpath:quartz.properties"/>
<property name="triggers">
<list>
<ref bean="TestTrigger" />
</list>
</property>
<property name="applicationContextSchedulerContextKey" value="applicationContext" />
</bean>
3.5 測試
ServerA、ServerB的代碼、配置完全一樣,先啟動ServerA,后啟動ServerB,當(dāng)Server關(guān)斷之后,ServerB會監(jiān)測到其關(guān)閉,并將ServerA上正在執(zhí)行的Job接管,繼續(xù)執(zhí)行。
4、Quartz集群實例(單獨Quartz)
盡管我們已經(jīng)實現(xiàn)了Spring+Quartz的集群配置,但是因為Spring與Quartz之間的兼容問題還是不建議使用該方式。在本小節(jié)中,我們實現(xiàn)了單獨用Quartz配置的集群,相對Spring+Quartz的方式來說,簡單、穩(wěn)定。
4.1 工程結(jié)構(gòu)
我們采用單獨使用Quartz來實現(xiàn)其集群功能,代碼結(jié)構(gòu)及所需的第三方j(luò)ar包如圖3.1所示。其中,Mysql版本:5.1.52,Mysql驅(qū)動版本:mysql-connector-java-5.1.5-bin.jar(針對于5.1.52,建議采用該版本驅(qū)動,因為Quartz存在BUG使得其與某些Mysql驅(qū)動結(jié)合時不能正常運行)。

圖4.1 Quartz集群工程結(jié)構(gòu)及所需第三方j(luò)ar包
其中quartz.properties為Quartz配置文件,放在src目錄下,若無該文件,Quartz將自動加載jar包中的quartz.properties文件;SimpleRecoveryJob.java、SimpleRecoveryStatefulJob.java為兩個Job;ClusterExample.java中編寫了調(diào)度信息、觸發(fā)機(jī)制及相應(yīng)的測試main函數(shù)。
4.2 配置文件quartz.properties
默認(rèn)文件名稱quartz.properties,通過設(shè)置"org.quartz.jobStore.isClustered"屬性為"true"來激活集群特性。在集群中的每一個實例都必須有一個唯一的"instance id" ("org.quartz.scheduler.instanceId" 屬性), 但是應(yīng)該有相同的"scheduler instance name" ("org.quartz.scheduler.instanceName"),也就是說集群中的每一個實例都必須使用相同的quartz.properties 配置文件。除了以下幾種例外,配置文件的內(nèi)容其他都必須相同:
a.線程池大小。
b.不同的"org.quartz.scheduler.instanceId"屬性值(通過設(shè)定為"AUTO"即可)。
#============================================================== #Configure Main Scheduler Properties #============================================================== org.quartz.scheduler.instanceName = quartzScheduler org.quartz.scheduler.instanceId = AUTO #============================================================== #Configure JobStore #============================================================== 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.isClustered = true org.quartz.jobStore.clusterCheckinInterval = 10000 org.quartz.jobStore.dataSource = myDS #============================================================== #Configure DataSource #============================================================== org.quartz.dataSource.myDS.driver = com.mysql.jdbc.Driver org.quartz.dataSource.myDS.URL = jdbc:mysql://192.168.31.18:3306/test?useUnicode=true&characterEncoding=UTF-8 org.quartz.dataSource.myDS.user = root org.quartz.dataSource.myDS.password = 123456 org.quartz.dataSource.myDS.maxConnections = 30 #============================================================== #Configure ThreadPool #============================================================== org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool org.quartz.threadPool.threadCount = 5 org.quartz.threadPool.threadPriority = 5 org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true
4.3 ClusterExample.java文件
package cluster;
import java.util.Date;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerFactory;
import org.quartz.SimpleTrigger;
import org.quartz.impl.StdSchedulerFactory;
public class ClusterExample {
public void cleanUp(Scheduler inScheduler) throws Exception {
System.out.println("***** Deleting existing jobs/triggers *****");
// unschedule jobs
String[] groups = inScheduler.getTriggerGroupNames();
for (int i = 0; i < groups.length; i++) {
String[] names = inScheduler.getTriggerNames(groups[i]);
for (int j = 0; j < names.length; j++) {
inScheduler.unscheduleJob(names[j], groups[i]);
}
}
// delete jobs
groups = inScheduler.getJobGroupNames();
for (int i = 0; i < groups.length; i++) {
String[] names = inScheduler.getJobNames(groups[i]);
for (int j = 0; j < names.length; j++) {
inScheduler.deleteJob(names[j], groups[i]);
}
}
}
public void run(boolean inClearJobs, boolean inScheduleJobs)
throws Exception {
// First we must get a reference to a scheduler
SchedulerFactory sf = new StdSchedulerFactory();
Scheduler sched = sf.getScheduler();
if (inClearJobs) {
cleanUp(sched);
}
System.out.println("------- Initialization Complete -----------");
if (inScheduleJobs) {
System.out.println("------- Scheduling Jobs ------------------");
String schedId = sched.getSchedulerInstanceId();
int count = 1;
JobDetail job = new JobDetail("job_" + count, schedId, SimpleRecoveryJob.class);
// ask scheduler to re-execute this job if it was in progress when
// the scheduler went down...
job.setRequestsRecovery(true);
SimpleTrigger trigger =
new SimpleTrigger("triger_" + count, schedId, 200, 1000L);
trigger.setStartTime(new Date(System.currentTimeMillis() + 1000L));
System.out.println(job.getFullName() +
" will run at: " + trigger.getNextFireTime() +
" and repeat: " + trigger.getRepeatCount() +
" times, every " + trigger.getRepeatInterval() / 1000 + " seconds");
sched.scheduleJob(job, trigger);
count++;
job = new JobDetail("job_" + count, schedId,
SimpleRecoveryStatefulJob.class);
// ask scheduler to re-execute this job if it was in progress when
// the scheduler went down...
job.setRequestsRecovery(false);
trigger = new SimpleTrigger("trig_" + count, schedId, 100, 2000L);
trigger.setStartTime(new Date(System.currentTimeMillis() + 2000L));
System.out.println(job.getFullName() +
" will run at: " + trigger.getNextFireTime() +
" and repeat: " + trigger.getRepeatCount() +
" times, every " + trigger.getRepeatInterval() / 1000 + " seconds");
sched.scheduleJob(job, trigger);
}
// jobs don't start firing until start() has been called...
System.out.println("------- Starting Scheduler ---------------");
sched.start();
System.out.println("------- Started Scheduler ----------------");
System.out.println("------- Waiting for one hour... ----------");
try {
Thread.sleep(3600L * 1000L);
} catch (Exception e) {
}
System.out.println("------- Shutting Down --------------------");
sched.shutdown();
System.out.println("------- Shutdown Complete ----------------");
}
public static void main(String[] args) throws Exception {
boolean clearJobs = true;
boolean scheduleJobs = true;
for (int i = 0; i < args.length; i++) {
if (args[i].equalsIgnoreCase("clearJobs")) {
clearJobs = true;
} else if (args[i].equalsIgnoreCase("dontScheduleJobs")) {
scheduleJobs = false;
}
}
ClusterExample example = new ClusterExample();
example.run(clearJobs, scheduleJobs);
}
}
4.4 SimpleRecoveryJob.java
package cluster;
import java.io.Serializable;
import java.util.Date;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
//如果有想反復(fù)執(zhí)行的動作,作業(yè),任務(wù)就把相關(guān)的代碼寫在execute這個方法里,前提:實現(xiàn)Job這個接口
//至于SimpleJob這個類什么時候?qū)嵗琫xecute這個方法何時被調(diào)用,我們不用關(guān)注,交給Quartz
public class SimpleRecoveryJob implements Job, Serializable {
private static Log _log = LogFactory.getLog(SimpleRecoveryJob.class);
public SimpleRecoveryJob() {
}
public void execute(JobExecutionContext context)
throws JobExecutionException {
//這個作業(yè)只是簡單的打印出作業(yè)名字和此作業(yè)運行的時間
String jobName = context.getJobDetail().getFullName();
System.out.println("JOB 1111111111111111111 SimpleRecoveryJob says: " + jobName + " executing at " + new Date());
}
}
4.5 運行結(jié)果
Server A與Server B中的配置和代碼完全一樣。運行方法:運行任意主機(jī)上的ClusterExample.java,將任務(wù)加入調(diào)度,觀察運行結(jié)果:
運行ServerA,結(jié)果如圖4.2所示。

圖4.2 ServerA運行結(jié)果1
開啟ServerB后,ServerA與ServerB的輸出如圖4.3、4.4所示。

圖4.3 ServerA運行結(jié)果2

圖4.4 ServerB運行結(jié)果1
從圖4.3、4.4可以看出,ServerB開啟后,系統(tǒng)自動實現(xiàn)了負(fù)責(zé)均衡,ServerB接手Job1。關(guān)斷ServerA后,ServerB的運行結(jié)果如圖4.5所示。

圖4.5 ServerB運行結(jié)果2
從圖4.5中可以看出,ServerB可以檢測出ServerA丟失,將其負(fù)責(zé)的任務(wù)Job2接手,并將ServerA丟失到Server檢測出這段異常時間中需要執(zhí)行的Job2重新執(zhí)行了。
5、注意事項
5.1 時間同步問題
Quartz實際并不關(guān)心你是在相同還是不同的機(jī)器上運行節(jié)點。當(dāng)集群放置在不同的機(jī)器上時,稱之為水平集群。節(jié)點跑在同一臺機(jī)器上時,稱之為垂直集群。對于垂直集群,存在著單點故障的問題。這對高可用性的應(yīng)用來說是無法接受的,因為一旦機(jī)器崩潰了,所有的節(jié)點也就被終止了。對于水平集群,存在著時間同步問題。
節(jié)點用時間戳來通知其他實例它自己的最后檢入時間。假如節(jié)點的時鐘被設(shè)置為將來的時間,那么運行中的Scheduler將再也意識不到那個結(jié)點已經(jīng)宕掉了。另一方面,如果某個節(jié)點的時鐘被設(shè)置為過去的時間,也許另一節(jié)點就會認(rèn)定那個節(jié)點已宕掉并試圖接過它的Job重運行。最簡單的同步計算機(jī)時鐘的方式是使用某一個Internet時間服務(wù)器(Internet Time Server ITS)。
5.2 節(jié)點爭搶Job問題
因為Quartz使用了一個隨機(jī)的負(fù)載均衡算法, Job以隨機(jī)的方式由不同的實例執(zhí)行。Quartz官網(wǎng)上提到當(dāng)前,還不存在一個方法來指派(釘住) 一個 Job 到集群中特定的節(jié)點。
5.3 從集群獲取Job列表問題
當(dāng)前,如果不直接進(jìn)到數(shù)據(jù)庫查詢的話,還沒有一個簡單的方式來得到集群中所有正在執(zhí)行的Job列表。請求一個Scheduler實例,將只能得到在那個實例上正運行Job的列表。Quartz官網(wǎng)建議可以通過寫一些訪問數(shù)據(jù)庫JDBC代碼來從相應(yīng)的表中獲取全部的Job信息。
總結(jié)
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持。
- spring boot + quartz集群搭建的完整步驟
- SpringBoot定時任務(wù)兩種(Spring Schedule 與 Quartz 整合 )實現(xiàn)方法
- 實現(xiàn)quartz定時器及quartz定時器原理介紹
- quartz實現(xiàn)定時功能實例詳解(servlet定時器配置方法)
- 詳解Spring整合Quartz實現(xiàn)動態(tài)定時任務(wù)
- java定時調(diào)度器(Quartz)使用實例
- 在Java的Spring框架中配置Quartz的教程
- 最流行的java后臺框架spring quartz定時任務(wù)
- Spring整合Quartz實現(xiàn)動態(tài)定時器的示例代碼
- Java的作業(yè)調(diào)度類庫Quartz基本使用指南
相關(guān)文章
java實現(xiàn)學(xué)生管理系統(tǒng)(面向?qū)ο?
這篇文章主要為大家詳細(xì)介紹了java實現(xiàn)學(xué)生管理系統(tǒng)(面向?qū)ο螅闹惺纠a介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2020-03-03
spring boot整合quartz實現(xiàn)多個定時任務(wù)的方法
這篇文章主要介紹了spring boot整合quartz實現(xiàn)多個定時任務(wù)的方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-01-01
SpringBoot-RestTemplate如何實現(xiàn)調(diào)用第三方API
這篇文章主要介紹了SpringBoot-RestTemplate實現(xiàn)調(diào)用第三方API的操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-08-08
mybatis整合ehcache做三級緩存的實現(xiàn)方法
ehcache是一個快速內(nèi)存緩存框架,java項目里用起來很方便,下面這篇文章主要給大家介紹了關(guān)于mybatis整合ehcache做三級緩存的實現(xiàn)方法,文中通過實例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-06-06

