本文主要聚焦于Quartz在集群模式下的调度原理分析 一个较为典型的Quartz调度器的spring-xml配置如下
<bean id="helloWorldJob" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
<property name="durability" value="true" />
<property name="requestsRecovery" value="true" />
<property name="jobClass" value="com.paopao.HelloWord" />
</bean>
<bean id="execute_helloWorld" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<property name="jobDetail" ref="helloWorldJob" />
<property name="cronExpression" value="0/20 * * * * ?"/>
</bean>
<bean id="helloWorldScheduler" lazy-init="false" autowire="no"
class="org.springframework.scheduling.quartz.SchedulerFactoryBean" destroy-method="destroy">
<property name="quartzProperties">
<props>
<prop key="org.quartz.scheduler.skipUpdateCheck">true</prop>
</props>
</property>
<property name="applicationContextSchedulerContextKey" value="applicationContext" />
<property name="configLocation" value="classpath:quartz-exclusive.properties" />
<property name="dataSource" ref="quartzDatasource" />
<property name="overwriteExistingJobs" value="true" />
<property name="startupDelay" value="3" />
<property name="autoStartup" value="true" />
<property name="triggers">
<list>
<ref bean="execute_helloWorld"/>
</list>
</property>
</bean>
SchedulerFactoryBean实现了InitializingBean接口,在初始化bean的时候,spring容器会调用实现方法afterPropertiesSet() afterPropertiesSet()方法最关键的工作是完成了scheduler(真正实现调度工作的对象)的创建 --line10:this.scheduler = this.prepareScheduler(this.prepareSchedulerFactory());
public void afterPropertiesSet() throws Exception {
if (this.dataSource == null && this.nonTransactionalDataSource != null) {
this.dataSource = this.nonTransactionalDataSource;
}
if (this.applicationContext != null && this.resourceLoader == null) {
this.resourceLoader = this.applicationContext;
}
this.scheduler = this.prepareScheduler(this.prepareSchedulerFactory());
try {
this.registerListeners();
// 向数据库写入triggers和jobs的具体信息
this.registerJobsAndTriggers();
} catch (Exception var4) {
try {
this.scheduler.shutdown(true);
} catch (Exception var3) {
this.logger.debug("Scheduler shutdown exception after registration failure", var3);
}
throw var4;
}
}
其中prepareSchedulerFactory()方法初始化schedulerFactory private Class<? extends SchedulerFactory> schedulerFactoryClass = StdSchedulerFactory.class; 可以看到SchedulerFactoryBean.schedulerFactoryClass的默认值是StdSchedulerFactory.class 以下代码返回StdSchedulerFactory类的对象
private SchedulerFactory prepareSchedulerFactory() throws SchedulerException, IOException {
SchedulerFactory schedulerFactory = this.schedulerFactory;
if (schedulerFactory == null) {
schedulerFactory = (SchedulerFactory)BeanUtils.instantiateClass(this.schedulerFactoryClass);
if (schedulerFactory instanceof StdSchedulerFactory) {
this.initSchedulerFactory((StdSchedulerFactory)schedulerFactory);
} else if (this.configLocation != null || this.quartzProperties != null || this.taskExecutor != null || this.dataSource != null) {
throw new IllegalArgumentException("StdSchedulerFactory required for applying Quartz properties: " + schedulerFactory);
}
}
return schedulerFactory;
}
在此展开一下initSchedulerFactory()方法, this.configLocation即开头xml文件中配置的 可以看到在这里把该配置文件的内容传递给了schedulerFactory(在后面初始化scheduler的时候起到作用)
private void initSchedulerFactory(StdSchedulerFactory schedulerFactory) throws SchedulerException, IOException {
Properties mergedProps = new Properties();
....
// 可以看到,如果没有给调度器的bean配置taskExecutor的话,即使在quartz-exclusive.properties配置了线程池,也会被SimpleThreadPool覆盖
if (this.taskExecutor != null) {
mergedProps.setProperty("org.quartz.threadPool.class", LocalTaskExecutorThreadPool.class.getName());
} else {
mergedProps.setProperty("org.quartz.threadPool.class", SimpleThreadPool.class.getName());
mergedProps.setProperty("org.quartz.threadPool.threadCount", Integer.toString(10));
}
if (this.configLocation != null) {
....
PropertiesLoaderUtils.fillProperties(mergedProps, this.configLocation);
}
....
// 如果配置了dataSouce,那么会用LocalDataSourceJobStore覆盖org.quartz.jobStore.class配置
if (this.dataSource != null) {
mergedProps.setProperty("org.quartz.jobStore.class", LocalDataSourceJobStore.class.getName());
}
// 如果没有给调度器的bean配置schedulerName,那么会使用配置项org.quartz.scheduler.instanceName的值作为schedulerName
// 如果以上都没有配置,那么使用beanName作为schedulerName,由于SchedulerFactoryBean实现了BeanNameAware接口,所有beanName就是beanId
// schedulerName会存储在qrtz_scheduler_state表中(SCHED_NAME)
if (this.schedulerName != null) {
mergedProps.setProperty("org.quartz.scheduler.instanceName", this.schedulerName);
} else {
String nameProp = mergedProps.getProperty("org.quartz.scheduler.instanceName");
if (nameProp != null) {
this.schedulerName = nameProp;
} else if (this.beanName != null) {
mergedProps.setProperty("org.quartz.scheduler.instanceName", this.beanName);
this.schedulerName = this.beanName;
}
}
// 最后会把prop赋给StdSchedulerFactory用以初始化StdScheduler
schedulerFactory.initialize(mergedProps);
}
然后再通过SchedulerFactoryBean.prepareScheduler()来使用初始化真正工作的scheduler
private Scheduler prepareScheduler(SchedulerFactory schedulerFactory) throws SchedulerException {
....
Scheduler var3;
try {
Scheduler scheduler = this.createScheduler(schedulerFactory, this.schedulerName);
....
var3 = scheduler;
} finally {
....
}
return var3;
}
进入createScheduler方法,可以看到最终初始化scheduler的是schedulerFactory(实现类StdSchedulerFactory)的getScheduler()方法
protected Scheduler createScheduler(SchedulerFactory schedulerFactory, @Nullable String schedulerName) throws SchedulerException {
....
Scheduler var10;
try {
SchedulerRepository repository = SchedulerRepository.getInstance();
synchronized(repository) {
....
Scheduler newScheduler = schedulerFactory.getScheduler();
....
var10 = newScheduler;
}
} finally {
....
}
return var10;
}
然后进入getScheduler()方法,可以看到,调用了instantiate()
public Scheduler getScheduler() throws SchedulerException {
....
sched = instantiate();
return sched;
}
instantiate()是一个长达800行的方法,以下摘录重点片段
private Scheduler instantiate() throws SchedulerException {
....
// org.quartz.scheduler.instanceName配置项在quartz-exclusive.properties中指定
String schedName = cfg.getStringProperty("org.quartz.scheduler.instanceName", "QuartzScheduler");
....
// 一般使用org.quartz.scheduler.instanceId=AUTO
String schedInstId = cfg.getStringProperty("org.quartz.scheduler.instanceId", "NON_CLUSTERED");
if (schedInstId.equals("AUTO")) {
// 当配置org.quartz.scheduler.instanceId=AUTO的时候,默认使用SimpleInstanceIdGenerator,生成调度器Id的方法是hostName+timeStamp
// 调度器Id会存储在qrtz_scheduler_state表中(INSTANCE_NAME)
instanceIdGeneratorClass = cfg.getStringProperty("org.quartz.scheduler.instanceIdGenerator.class", "org.quartz.simpl.SimpleInstanceIdGenerator");
}
....
// 在schedulerFactoryBean中配置了org.quartz.jobStore.class=LocalDataSourceJobStore
String jsClass = cfg.getStringProperty("org.quartz.jobStore.class", RAMJobStore.class.getName());
....
// 这个jobStore对象是对数据库进行操作的关键对象
jobStore js = (JobStore) loadHelper.loadClass(jsClass).newInstance();
....
// 把scheName和scheInstId复制给jobStore
SchedulerDetailsSetter.setDetails(js, schedName, schedInstId);
// 把配置文件中org.quartz.jobStore.*的几个配置项取出并且赋值给jobStore
tProps = cfg.getPropertyGroup(PROP_JOB_STORE_PREFIX, true, new String[] {PROP_JOB_STORE_LOCK_HANDLER_PREFIX});
try {
setBeanProps(js, tProps);
} catch (Exception e) {
initException = new SchedulerException("JobStore class '" + jsClass + "' props could not be configured.", e);
throw initException;
}
....
QuartzSchedulerResources rsrcs = new QuartzSchedulerResources();
rsrcs.setJobStore(js);
....
// 可以看到,这里的工作是把一些配置信息赋给了QuartzSchedulerResources,然后QuartzScheduler持有这些信息,StdScheduler又持有QuartzScheduler
// QuartzScheduler的构造方法会在下面展开(重要)
QuartzScheduler qs = new QuartzScheduler(rsrcs, idleWaitTime, dbFailureRetry);
Scheduler scheduler = instantiate(rsrcs, qs);
....
js.initialize(loadHelper, qs.getSchedulerSignaler());
....
// 在这里终于把StdScheduler给return出来了,回到SchedulerFactoryBean.repareScheduler()方法
return scheduler;
}
scheduler的初始化完成后,向数据库写入triggers和jobs的具体信息 这里比较宏观的反映overwriteExistingJobs是如何控制job和trigger的初始化的,对数据库操作的细节在调度器的运行篇章
private boolean addTriggerToScheduler(Trigger trigger) throws SchedulerException {
// 先查库确认trigger是否存在
boolean triggerExists = this.getScheduler().getTrigger(trigger.getKey()) != null;
if (triggerExists && !this.overwriteExistingJobs) {
// 存在并且不需要覆盖,则退出
return false;
} else {
JobDetail jobDetail = (JobDetail)trigger.getJobDataMap().remove("jobDetail");
if (triggerExists) {
// 如果存在并覆写,this.addJobToScheduler()方法会覆写qrtz_job_details表
if (jobDetail != null && this.jobDetails != null && !this.jobDetails.contains(jobDetail) && this.addJobToScheduler(jobDetail)) {
this.jobDetails.add(jobDetail);
}
try {
// rescheduleJob()方法会覆写qrtz_triggers表和qrtz_cron_triggers表
this.getScheduler().rescheduleJob(trigger.getKey(), trigger);
} catch (ObjectAlreadyExistsException var6) {
// 多节点同时写的时候,其他节点抢先更新了trigger信息,有可能进入catch分支,可忽略的异常,记录日志
}
} else {
// 进入这个分支,说明数据库中不存在数据
try {
if (jobDetail == null || this.jobDetails == null || this.jobDetails.contains(jobDetail) || !this.overwriteExistingJobs && this.getScheduler().getJobDetail(jobDetail.getKey()) != null) {
this.getScheduler().scheduleJob(trigger);
} else {
this.getScheduler().scheduleJob(jobDetail, trigger);
this.jobDetails.add(jobDetail);
}
} catch (ObjectAlreadyExistsException var5) {
// 原先数据库没有数据,多节点同时写的时候,有可能进入catch分支
if (this.overwriteExistingJobs) {
this.getScheduler().rescheduleJob(trigger.getKey(), trigger);
}
}
}
return true;
}
}
JobStore的初始化比较重要,初始化的时候生成了StdRowLockSemaphore类的对象用来管理分布式锁
public void initialize(ClassLoadHelper loadHelper, SchedulerSignaler signaler) throws SchedulerConfigException {
....
if (getLockHandler() == null) {
if (isClustered()) {
// 集群模式下必须使用数据库锁,防止更新表的时候冲突
setUseDBLocks(true);
}
if (getUseDBLocks()) {
// Delegate使用的是StdJDBCDelegate,所以不进入if分支
if(getDriverDelegateClass() != null && getDriverDelegateClass().equals(MSSQLDelegate.class.getName())) {
if(getSelectWithLockSQL() == null) {
String msSqlDflt = "SELECT * FROM {0}LOCKS WITH (UPDLOCK,ROWLOCK) WHERE " + COL_SCHEDULER_NAME + " = {1} AND LOCK_NAME = ?";
getLog().info("Detected usage of MSSQLDelegate class - defaulting 'selectWithLockSQL' to '" + msSqlDflt + "'.");
setSelectWithLockSQL(msSqlDflt);
}
}
// tablePrefix和selectWithLockSQL已经在执行该方法之前赋值给JobStore
setLockHandler(new StdRowLockSemaphore(getTablePrefix(), getInstanceName(), getSelectWithLockSQL()));
} else {
getLog().info(
"Using thread monitor-based data access locking (synchronization).");
setLockHandler(new SimpleSemaphore());
}
}
}
QuartzScheduler的构造方法如下,可以看到在构造方法中初始化了QuartzSchedulerThread并执行这个线程类 QuartzSchedulerThread这个线程类的run方法就是整个Quartz执行任务的过程(在运行篇讲解)
public QuartzScheduler(QuartzSchedulerResources resources, long idleWaitTime, @Deprecated long dbRetryInterval)
throws SchedulerException {
....
this.schedThread = new QuartzSchedulerThread(this, resources);
ThreadExecutor schedThreadExecutor = resources.getThreadExecutor();
schedThreadExecutor.execute(this.schedThread);
....
}
至此,整个SchedulerFactoryBean的初始化主流程结束了,由于SchedulerFactoryBean实现了SmartLifecycle接口,将在spring容器启动后,自动执行start()方法,这将放到调度器的运行篇来讲解