Spring-Quartz企业级理解-调度器的初始化

1,159 阅读4分钟

本文主要聚焦于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()方法,这将放到调度器的运行篇来讲解