This article focuses on Quartz scheduling in cluster mode and analyzes the spring-XML configuration of a typical Quartz scheduler as follows
<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>Copy the code
SchedulerFactoryBean implements the InitializingBean interface, and when the bean is initialized, The afterPropertiesSet() method is called by the Spring container. The afterPropertiesSet() method completes the creation of the scheduler (the object that does the actual scheduling). 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(); / / write to the database triggers and specific information of jobs enclosing registerJobsAndTriggers (); } catch (Exception var4) { try { this.scheduler.shutdown(true); } catch (Exception var3) { this.logger.debug("Scheduler shutdown exception after registration failure", var3); } throw var4; }}Copy the code
The prepareSchedulerFactory() method initializes the schedulerFactory private Class<? extends SchedulerFactory> schedulerFactoryClass = StdSchedulerFactory.class; Can see SchedulerFactoryBean. SchedulerFactoryClass default value is StdSchedulerFactory. The class the following code returns StdSchedulerFactory objects of a class
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; }Copy the code
Expand the initSchedulerFactory() method here, This. ConfigLocation is configured in the header XML file and you can see that the contents of the configuration file are passed to the schedulerFactory (which will later initialize the scheduler).
private void initSchedulerFactory(StdSchedulerFactory schedulerFactory) throws SchedulerException, IOException { Properties mergedProps = new Properties(); . If (this.taskExecutor!) {// 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); }... / / if the configuration dataSouce, then will use LocalDataSourceJobStore cover org. Quartz jobStore class configuration if (this. The dataSource. = null) { mergedProps.setProperty("org.quartz.jobStore.class", LocalDataSourceJobStore.class.getName()); } / / if you don't have to bean configuration schedulerName scheduler, so will use configuration items org. Quartz. The scheduler. The instanceName value as the schedulerName / / if none of the above configuration, Using beanName as schedulerName, since SchedulerFactoryBean implements the BeanNameAware interface, All beannames are beanId // Schedulernames are stored in the qrtz_scheduler_state table (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; }} / / will finally get the prop is assigned to StdSchedulerFactory used to initialize the StdScheduler schedulerFactory. The initialize (mergedProps); }Copy the code
Then through SchedulerFactoryBean. PrepareScheduler initialization () to use real job scheduler
private Scheduler prepareScheduler(SchedulerFactory schedulerFactory) throws SchedulerException { .... Scheduler var3; try { Scheduler scheduler = this.createScheduler(schedulerFactory, this.schedulerName); . var3 = scheduler; } finally { .... } return var3; }Copy the code
Entering the createScheduler method, you can see that the final initialization of the scheduler is the getScheduler() method of the schedulerFactory (implementation class StdSchedulerFactory)
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; }Copy the code
We then go to the getScheduler() method and see that we called instantiate()
public Scheduler getScheduler() throws SchedulerException {
....
sched = instantiate();
return sched;
}
Copy the code
Instantiate () is an 800-line method, highlighted below
private Scheduler instantiate() throws SchedulerException { .... / / org. Quartz. The scheduler. InstanceName configuration items in quartz - exclusive. The properties specified in the String schedName = cfg.getStringProperty("org.quartz.scheduler.instanceName", "QuartzScheduler"); . / / generally use org. Quartz. The scheduler. InstanceId = AUTO String schedInstId = cfg.getStringProperty("org.quartz.scheduler.instanceId", "NON_CLUSTERED"); If (schedInstId. Equals (" AUTO ")) {/ / when configuration org. Quartz. The scheduler. InstanceId = AUTO, use the default SimpleInstanceIdGenerator, The scheduler Id is stored in the qrTZ_scheduler_State table (INSTANCE_NAME) instanceIdGeneratorClass = cfg.getStringProperty("org.quartz.scheduler.instanceIdGenerator.class", "org.quartz.simpl.SimpleInstanceIdGenerator"); }... / / configuration in schedulerFactoryBean org. Quartz. JobStore. Class = LocalDataSourceJobStore String jsClass = cfg.getStringProperty("org.quartz.jobStore.class", RAMJobStore.class.getName()); . JobStore js = (jobStore) loadHelper.loadClass(jsClass).newinstance (); . / / copy to the jobStore scheName and scheInstId SchedulerDetailsSetter. SetDetails (js, schedName schedInstId); GetPropertyGroup (PROP_JOB_STORE_PREFIX, true,); // 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); . // As you can see, the job here is to assign some configuration information to QuartzSchedulerResources, and then QuartzScheduler holds this information. QuartzScheduler qs = new QuartzScheduler(RSRCS, idleWaitTime, dbFailureRetry); Scheduler scheduler = instantiate(rsrcs, qs); . js.initialize(loadHelper, qs.getSchedulerSignaler()); . / / here give return StdScheduler out finally, back to SchedulerFactoryBean. RepareScheduler () method return scheduler; }Copy the code
After scheduler initialization is complete, triggers and Jobs are written to the database. Here is a macro view of how overwriteExistingJobs controls the initialization of jobs and triggers. Details of database operations are included in the scheduler operation chapter
Private Boolean addTriggerToScheduler(Trigger Trigger) throws SchedulerException {// Check whether Trigger has a Boolean triggerExists = this.getScheduler().getTrigger(trigger.getKey()) ! = null; if (triggerExists && ! Enclosing overwriteExistingJobs) {/ / exists and does not need to cover, the exit return false. } else { JobDetail jobDetail = (JobDetail)trigger.getJobDataMap().remove("jobDetail"); If (triggerExists) {// The this.addJobToScheduler() method overwrites the qrtz_job_details table if (jobDetail! = null && this.jobDetails ! = null && ! this.jobDetails.contains(jobDetail) && this.addJobToScheduler(jobDetail)) { this.jobDetails.add(jobDetail); } try {// rescheduleJob() triggers this.getscheduler ().rescheduleJob(), trigger); } the catch (ObjectAlreadyExistsException var6) {/ / multi-node write at the same time, other nodes first to update the trigger information, may enter the catch branch, negligible abnormalities, }} else {// Enter the branch, Try {if there is no data in the database (jobDetail = = null | | this. JobDetails = = null | | this. JobDetails. The contains (jobDetail) | |! this.overwriteExistingJobs && this.getScheduler().getJobDetail(jobDetail.getKey()) ! = null) { this.getScheduler().scheduleJob(trigger); } else { this.getScheduler().scheduleJob(jobDetail, trigger); this.jobDetails.add(jobDetail); }} the catch (ObjectAlreadyExistsException var5) {/ / original database without the data, multi-node write at the same time, Branch is likely to enter the catch the if (this. OverwriteExistingJobs) {enclosing getScheduler () rescheduleJob (trigger. The getKey (), the trigger). } } } return true; }}Copy the code
The initialization of JobStore is important and generates objects of the StdRowLockSemaphore class to manage distributed locks
public void initialize(ClassLoadHelper loadHelper, SchedulerSignaler signaler) throws SchedulerConfigException { .... If (getLockHandler() == null) {if (isClustered()) {// Database locks must be used to prevent table updates from colliding setUseDBLocks(true); } if(getUseDBLocks()) {// Delegate uses StdJDBCDelegate, so do not enter the if branch 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 and selectWithLockSQL have been assigned to JobStore setLockHandler(new stlockSemaphore (getTablePrefix(), getInstanceName(), getSelectWithLockSQL())); } else { getLog().info( "Using thread monitor-based data access locking (synchronization)."); setLockHandler(new SimpleSemaphore()); }}}Copy the code
The construction method of QuartzScheduler is as follows: The constructor initializes the QuartzSchedulerThread and executes the thread class QuartzSchedulerThread. The run method of the thread class is the entire Process of executing the Quartz task.
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); . }Copy the code
Since SchedulerFactoryBean implements the SmartLifecycle interface, it automatically executes the Start () method once the Spring container is started, which will be explained in the scheduler runtime section