Quartz介绍
Quartz是一个完全由 Java 编写的开源作业调度框架
官网:Quartz Enterprise Job Scheduler
下载:Downloads
API手册:Quartz Enterprise Job Scheduler 2.2.2 API
GitHub:https://github.com/quartz-scheduler/quartz/releases/tag/v2.3.2
Quartz简单使用
public class ScheduleTest { public static void main(String[] args) { try { //1.初始化调度器 Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler(); scheduler.getContext().put("skey", "svalue"); //2.创建触发器 SimpleTrigger trigger = TriggerBuilder.newTrigger() .withIdentity("trigger1", "group1") .usingJobData("t1", "tv1") .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInMinutes(2).repeatForever()) .build(); trigger.getJobDataMap().put("t2", "tv2"); //3.创建Job JobDetail job = JobBuilder.newJob(MyJob.class).usingJobData("j1", "jv1") .withIdentity("myjob", "mygroup") .build(); job.getJobDataMap().put("j2", "jv2"); //4.调度器启动 scheduler.scheduleJob(job, trigger); scheduler.start(); } catch (SchedulerException e) { e.printStackTrace(); } } }
public class MyJob implements Job { @Override public void execute(JobExecutionContext context) throws JobExecutionException { System.out.println("调度任务测试..."); Object tv1 = context.getTrigger().getJobDataMap().get("t1"); Object tv2 = context.getTrigger().getJobDataMap().get("t2"); Object jv1 = context.getJobDetail().getJobDataMap().get("j1"); Object jv2 = context.getJobDetail().getJobDataMap().get("j2"); Object skey = null; try { skey = context.getScheduler().getContext().get("skey"); } catch (SchedulerException e) { e.printStackTrace(); } System.out.println(tv1 + ":" + tv2); System.out.println(jv1 + ":" + jv2); System.out.println("skey:" + skey); System.out.println("调度时间--" + LocalDateTime.now()); } }
Quartz源码解析
类图:
-
创建Scheduler
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler(); 等价于Scheduler scheduler = new StdSchedulerFactory().getScheduler();
public Scheduler getScheduler() throws SchedulerException { /** * cfg即PropertiesParse对象,如果配置文件没被加载,加载配置文件,将其读取到Properties类,然后创建PropertiesParser类,并传入 * Properties将其初始化,即最终生成了一个包含quartz.properties文件配置属性的PropertiesParse对象 */ if (cfg == null) { initialize(); } /** * 获取一个SchedulerRepository调度器仓库实例,判断名称为"QuartzScheduler"调度器是否存在,存在且非shutdown状态的话,返回仓库中的调度器; * 不存在的话,将"QuartzScheduler"放入repository仓库 */ SchedulerRepository schedRep = SchedulerRepository.getInstance(); Scheduler sched = schedRep.lookup(getSchedulerName()); if (sched != null) { if (sched.isShutdown()) { schedRep.remove(getSchedulerName()); } else { return sched; } } //根据PropertiesParser(quartz.properties)属性初始化scheduler,并将scheduler放入ScheudlerRepository仓库 sched = instantiate(); return sched; }
initialize():配置文件没有被加载或PropertiesParser为空的话,会依次根据系统变量、工程classpath、quartz包路径的顺序查找quartz.properties文件
public void initialize() throws SchedulerException { // short-circuit if already initialized if (cfg != null) { return; } if (initException != null) { throw initException; } String requestedFile = System.getProperty(PROPERTIES_FILE); String propFileName = requestedFile != null ? requestedFile : "quartz.properties"; File propFile = new File(propFileName); Properties props = new Properties(); InputStream in = null; try { if (propFile.exists()) { try { if (requestedFile != null) { propSrc = "specified file: '" + requestedFile + "'"; } else { propSrc = "default file in current working dir: 'quartz.properties'"; } in = new BufferedInputStream(new FileInputStream(propFileName)); props.load(in); } catch (IOException ioe) { initException = new SchedulerException("Properties file: '" + propFileName + "' could not be read.", ioe); throw initException; } } else if (requestedFile != null) { in = Thread.currentThread().getContextClassLoader().getResourceAsStream(requestedFile); if(in == null) { initException = new SchedulerException("Properties file: '" + requestedFile + "' could not be found."); throw initException; } propSrc = "specified file: '" + requestedFile + "' in the class resource path."; in = new BufferedInputStream(in); try { props.load(in); } catch (IOException ioe) { initException = new SchedulerException("Properties file: '" + requestedFile + "' could not be read.", ioe); throw initException; } } else { propSrc = "default resource file in Quartz package: 'quartz.properties'"; ClassLoader cl = getClass().getClassLoader(); if(cl == null) cl = findClassloader(); if(cl == null) throw new SchedulerConfigException("Unable to find a class loader on the current thread or class."); in = cl.getResourceAsStream( "quartz.properties"); if (in == null) { in = cl.getResourceAsStream( "/quartz.properties"); } if (in == null) { in = cl.getResourceAsStream( "org/quartz/quartz.properties"); } if (in == null) { initException = new SchedulerException( "Default quartz.properties not found in class path"); throw initException; } try { props.load(in); } catch (IOException ioe) { initException = new SchedulerException( "Resource properties file: 'org/quartz/quartz.properties' " + "could not be read from the classpath.", ioe); throw initException; } } } finally { if(in != null) { try { in.close(); } catch(IOException ignore) { /* ignore */ } } } initialize(overrideWithSysProps(props, getLog())); }
instantiate():
private Scheduler instantiate() throws SchedulerException { if (cfg == null) { initialize(); } if (initException != null) { throw initException; } JobStore js = null; ThreadPool tp = null; QuartzScheduler qs = null; DBConnectionManager dbMgr = null; String instanceIdGeneratorClass = null; Properties tProps = null; String userTXLocation = null; boolean wrapJobInTx = false; boolean autoId = false; long idleWaitTime = -1; long dbFailureRetry = 15000L; // 15 secs String classLoadHelperClass; String jobFactoryClass; ThreadExecutor threadExecutor; SchedulerRepository schedRep = SchedulerRepository.getInstance(); // Get Scheduler Properties // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ String schedName = cfg.getStringProperty(PROP_SCHED_INSTANCE_NAME, "QuartzScheduler"); String threadName = cfg.getStringProperty(PROP_SCHED_THREAD_NAME, schedName + "_QuartzSchedulerThread"); // schedInstId生成,如果配置成自动生成,则获取自动生成器去生成;如果配置成系统属性生成,则用相应的类生成 String schedInstId = cfg.getStringProperty(PROP_SCHED_INSTANCE_ID, DEFAULT_INSTANCE_ID); if (schedInstId.equals(AUTO_GENERATE_INSTANCE_ID)) { autoId = true; instanceIdGeneratorClass = cfg.getStringProperty( PROP_SCHED_INSTANCE_ID_GENERATOR_CLASS, "org.quartz.simpl.SimpleInstanceIdGenerator"); } else if (schedInstId.equals(SYSTEM_PROPERTY_AS_INSTANCE_ID)) { autoId = true; instanceIdGeneratorClass = "org.quartz.simpl.SystemPropertyInstanceIdGenerator"; } //获取UserTansaction的Jndi url userTXLocation = cfg.getStringProperty(PROP_SCHED_USER_TX_URL, userTXLocation); if (userTXLocation != null && userTXLocation.trim().length() == 0) { userTXLocation = null; } classLoadHelperClass = cfg.getStringProperty( PROP_SCHED_CLASS_LOAD_HELPER_CLASS, "org.quartz.simpl.CascadingClassLoadHelper"); //是否用UserTansaction来执行Job wrapJobInTx = cfg.getBooleanProperty(PROP_SCHED_WRAP_JOB_IN_USER_TX, wrapJobInTx); jobFactoryClass = cfg.getStringProperty( PROP_SCHED_JOB_FACTORY_CLASS, null); idleWaitTime = cfg.getLongProperty(PROP_SCHED_IDLE_WAIT_TIME, idleWaitTime); if(idleWaitTime > -1 && idleWaitTime < 1000) { throw new SchedulerException("org.quartz.scheduler.idleWaitTime of less than 1000ms is not legal."); } dbFailureRetry = cfg.getLongProperty(PROP_SCHED_DB_FAILURE_RETRY_INTERVAL, dbFailureRetry); if (dbFailureRetry < 0) { throw new SchedulerException(PROP_SCHED_DB_FAILURE_RETRY_INTERVAL + " of less than 0 ms is not legal."); } boolean makeSchedulerThreadDaemon = cfg.getBooleanProperty(PROP_SCHED_MAKE_SCHEDULER_THREAD_DAEMON); boolean threadsInheritInitalizersClassLoader = cfg.getBooleanProperty(PROP_SCHED_SCHEDULER_THREADS_INHERIT_CONTEXT_CLASS_LOADER_OF_INITIALIZING_THREAD); long batchTimeWindow = cfg.getLongProperty(PROP_SCHED_BATCH_TIME_WINDOW, 0L); int maxBatchSize = cfg.getIntProperty(PROP_SCHED_MAX_BATCH_SIZE, 1); boolean interruptJobsOnShutdown = cfg.getBooleanProperty(PROP_SCHED_INTERRUPT_JOBS_ON_SHUTDOWN, false); boolean interruptJobsOnShutdownWithWait = cfg.getBooleanProperty(PROP_SCHED_INTERRUPT_JOBS_ON_SHUTDOWN_WITH_WAIT, false); //JMX相关配置 boolean jmxExport = cfg.getBooleanProperty(PROP_SCHED_JMX_EXPORT); String jmxObjectName = cfg.getStringProperty(PROP_SCHED_JMX_OBJECT_NAME); boolean jmxProxy = cfg.getBooleanProperty(PROP_SCHED_JMX_PROXY); String jmxProxyClass = cfg.getStringProperty(PROP_SCHED_JMX_PROXY_CLASS); //RMI相关配置 boolean rmiExport = cfg.getBooleanProperty(PROP_SCHED_RMI_EXPORT, false); boolean rmiProxy = cfg.getBooleanProperty(PROP_SCHED_RMI_PROXY, false); String rmiHost = cfg.getStringProperty(PROP_SCHED_RMI_HOST, "localhost"); int rmiPort = cfg.getIntProperty(PROP_SCHED_RMI_PORT, 1099); int rmiServerPort = cfg.getIntProperty(PROP_SCHED_RMI_SERVER_PORT, -1); String rmiCreateRegistry = cfg.getStringProperty( PROP_SCHED_RMI_CREATE_REGISTRY, QuartzSchedulerResources.CREATE_REGISTRY_NEVER); String rmiBindName = cfg.getStringProperty(PROP_SCHED_RMI_BIND_NAME); //JMX跟RMI不能同时开启 if (jmxProxy && rmiProxy) { throw new SchedulerConfigException("Cannot proxy both RMI and JMX."); } boolean managementRESTServiceEnabled = cfg.getBooleanProperty(MANAGEMENT_REST_SERVICE_ENABLED, false); String managementRESTServiceHostAndPort = cfg.getStringProperty(MANAGEMENT_REST_SERVICE_HOST_PORT, "0.0.0.0:9889"); Properties schedCtxtProps = cfg.getPropertyGroup(PROP_SCHED_CONTEXT_PREFIX, true); // If Proxying to remote scheduler, short-circuit here... // ~~~~~~~~~~~~~~~~~~ //如果是RMI代理,则创建RemoteScheduler if (rmiProxy) { if (autoId) { schedInstId = DEFAULT_INSTANCE_ID; } String uid = (rmiBindName == null) ? QuartzSchedulerResources.getUniqueIdentifier( schedName, schedInstId) : rmiBindName; RemoteScheduler remoteScheduler = new RemoteScheduler(uid, rmiHost, rmiPort); schedRep.bind(remoteScheduler); return remoteScheduler; } // Create class load helper ClassLoadHelper loadHelper = null; try { loadHelper = (ClassLoadHelper) loadClass(classLoadHelperClass) .newInstance(); } catch (Exception e) { throw new SchedulerConfigException( "Unable to instantiate class load helper class: " + e.getMessage(), e); } loadHelper.initialize(); // If Proxying to remote JMX scheduler, short-circuit here... // ~~~~~~~~~~~~~~~~~~ //如果是JMX代理,则创建RemoteMBeanScheduler if (jmxProxy) { if (autoId) { schedInstId = DEFAULT_INSTANCE_ID; } if (jmxProxyClass == null) { throw new SchedulerConfigException("No JMX Proxy Scheduler class provided"); } RemoteMBeanScheduler jmxScheduler = null; try { jmxScheduler = (RemoteMBeanScheduler)loadHelper.loadClass(jmxProxyClass) .newInstance(); } catch (Exception e) { throw new SchedulerConfigException( "Unable to instantiate RemoteMBeanScheduler class.", e); } if (jmxObjectName == null) { jmxObjectName = QuartzSchedulerResources.generateJMXObjectName(schedName, schedInstId); } jmxScheduler.setSchedulerObjectName(jmxObjectName); tProps = cfg.getPropertyGroup(PROP_SCHED_JMX_PROXY, true); try { setBeanProps(jmxScheduler, tProps); } catch (Exception e) { initException = new SchedulerException("RemoteMBeanScheduler class '" + jmxProxyClass + "' props could not be configured.", e); throw initException; } jmxScheduler.initialize(); schedRep.bind(jmxScheduler); return jmxScheduler; } //初始化jobFactory JobFactory jobFactory = null; if(jobFactoryClass != null) { try { jobFactory = (JobFactory) loadHelper.loadClass(jobFactoryClass) .newInstance(); } catch (Exception e) { throw new SchedulerConfigException( "Unable to instantiate JobFactory class: " + e.getMessage(), e); } tProps = cfg.getPropertyGroup(PROP_SCHED_JOB_FACTORY_PREFIX, true); try { setBeanProps(jobFactory, tProps); } catch (Exception e) { initException = new SchedulerException("JobFactory class '" + jobFactoryClass + "' props could not be configured.", e); throw initException; } } InstanceIdGenerator instanceIdGenerator = null; if(instanceIdGeneratorClass != null) { try { instanceIdGenerator = (InstanceIdGenerator) loadHelper.loadClass(instanceIdGeneratorClass) .newInstance(); } catch (Exception e) { throw new SchedulerConfigException( "Unable to instantiate InstanceIdGenerator class: " + e.getMessage(), e); } tProps = cfg.getPropertyGroup(PROP_SCHED_INSTANCE_ID_GENERATOR_PREFIX, true); try { setBeanProps(instanceIdGenerator, tProps); } catch (Exception e) { initException = new SchedulerException("InstanceIdGenerator class '" + instanceIdGeneratorClass + "' props could not be configured.", e); throw initException; } } // Get ThreadPool Properties // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //初始化ThreadPool String tpClass = cfg.getStringProperty(PROP_THREAD_POOL_CLASS, SimpleThreadPool.class.getName()); if (tpClass == null) { initException = new SchedulerException( "ThreadPool class not specified. "); throw initException; } try { tp = (ThreadPool) loadHelper.loadClass(tpClass).newInstance(); } catch (Exception e) { initException = new SchedulerException("ThreadPool class '" + tpClass + "' could not be instantiated.", e); throw initException; } tProps = cfg.getPropertyGroup(PROP_THREAD_POOL_PREFIX, true); try { setBeanProps(tp, tProps); } catch (Exception e) { initException = new SchedulerException("ThreadPool class '" + tpClass + "' props could not be configured.", e); throw initException; } // Get JobStore Properties // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //初始化JobStore String jsClass = cfg.getStringProperty(PROP_JOB_STORE_CLASS, RAMJobStore.class.getName()); if (jsClass == null) { initException = new SchedulerException( "JobStore class not specified. "); throw initException; } try { js = (JobStore) loadHelper.loadClass(jsClass).newInstance(); } catch (Exception e) { initException = new SchedulerException("JobStore class '" + jsClass + "' could not be instantiated.", e); throw initException; } SchedulerDetailsSetter.setDetails(js, schedName, schedInstId); 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; } //如果是JobStoreSupport的话,需要设置锁控制器,用于集群的调度同步 if (js instanceof JobStoreSupport) { // Install custom lock handler (Semaphore) String lockHandlerClass = cfg.getStringProperty(PROP_JOB_STORE_LOCK_HANDLER_CLASS); if (lockHandlerClass != null) { try { Semaphore lockHandler = (Semaphore)loadHelper.loadClass(lockHandlerClass).newInstance(); tProps = cfg.getPropertyGroup(PROP_JOB_STORE_LOCK_HANDLER_PREFIX, true); // If this lock handler requires the table prefix, add it to its properties. if (lockHandler instanceof TablePrefixAware) { tProps.setProperty( PROP_TABLE_PREFIX, ((JobStoreSupport)js).getTablePrefix()); tProps.setProperty( PROP_SCHED_NAME, schedName); } try { setBeanProps(lockHandler, tProps); } catch (Exception e) { initException = new SchedulerException("JobStore LockHandler class '" + lockHandlerClass + "' props could not be configured.", e); throw initException; } ((JobStoreSupport)js).setLockHandler(lockHandler); getLog().info("Using custom data access locking (synchronization): " + lockHandlerClass); } catch (Exception e) { initException = new SchedulerException("JobStore LockHandler class '" + lockHandlerClass + "' could not be instantiated.", e); throw initException; } } } // Set up any DataSources // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //获取dataSource String[] dsNames = cfg.getPropertyGroups(PROP_DATASOURCE_PREFIX); for (int i = 0; i < dsNames.length; i++) { PropertiesParser pp = new PropertiesParser(cfg.getPropertyGroup( PROP_DATASOURCE_PREFIX + "." + dsNames[i], true)); //datasource被封装为ConnectionProvider String cpClass = pp.getStringProperty(PROP_CONNECTION_PROVIDER_CLASS, null); // custom connectionProvider... if(cpClass != null) { ConnectionProvider cp = null; try { cp = (ConnectionProvider) loadHelper.loadClass(cpClass).newInstance(); } catch (Exception e) { initException = new SchedulerException("ConnectionProvider class '" + cpClass + "' could not be instantiated.", e); throw initException; } try { // remove the class name, so it isn't attempted to be set pp.getUnderlyingProperties().remove( PROP_CONNECTION_PROVIDER_CLASS); if (cp instanceof PoolingConnectionProvider) { populateProviderWithExtraProps((PoolingConnectionProvider)cp, pp.getUnderlyingProperties()); } else { setBeanProps(cp, pp.getUnderlyingProperties()); } cp.initialize(); } catch (Exception e) { initException = new SchedulerException("ConnectionProvider class '" + cpClass + "' props could not be configured.", e); throw initException; } dbMgr = DBConnectionManager.getInstance(); dbMgr.addConnectionProvider(dsNames[i], cp); } else { //JNDI的配置 String dsJndi = pp.getStringProperty(PROP_DATASOURCE_JNDI_URL, null); if (dsJndi != null) { boolean dsAlwaysLookup = pp.getBooleanProperty( PROP_DATASOURCE_JNDI_ALWAYS_LOOKUP); String dsJndiInitial = pp.getStringProperty( PROP_DATASOURCE_JNDI_INITIAL); String dsJndiProvider = pp.getStringProperty( PROP_DATASOURCE_JNDI_PROVDER); String dsJndiPrincipal = pp.getStringProperty( PROP_DATASOURCE_JNDI_PRINCIPAL); String dsJndiCredentials = pp.getStringProperty( PROP_DATASOURCE_JNDI_CREDENTIALS); Properties props = null; if (null != dsJndiInitial || null != dsJndiProvider || null != dsJndiPrincipal || null != dsJndiCredentials) { props = new Properties(); if (dsJndiInitial != null) { props.put(PROP_DATASOURCE_JNDI_INITIAL, dsJndiInitial); } if (dsJndiProvider != null) { props.put(PROP_DATASOURCE_JNDI_PROVDER, dsJndiProvider); } if (dsJndiPrincipal != null) { props.put(PROP_DATASOURCE_JNDI_PRINCIPAL, dsJndiPrincipal); } if (dsJndiCredentials != null) { props.put(PROP_DATASOURCE_JNDI_CREDENTIALS, dsJndiCredentials); } } JNDIConnectionProvider cp = new JNDIConnectionProvider(dsJndi, props, dsAlwaysLookup); dbMgr = DBConnectionManager.getInstance(); dbMgr.addConnectionProvider(dsNames[i], cp); } else { String poolingProvider = pp.getStringProperty(PoolingConnectionProvider.POOLING_PROVIDER); //本地driver配置 String dsDriver = pp.getStringProperty(PoolingConnectionProvider.DB_DRIVER); String dsURL = pp.getStringProperty(PoolingConnectionProvider.DB_URL); if (dsDriver == null) { initException = new SchedulerException( "Driver not specified for DataSource: " + dsNames[i]); throw initException; } if (dsURL == null) { initException = new SchedulerException( "DB URL not specified for DataSource: " + dsNames[i]); throw initException; } // we load even these "core" providers by class name in order to avoid a static dependency on // the c3p0 and hikaricp libraries if(poolingProvider != null && poolingProvider.equals(PoolingConnectionProvider.POOLING_PROVIDER_HIKARICP)) { cpClass = "org.quartz.utils.HikariCpPoolingConnectionProvider"; } else { cpClass = "org.quartz.utils.C3p0PoolingConnectionProvider"; } log.info("Using ConnectionProvider class '" + cpClass + "' for data source '" + dsNames[i] + "'"); try { ConnectionProvider cp = null; try { Constructor constructor = loadHelper.loadClass(cpClass).getConstructor(Properties.class); cp = (ConnectionProvider) constructor.newInstance(pp.getUnderlyingProperties()); } catch (Exception e) { initException = new SchedulerException("ConnectionProvider class '" + cpClass + "' could not be instantiated.", e); throw initException; } dbMgr = DBConnectionManager.getInstance(); dbMgr.addConnectionProvider(dsNames[i], cp); // Populate the underlying C3P0/HikariCP data source pool properties populateProviderWithExtraProps((PoolingConnectionProvider)cp, pp.getUnderlyingProperties()); } catch (Exception sqle) { initException = new SchedulerException( "Could not initialize DataSource: " + dsNames[i], sqle); throw initException; } } } } // Set up any SchedulerPlugins // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //SchedulerPlugin初始化 String[] pluginNames = cfg.getPropertyGroups(PROP_PLUGIN_PREFIX); SchedulerPlugin[] plugins = new SchedulerPlugin[pluginNames.length]; for (int i = 0; i < pluginNames.length; i++) { Properties pp = cfg.getPropertyGroup(PROP_PLUGIN_PREFIX + "." + pluginNames[i], true); String plugInClass = pp.getProperty(PROP_PLUGIN_CLASS, null); if (plugInClass == null) { initException = new SchedulerException( "SchedulerPlugin class not specified for plugin '" + pluginNames[i] + "'"); throw initException; } SchedulerPlugin plugin = null; try { plugin = (SchedulerPlugin) loadHelper.loadClass(plugInClass).newInstance(); } catch (Exception e) { initException = new SchedulerException( "SchedulerPlugin class '" + plugInClass + "' could not be instantiated.", e); throw initException; } try { setBeanProps(plugin, pp); } catch (Exception e) { initException = new SchedulerException( "JobStore SchedulerPlugin '" + plugInClass + "' props could not be configured.", e); throw initException; } plugins[i] = plugin; } // Set up any JobListeners // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //JobListener初始化 Class<?>[] strArg = new Class[] { String.class }; String[] jobListenerNames = cfg.getPropertyGroups(PROP_JOB_LISTENER_PREFIX); JobListener[] jobListeners = new JobListener[jobListenerNames.length]; for (int i = 0; i < jobListenerNames.length; i++) { Properties lp = cfg.getPropertyGroup(PROP_JOB_LISTENER_PREFIX + "." + jobListenerNames[i], true); String listenerClass = lp.getProperty(PROP_LISTENER_CLASS, null); if (listenerClass == null) { initException = new SchedulerException( "JobListener class not specified for listener '" + jobListenerNames[i] + "'"); throw initException; } JobListener listener = null; try { listener = (JobListener) loadHelper.loadClass(listenerClass).newInstance(); } catch (Exception e) { initException = new SchedulerException( "JobListener class '" + listenerClass + "' could not be instantiated.", e); throw initException; } try { Method nameSetter = null; try { nameSetter = listener.getClass().getMethod("setName", strArg); } catch(NoSuchMethodException ignore) { /* do nothing */ } if(nameSetter != null) { nameSetter.invoke(listener, new Object[] {jobListenerNames[i] } ); } setBeanProps(listener, lp); } catch (Exception e) { initException = new SchedulerException( "JobListener '" + listenerClass + "' props could not be configured.", e); throw initException; } jobListeners[i] = listener; } // Set up any TriggerListeners // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //TriggerListener初始化 String[] triggerListenerNames = cfg.getPropertyGroups(PROP_TRIGGER_LISTENER_PREFIX); TriggerListener[] triggerListeners = new TriggerListener[triggerListenerNames.length]; for (int i = 0; i < triggerListenerNames.length; i++) { Properties lp = cfg.getPropertyGroup(PROP_TRIGGER_LISTENER_PREFIX + "." + triggerListenerNames[i], true); String listenerClass = lp.getProperty(PROP_LISTENER_CLASS, null); if (listenerClass == null) { initException = new SchedulerException( "TriggerListener class not specified for listener '" + triggerListenerNames[i] + "'"); throw initException; } TriggerListener listener = null; try { listener = (TriggerListener) loadHelper.loadClass(listenerClass).newInstance(); } catch (Exception e) { initException = new SchedulerException( "TriggerListener class '" + listenerClass + "' could not be instantiated.", e); throw initException; } try { Method nameSetter = null; try { nameSetter = listener.getClass().getMethod("setName", strArg); } catch(NoSuchMethodException ignore) { /* do nothing */ } if(nameSetter != null) { nameSetter.invoke(listener, new Object[] {triggerListenerNames[i] } ); } setBeanProps(listener, lp); } catch (Exception e) { initException = new SchedulerException( "TriggerListener '" + listenerClass + "' props could not be configured.", e); throw initException; } triggerListeners[i] = listener; } boolean tpInited = false; boolean qsInited = false; // Get ThreadExecutor Properties // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //ThreadExecutor初始化 String threadExecutorClass = cfg.getStringProperty(PROP_THREAD_EXECUTOR_CLASS); if (threadExecutorClass != null) { tProps = cfg.getPropertyGroup(PROP_THREAD_EXECUTOR, true); try { threadExecutor = (ThreadExecutor) loadHelper.loadClass(threadExecutorClass).newInstance(); log.info("Using custom implementation for ThreadExecutor: " + threadExecutorClass); setBeanProps(threadExecutor, tProps); } catch (Exception e) { initException = new SchedulerException( "ThreadExecutor class '" + threadExecutorClass + "' could not be instantiated.", e); throw initException; } } else { log.info("Using default implementation for ThreadExecutor"); threadExecutor = new DefaultThreadExecutor(); } // Fire everything up // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ try { //实例化JobRunShellFactory JobRunShellFactory jrsf = null; // Create correct run-shell factory... if (userTXLocation != null) { UserTransactionHelper.setUserTxLocation(userTXLocation); } if (wrapJobInTx) { jrsf = new JTAJobRunShellFactory(); } else { jrsf = new JTAAnnotationAwareJobRunShellFactory(); } if (autoId) { try { schedInstId = DEFAULT_INSTANCE_ID; if (js.isClustered()) { schedInstId = instanceIdGenerator.generateInstanceId(); } } catch (Exception e) { getLog().error("Couldn't generate instance Id!", e); throw new IllegalStateException("Cannot run without an instance id."); } } if (js.getClass().getName().startsWith("org.terracotta.quartz")) { try { String uuid = (String) js.getClass().getMethod("getUUID").invoke(js); if(schedInstId.equals(DEFAULT_INSTANCE_ID)) { schedInstId = "TERRACOTTA_CLUSTERED,node=" + uuid; if (jmxObjectName == null) { jmxObjectName = QuartzSchedulerResources.generateJMXObjectName(schedName, schedInstId); } } else if(jmxObjectName == null) { jmxObjectName = QuartzSchedulerResources.generateJMXObjectName(schedName, schedInstId + ",node=" + uuid); } } catch(Exception e) { throw new RuntimeException("Problem obtaining node id from TerracottaJobStore.", e); } if(null == cfg.getStringProperty(PROP_SCHED_JMX_EXPORT)) { jmxExport = true; } } if (js instanceof JobStoreSupport) { JobStoreSupport jjs = (JobStoreSupport)js; jjs.setDbRetryInterval(dbFailureRetry); if(threadsInheritInitalizersClassLoader) jjs.setThreadsInheritInitializersClassLoadContext(threadsInheritInitalizersClassLoader); jjs.setThreadExecutor(threadExecutor); } //将上边初始化或实例化的对象放入到QuartzSchedulerResources,给Scheduler用 QuartzSchedulerResources rsrcs = new QuartzSchedulerResources(); rsrcs.setName(schedName); rsrcs.setThreadName(threadName); rsrcs.setInstanceId(schedInstId); rsrcs.setJobRunShellFactory(jrsf); rsrcs.setMakeSchedulerThreadDaemon(makeSchedulerThreadDaemon); rsrcs.setThreadsInheritInitializersClassLoadContext(threadsInheritInitalizersClassLoader); rsrcs.setBatchTimeWindow(batchTimeWindow); rsrcs.setMaxBatchSize(maxBatchSize); rsrcs.setInterruptJobsOnShutdown(interruptJobsOnShutdown); rsrcs.setInterruptJobsOnShutdownWithWait(interruptJobsOnShutdownWithWait); rsrcs.setJMXExport(jmxExport); rsrcs.setJMXObjectName(jmxObjectName); if (managementRESTServiceEnabled) { ManagementRESTServiceConfiguration managementRESTServiceConfiguration = new ManagementRESTServiceConfiguration(); managementRESTServiceConfiguration.setBind(managementRESTServiceHostAndPort); managementRESTServiceConfiguration.setEnabled(managementRESTServiceEnabled); rsrcs.setManagementRESTServiceConfiguration(managementRESTServiceConfiguration); } if (rmiExport) { rsrcs.setRMIRegistryHost(rmiHost); rsrcs.setRMIRegistryPort(rmiPort); rsrcs.setRMIServerPort(rmiServerPort); rsrcs.setRMICreateRegistryStrategy(rmiCreateRegistry); rsrcs.setRMIBindName(rmiBindName); } SchedulerDetailsSetter.setDetails(tp, schedName, schedInstId); rsrcs.setThreadExecutor(threadExecutor); threadExecutor.initialize(); rsrcs.setThreadPool(tp); if(tp instanceof SimpleThreadPool) { if(threadsInheritInitalizersClassLoader) ((SimpleThreadPool)tp).setThreadsInheritContextClassLoaderOfInitializingThread(threadsInheritInitalizersClassLoader); } tp.initialize(); tpInited = true; rsrcs.setJobStore(js); // add plugins for (int i = 0; i < plugins.length; i++) { rsrcs.addSchedulerPlugin(plugins[i]); } //初始化QuartzScheduler qs = new QuartzScheduler(rsrcs, idleWaitTime, dbFailureRetry); qsInited = true; // Create Scheduler ref... Scheduler scheduler = instantiate(rsrcs, qs); // set job factory if specified if(jobFactory != null) { qs.setJobFactory(jobFactory); } // Initialize plugins now that we have a Scheduler instance. for (int i = 0; i < plugins.length; i++) { plugins[i].initialize(pluginNames[i], scheduler, loadHelper); } // add listeners for (int i = 0; i < jobListeners.length; i++) { qs.getListenerManager().addJobListener(jobListeners[i], EverythingMatcher.allJobs()); } for (int i = 0; i < triggerListeners.length; i++) { qs.getListenerManager().addTriggerListener(triggerListeners[i], EverythingMatcher.allTriggers()); } // set scheduler context data... for(Object key: schedCtxtProps.keySet()) { String val = schedCtxtProps.getProperty((String) key); scheduler.getContext().put((String)key, val); } // fire up job store, and runshell factory js.setInstanceId(schedInstId); js.setInstanceName(schedName); js.setThreadPoolSize(tp.getPoolSize()); js.initialize(loadHelper, qs.getSchedulerSignaler()); //初始化JobRunShellFactory jrsf.initialize(scheduler); //初始化QuartzScheduler qs.initialize(); getLog().info( "Quartz scheduler '" + scheduler.getSchedulerName() + "' initialized from " + propSrc); getLog().info("Quartz scheduler version: " + qs.getVersion()); // prevents the repository from being garbage collected qs.addNoGCObject(schedRep); // prevents the db manager from being garbage collected if (dbMgr != null) { qs.addNoGCObject(dbMgr); } //将scheduler放入SchedulerRepository仓库 schedRep.bind(scheduler); return scheduler; } catch(SchedulerException e) { shutdownFromInstantiateException(tp, qs, tpInited, qsInited); throw e; } catch(RuntimeException re) { shutdownFromInstantiateException(tp, qs, tpInited, qsInited); throw re; } catch(Error re) { shutdownFromInstantiateException(tp, qs, tpInited, qsInited); throw re; } }
new QuartzScheduler(rsrcs, idleWaitTime, dbFailureRetry):
public QuartzScheduler(QuartzSchedulerResources resources, long idleWaitTime, @Deprecated long dbRetryInterval) throws SchedulerException { this.resources = resources; if (resources.getJobStore() instanceof JobListener) { addInternalJobListener((JobListener)resources.getJobStore()); } this.schedThread = new QuartzSchedulerThread(this, resources); ThreadExecutor schedThreadExecutor = resources.getThreadExecutor(); schedThreadExecutor.execute(this.schedThread); if (idleWaitTime > 0) { this.schedThread.setIdleWaitTime(idleWaitTime); } jobMgr = new ExecutingJobsManager(); addInternalJobListener(jobMgr); errLogger = new ErrorLogger(); addInternalSchedulerListener(errLogger); signaler = new SchedulerSignalerImpl(this, this.schedThread); getLog().info("Quartz Scheduler v." + getVersion() + " created."); }
总结即,判断在仓库中是否存在调度器,存在的话,返回仓库中调度器,不存在的话,创建StdScheduler并初始化
初始化过程包含,读取配置文件,放到PropertiesParser,然后供相关需要实例化或初始化的对象使用,再把初始化或实例化后的对象放到QuartzSchedulerResources,然后给Scheduler初始化用
-
创建触发器
//TriggerBuilder public T build() { if(scheduleBuilder == null) scheduleBuilder = SimpleScheduleBuilder.simpleSchedule(); MutableTrigger trig = scheduleBuilder.build(); trig.setCalendarName(calendarName); trig.setDescription(description); trig.setStartTime(startTime); trig.setEndTime(endTime); if(key == null) key = new TriggerKey(Key.createUniqueName(null), null); trig.setKey(key); if(jobKey != null) trig.setJobKey(jobKey); trig.setPriority(priority); if(!jobDataMap.isEmpty()) trig.setJobDataMap(jobDataMap); return (T) trig; }
-
创建Job
public JobDetail build() { JobDetailImpl job = new JobDetailImpl(); job.setJobClass(jobClass); job.setDescription(description); if(key == null) key = new JobKey(Key.createUniqueName(null), null); job.setKey(key); job.setDurability(durability); job.setRequestsRecovery(shouldRecover); if(!jobDataMap.isEmpty()) job.setJobDataMap(jobDataMap); return job; }
-
调度器启动
StdScheduler调用QuartzScheduler.scheduleJob()方法
public Date scheduleJob(JobDetail jobDetail, Trigger trigger) throws SchedulerException { validateState(); if (jobDetail == null) { throw new SchedulerException("JobDetail cannot be null"); } if (trigger == null) { throw new SchedulerException("Trigger cannot be null"); } if (jobDetail.getKey() == null) { throw new SchedulerException("Job's key cannot be null"); } if (jobDetail.getJobClass() == null) { throw new SchedulerException("Job's class cannot be null"); } OperableTrigger trig = (OperableTrigger)trigger; if (trigger.getJobKey() == null) { trig.setJobKey(jobDetail.getKey()); } else if (!trigger.getJobKey().equals(jobDetail.getKey())) { throw new SchedulerException( "Trigger does not reference given job!"); } trig.validate(); Calendar cal = null; if (trigger.getCalendarName() != null) { cal = resources.getJobStore().retrieveCalendar(trigger.getCalendarName()); } Date ft = trig.computeFirstFireTime(cal); if (ft == null) { throw new SchedulerException( "Based on configured schedule, the given trigger '" + trigger.getKey() + "' will never fire."); } resources.getJobStore().storeJobAndTrigger(jobDetail, trig); notifySchedulerListenersJobAdded(jobDetail); notifySchedulerThread(trigger.getNextFireTime().getTime()); notifySchedulerListenersSchduled(trigger); return ft; }
resources.getJobStore().storeJobAndTrigger(jobDetail, trig)跟进:
// JobStoreSupport public void storeJobAndTrigger(final JobDetail newJob, final OperableTrigger newTrigger) throws JobPersistenceException { executeInLock( (isLockOnInsert()) ? LOCK_TRIGGER_ACCESS : null, new VoidTransactionCallback() { public void executeVoid(Connection conn) throws JobPersistenceException { storeJob(conn, newJob, false); storeTrigger(conn, newTrigger, newJob, false, Constants.STATE_WAITING, false, false); } }); }
storeJob(conn, newJob, false):
// JobStoreSupport protected void storeJob(Connection conn, JobDetail newJob, boolean replaceExisting) throws JobPersistenceException { boolean existingJob = jobExists(conn, newJob.getKey()); try { if (existingJob) { if (!replaceExisting) { throw new ObjectAlreadyExistsException(newJob); } getDelegate().updateJobDetail(conn, newJob); } else { getDelegate().insertJobDetail(conn, newJob); } } catch (IOException e) { throw new JobPersistenceException("Couldn't store job: " + e.getMessage(), e); } catch (SQLException e) { throw new JobPersistenceException("Couldn't store job: " + e.getMessage(), e); } }
getDelegate().insertJobDetail(conn, newJob);
// StdJDBCDelegate public int insertJobDetail(Connection conn, JobDetail job) throws IOException, SQLException { ByteArrayOutputStream baos = serializeJobData(job.getJobDataMap()); PreparedStatement ps = null; int insertResult = 0; try { ps = conn.prepareStatement(rtp(INSERT_JOB_DETAIL)); ps.setString(1, job.getKey().getName()); ps.setString(2, job.getKey().getGroup()); ps.setString(3, job.getDescription()); ps.setString(4, job.getJobClass().getName()); setBoolean(ps, 5, job.isDurable()); setBoolean(ps, 6, job.isConcurrentExectionDisallowed()); setBoolean(ps, 7, job.isPersistJobDataAfterExecution()); setBoolean(ps, 8, job.requestsRecovery()); setBytes(ps, 9, baos); insertResult = ps.executeUpdate(); } finally { closeStatement(ps); } return insertResult; }
job-insert语句:INSERT INTO QRTZ_JOB_DETAILS (SCHED_NAME, JOB_NAME, JOB_GROUP, DESCRIPTION, JOB_CLASS_NAME, IS_DURABLE, IS_NONCONCURRENT, IS_UPDATE_DATA, REQUESTS_RECOVERY, JOB_DATA) VALUES ('DefaultScheduler', ?, ?, ?, ?, ?, ?, ?, ?, ?)
storeTrigger(conn, newTrigger, newJob, false,Constants.STATE_WAITING, false, false):
// JobStoreSupport protected void storeTrigger(Connection conn, OperableTrigger newTrigger, JobDetail job, boolean replaceExisting, String state, boolean forceState, boolean recovering) throws JobPersistenceException { boolean existingTrigger = triggerExists(conn, newTrigger.getKey()); if ((existingTrigger) && (!replaceExisting)) { throw new ObjectAlreadyExistsException(newTrigger); } try { boolean shouldBepaused; if (!forceState) { shouldBepaused = getDelegate().isTriggerGroupPaused( conn, newTrigger.getKey().getGroup()); if(!shouldBepaused) { shouldBepaused = getDelegate().isTriggerGroupPaused(conn, ALL_GROUPS_PAUSED); if (shouldBepaused) { getDelegate().insertPausedTriggerGroup(conn, newTrigger.getKey().getGroup()); } } if (shouldBepaused && (state.equals(STATE_WAITING) || state.equals(STATE_ACQUIRED))) { state = STATE_PAUSED; } } if(job == null) { job = retrieveJob(conn, newTrigger.getJobKey()); } if (job == null) { throw new JobPersistenceException("The job (" + newTrigger.getJobKey() + ") referenced by the trigger does not exist."); } if (job.isConcurrentExectionDisallowed() && !recovering) { state = checkBlockedState(conn, job.getKey(), state); } if (existingTrigger) { getDelegate().updateTrigger(conn, newTrigger, state, job); } else { getDelegate().insertTrigger(conn, newTrigger, state, job); } } catch (Exception e) { throw new JobPersistenceException("Couldn't store trigger '" + newTrigger.getKey() + "' for '" + newTrigger.getJobKey() + "' job:" + e.getMessage(), e); } } ```
getDelegate().insertTrigger(conn, newTrigger, state, job); 跟进:
// StdJDBCDelegate public int insertTrigger(Connection conn, OperableTrigger trigger, String state, JobDetail jobDetail) throws SQLException, IOException { ByteArrayOutputStream baos = null; if(trigger.getJobDataMap().size() > 0) { baos = serializeJobData(trigger.getJobDataMap()); } PreparedStatement ps = null; int insertResult = 0; try { ps = conn.prepareStatement(rtp(INSERT_TRIGGER)); ps.setString(1, trigger.getKey().getName()); ps.setString(2, trigger.getKey().getGroup()); ps.setString(3, trigger.getJobKey().getName()); ps.setString(4, trigger.getJobKey().getGroup()); ps.setString(5, trigger.getDescription()); if(trigger.getNextFireTime() != null) ps.setBigDecimal(6, new BigDecimal(String.valueOf(trigger .getNextFireTime().getTime()))); else ps.setBigDecimal(6, null); long prevFireTime = -1; if (trigger.getPreviousFireTime() != null) { prevFireTime = trigger.getPreviousFireTime().getTime(); } ps.setBigDecimal(7, new BigDecimal(String.valueOf(prevFireTime))); ps.setString(8, state); TriggerPersistenceDelegate tDel = findTriggerPersistenceDelegate(trigger); String type = TTYPE_BLOB; if(tDel != null) type = tDel.getHandledTriggerTypeDiscriminator(); ps.setString(9, type); ps.setBigDecimal(10, new BigDecimal(String.valueOf(trigger .getStartTime().getTime()))); long endTime = 0; if (trigger.getEndTime() != null) { endTime = trigger.getEndTime().getTime(); } ps.setBigDecimal(11, new BigDecimal(String.valueOf(endTime))); ps.setString(12, trigger.getCalendarName()); ps.setInt(13, trigger.getMisfireInstruction()); setBytes(ps, 14, baos); ps.setInt(15, trigger.getPriority()); insertResult = ps.executeUpdate(); if(tDel == null) insertBlobTrigger(conn, trigger); else tDel.insertExtendedTriggerProperties(conn, trigger, state, jobDetail); } finally { closeStatement(ps); } return insertResult; }
trigger-insert语句:INSERT_INTO QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP, JOB_NAME, JOB_GROUP, DESCRIPTION, NEXT_FIRE_TIME, PREV_FIRE_TIME, TRIGGER_STATE, TRIGGER_TYPE, START_TIME, END_TIME, CALENDAR_NAME, MISFIRE_INSTR, JOB_DATA, PRIORITY) VALUES ('DefaultScheduler', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
notifySchedulerListenersJobAdded(jobDetail); 跟进:
// QuartzScheduler public void notifySchedulerListenersJobAdded(JobDetail jobDetail) { // build a list of all scheduler listeners that are to be notified... List<SchedulerListener> schedListeners = buildSchedulerListenerList(); // notify all scheduler listeners for(SchedulerListener sl: schedListeners) { try { sl.jobAdded(jobDetail); } catch (Exception e) { getLog().error( "Error while notifying SchedulerListener of JobAdded.", e); } } }
// BroadcastSchedulerListener public void jobAdded(JobDetail jobDetail) { Iterator<SchedulerListener> itr = listeners.iterator(); while(itr.hasNext()) { SchedulerListener l = itr.next(); l.jobAdded(jobDetail); } }
notifySchedulerThread(trigger.getNextFireTime().getTime()); 跟进:
// QuartzScheduler protected void notifySchedulerThread(long candidateNewNextFireTime) { if (isSignalOnSchedulingChange()) { signaler.signalSchedulingChange(candidateNewNextFireTime); } }
// SchedulerSignalerImpl public void signalSchedulingChange(long candidateNewNextFireTime) { schedThread.signalSchedulingChange(candidateNewNextFireTime); }
// QuartzSchedulerThread public void signalSchedulingChange(long candidateNewNextFireTime) { synchronized(sigLock) { signaled = true; signaledNextFireTime = candidateNewNextFireTime; sigLock.notifyAll(); } }
notifySchedulerListenersSchduled(trigger); 跟进:
// QuartzScheduler public void notifySchedulerListenersSchduled(Trigger trigger) { // build a list of all scheduler listeners that are to be notified... List<SchedulerListener> schedListeners = buildSchedulerListenerList(); // notify all scheduler listeners for(SchedulerListener sl: schedListeners) { try { sl.jobScheduled(trigger); } catch (Exception e) { getLog().error( "Error while notifying SchedulerListener of scheduled job." + " Triger=" + trigger.getKey(), e); } } }
// BroadcastSchedulerListener public void jobScheduled(Trigger trigger) { Iterator<SchedulerListener> itr = listeners.iterator(); while(itr.hasNext()) { SchedulerListener l = itr.next(); l.jobScheduled(trigger); } }
总结即:验证调度器是否关闭;JobDetail、Trigger是否为空校验;存储Job和Trigger到数据库;通知调度器监听器增加job;通知调度器线程启动;通知调度器监听器增加触发器
-
调度启动:
// StdScheduler public void start() throws SchedulerException { sched.start(); }
sched.start(); 跟进:
// QuartzScheduler public void start() throws SchedulerException { if (shuttingDown|| closed) { throw new SchedulerException( "The Scheduler cannot be restarted after shutdown() has been called."); } // QTZ-212 : calling new schedulerStarting() method on the listeners // right after entering start() notifySchedulerListenersStarting(); if (initialStart == null) { initialStart = new Date(); this.resources.getJobStore().schedulerStarted(); startPlugins(); } else { resources.getJobStore().schedulerResumed(); } schedThread.togglePause(false); getLog().info( "Scheduler " + resources.getUniqueIdentifier() + " started."); notifySchedulerListenersStarted(); }
notifySchedulerListenersStarting();跟进:
// BroadcastSchedulerListener public void schedulerStarting() { Iterator<SchedulerListener> itr = listeners.iterator(); while (itr.hasNext()) { SchedulerListener l = itr.next(); l.schedulerStarting(); } }
this.resources.getJobStore().schedulerStarted(); 跟进:
// JobStoreSupport public void schedulerStarted() throws SchedulerException { if (isClustered()) { clusterManagementThread = new ClusterManager(); if(initializersLoader != null) clusterManagementThread.setContextClassLoader(initializersLoader); clusterManagementThread.initialize(); } else { try { recoverJobs(); } catch (SchedulerException se) { throw new SchedulerConfigException( "Failure occured during job recovery.", se); } } misfireHandler = new MisfireHandler(); if(initializersLoader != null) misfireHandler.setContextClassLoader(initializersLoader); misfireHandler.initialize(); schedulerRunning = true; getLog().debug("JobStore background threads started (as scheduler was started)."); }
startPlugins(); 跟进:
// QuartzScheduler private void startPlugins() { java.util.Iterator<SchedulerPlugin> itr = resources.getSchedulerPlugins().iterator(); while (itr.hasNext()) { SchedulerPlugin plugin = itr.next(); plugin.start(); } }
resources.getJobStore().schedulerResumed(); 跟进:
// JobStoreSupport public void schedulerResumed() { schedulerRunning = true; }
schedThread.togglePause(false); 跟进:
// QuartzSchedulerThread void togglePause(boolean pause) { synchronized (sigLock) { paused = pause; if (paused) { signalSchedulingChange(0); } else { sigLock.notifyAll(); } } }
public void signalSchedulingChange(long candidateNewNextFireTime) { synchronized(sigLock) { signaled = true; signaledNextFireTime = candidateNewNextFireTime; sigLock.notifyAll(); } }
notifySchedulerListenersStarted(); 跟进:
// QuartzScheduler public void notifySchedulerListenersStarted() { // build a list of all scheduler listeners that are to be notified... List<SchedulerListener> schedListeners = buildSchedulerListenerList(); // notify all scheduler listeners for(SchedulerListener sl: schedListeners) { try { sl.schedulerStarted(); } catch (Exception e) { getLog().error( "Error while notifying SchedulerListener of startup.", e); } } }
// BroadcastSchedulerListener public void schedulerStarted() { Iterator<SchedulerListener> itr = listeners.iterator(); while(itr.hasNext()) { SchedulerListener l = itr.next(); l.schedulerStarted(); } }
-
QuartzSchedulerThread.run();
public void run() { int acquiresFailed = 0; while (!halted.get()) { try { // check if we're supposed to pause... synchronized (sigLock) { while (paused && !halted.get()) { try { // wait until togglePause(false) is called... sigLock.wait(1000L); } catch (InterruptedException ignore) { } // reset failure counter when paused, so that we don't // wait again after unpausing acquiresFailed = 0; } if (halted.get()) { break; } } // wait a bit, if reading from job store is consistently // failing (e.g. DB is down or restarting).. if (acquiresFailed > 1) { try { long delay = computeDelayForRepeatedErrors(qsRsrcs.getJobStore(), acquiresFailed); Thread.sleep(delay); } catch (Exception ignore) { } } //获取可用线程的数量 int availThreadCount = qsRsrcs.getThreadPool().blockForAvailableThreads(); if(availThreadCount > 0) { // will always be true, due to semantics of blockForAvailableThreads... //定义触发器集合 List<OperableTrigger> triggers; long now = System.currentTimeMillis(); clearSignaledSchedulingChange(); try { //1. 从JobStore中获取将要触发的触发器集 合 triggers = qsRsrcs.getJobStore().acquireNextTriggers( now + idleWaitTime, Math.min(availThreadCount, qsRsrcs.getMaxBatchSize()), qsRsrcs.getBatchTimeWindow()); acquiresFailed = 0; if (log.isDebugEnabled()) log.debug("batch acquisition of " + (triggers == null ? 0 : triggers.size()) + " triggers"); } catch (JobPersistenceException jpe) { if (acquiresFailed == 0) { qs.notifySchedulerListenersError( "An error occurred while scanning for the next triggers to fire.", jpe); } if (acquiresFailed < Integer.MAX_VALUE) acquiresFailed++; continue; } catch (RuntimeException e) { if (acquiresFailed == 0) { getLog().error("quartzSchedulerThreadLoop: RuntimeException " +e.getMessage(), e); } if (acquiresFailed < Integer.MAX_VALUE) acquiresFailed++; continue; } //2. 触发触发器 if (triggers != null && !triggers.isEmpty()) { now = System.currentTimeMillis(); long triggerTime = triggers.get(0).getNextFireTime().getTime(); long timeUntilTrigger = triggerTime - now; //如果trigger的nextFireTime比当前时间大2ms则循环等待 while(timeUntilTrigger > 2) { synchronized (sigLock) { if (halted.get()) { break; } if (!isCandidateNewTimeEarlierWithinReason(triggerTime, false)) { try { // we could have blocked a long while // on 'synchronize', so we must recompute now = System.currentTimeMillis(); timeUntilTrigger = triggerTime - now; if(timeUntilTrigger >= 1) sigLock.wait(timeUntilTrigger); } catch (InterruptedException ignore) { } } } if(releaseIfScheduleChangedSignificantly(triggers, triggerTime)) { break; } now = System.currentTimeMillis(); timeUntilTrigger = triggerTime - now; } // this happens if releaseIfScheduleChangedSignificantly decided to release triggers if(triggers.isEmpty()) continue; // set triggers to 'executing' List<TriggerFiredResult> bndles = new ArrayList<TriggerFiredResult>(); boolean goAhead = true; synchronized(sigLock) { goAhead = !halted.get(); } if(goAhead) { try { List<TriggerFiredResult> res = qsRsrcs.getJobStore().triggersFired(triggers); if(res != null) bndles = res; } catch (SchedulerException se) { qs.notifySchedulerListenersError( "An error occurred while firing triggers '" + triggers + "'", se); //QTZ-179 : a problem occurred interacting with the triggers from the db //we release them and loop again for (int i = 0; i < triggers.size(); i++) { qsRsrcs.getJobStore().releaseAcquiredTrigger(triggers.get(i)); } continue; } } //执行Job for (int i = 0; i < bndles.size(); i++) { TriggerFiredResult result = bndles.get(i); TriggerFiredBundle bndle = result.getTriggerFiredBundle(); Exception exception = result.getException(); if (exception instanceof RuntimeException) { getLog().error("RuntimeException while firing trigger " + triggers.get(i), exception); qsRsrcs.getJobStore().releaseAcquiredTrigger(triggers.get(i)); continue; } // it's possible to get 'null' if the triggers was paused, // blocked, or other similar occurrences that prevent it being // fired at this time... or if the scheduler was shutdown (halted) if (bndle == null) { qsRsrcs.getJobStore().releaseAcquiredTrigger(triggers.get(i)); continue; } JobRunShell shell = null; try { shell = qsRsrcs.getJobRunShellFactory().createJobRunShell(bndle); shell.initialize(qs); } catch (SchedulerException se) { qsRsrcs.getJobStore().triggeredJobComplete(triggers.get(i), bndle.getJobDetail(), CompletedExecutionInstruction.SET_ALL_JOB_TRIGGERS_ERROR); continue; } if (qsRsrcs.getThreadPool().runInThread(shell) == false) { // this case should never happen, as it is indicative of the // scheduler being shutdown or a bug in the thread pool or // a thread pool being used concurrently - which the docs // say not to do... getLog().error("ThreadPool.runInThread() return false!"); qsRsrcs.getJobStore().triggeredJobComplete(triggers.get(i), bndle.getJobDetail(), CompletedExecutionInstruction.SET_ALL_JOB_TRIGGERS_ERROR); } } continue; // while (!halted) } } else { // if(availThreadCount > 0) // should never happen, if threadPool.blockForAvailableThreads() follows contract continue; // while (!halted) } long now = System.currentTimeMillis(); long waitTime = now + getRandomizedIdleWaitTime(); long timeUntilContinue = waitTime - now; synchronized(sigLock) { try { if(!halted.get()) { // QTZ-336 A job might have been completed in the mean time and we might have // missed the scheduled changed signal by not waiting for the notify() yet // Check that before waiting for too long in case this very job needs to be // scheduled very soon if (!isScheduleChanged()) { sigLock.wait(timeUntilContinue); } } } catch (InterruptedException ignore) { } } } catch(RuntimeException re) { getLog().error("Runtime error occurred in main trigger firing loop.", re); } } // while (!halted) // drop references to scheduler stuff to aid garbage collection... qs = null; qsRsrcs = null; }
获取触发器:triggers = qsRsrcs.getJobStore().acquireNextTriggers(now + idleWaitTime, Math.min(availThreadCount, qsRsrcs.getMaxBatchSize()), qsRsrcs.getBatchTimeWindow()) ;
//JobStoreSupport protected List<OperableTrigger> acquireNextTrigger(Connection conn, long noLaterThan, int maxCount, long timeWindow) throws JobPersistenceException { if (timeWindow < 0) { throw new IllegalArgumentException(); } List<OperableTrigger> acquiredTriggers = new ArrayList<OperableTrigger>(); Set<JobKey> acquiredJobKeysForNoConcurrentExec = new HashSet<JobKey>(); final int MAX_DO_LOOP_RETRY = 3; int currentLoopCount = 0; do { currentLoopCount ++; try { List<TriggerKey> keys = getDelegate().selectTriggerToAcquire(conn, noLaterThan + timeWindow, getMisfireTime(), maxCount); // No trigger is ready to fire yet. if (keys == null || keys.size() == 0) return acquiredTriggers; long batchEnd = noLaterThan; for(TriggerKey triggerKey: keys) { // If our trigger is no longer available, try a new one. OperableTrigger nextTrigger = retrieveTrigger(conn, triggerKey); if(nextTrigger == null) { continue; // next trigger } // If trigger's job is set as @DisallowConcurrentExecution, and it has already been added to result, then // put it back into the timeTriggers set and continue to search for next trigger. JobKey jobKey = nextTrigger.getJobKey(); JobDetail job; try { job = retrieveJob(conn, jobKey); } catch (JobPersistenceException jpe) { try { getLog().error("Error retrieving job, setting trigger state to ERROR.", jpe); getDelegate().updateTriggerState(conn, triggerKey, STATE_ERROR); } catch (SQLException sqle) { getLog().error("Unable to set trigger state to ERROR.", sqle); } continue; } if (job.isConcurrentExectionDisallowed()) { if (acquiredJobKeysForNoConcurrentExec.contains(jobKey)) { continue; // next trigger } else { acquiredJobKeysForNoConcurrentExec.add(jobKey); } } Date nextFireTime = nextTrigger.getNextFireTime(); // A trigger should not return NULL on nextFireTime when fetched from DB. // But for whatever reason if we do have this (BAD trigger implementation or // data?), we then should log a warning and continue to next trigger. // User would need to manually fix these triggers from DB as they will not // able to be clean up by Quartz since we are not returning it to be processed. if (nextFireTime == null) { log.warn("Trigger {} returned null on nextFireTime and yet still exists in DB!", nextTrigger.getKey()); continue; } if (nextFireTime.getTime() > batchEnd) { break; } // We now have a acquired trigger, let's add to return list. // If our trigger was no longer in the expected state, try a new one. int rowsUpdated = getDelegate().updateTriggerStateFromOtherState(conn, triggerKey, STATE_ACQUIRED, STATE_WAITING); if (rowsUpdated <= 0) { continue; // next trigger } nextTrigger.setFireInstanceId(getFiredTriggerRecordId()); //触发信息保存到fired_triggers表 getDelegate().insertFiredTrigger(conn, nextTrigger, STATE_ACQUIRED, null); if(acquiredTriggers.isEmpty()) { batchEnd = Math.max(nextFireTime.getTime(), System.currentTimeMillis()) + timeWindow; } acquiredTriggers.add(nextTrigger); } // if we didn't end up with any trigger to fire from that first // batch, try again for another batch. We allow with a max retry count. if(acquiredTriggers.size() == 0 && currentLoopCount < MAX_DO_LOOP_RETRY) { continue; } // We are done with the while loop. break; } catch (Exception e) { throw new JobPersistenceException( "Couldn't acquire next trigger: " + e.getMessage(), e); } } while (true); // Return the acquired trigger list return acquiredTriggers; }
获取qrtz_triggers表中TRIGGER_STATE是WAITING,且NEXT_FIRE_TIME在参数区间内的触发器: List<TriggerKey> keys = getDelegate().selectTriggerToAcquire(conn, noLaterThan + timeWindow, getMisfireTime(), maxCount);
//StdJDBCDelegate public List<TriggerKey> selectTriggerToAcquire(Connection conn, long noLaterThan, long noEarlierThan, int maxCount) throws SQLException { PreparedStatement ps = null; ResultSet rs = null; List<TriggerKey> nextTriggers = new LinkedList<TriggerKey>(); try { ps = conn.prepareStatement(rtp(SELECT_NEXT_TRIGGER_TO_ACQUIRE)); // Set max rows to retrieve if (maxCount < 1) maxCount = 1; // we want at least one trigger back. ps.setMaxRows(maxCount); // Try to give jdbc driver a hint to hopefully not pull over more than the few rows we actually need. // Note: in some jdbc drivers, such as MySQL, you must set maxRows before fetchSize, or you get exception! ps.setFetchSize(maxCount); ps.setString(1, STATE_WAITING); ps.setBigDecimal(2, new BigDecimal(String.valueOf(noLaterThan))); ps.setBigDecimal(3, new BigDecimal(String.valueOf(noEarlierThan))); rs = ps.executeQuery(); while (rs.next() && nextTriggers.size() < maxCount) { nextTriggers.add(triggerKey( rs.getString(COL_TRIGGER_NAME), rs.getString(COL_TRIGGER_GROUP))); } return nextTriggers; } finally { closeResultSet(rs); closeStatement(ps); } }
SELECT_NEXT_TRIGGER_TO_ACQUIRE sql:
String SELECT_NEXT_TRIGGER_TO_ACQUIRE = "SELECT " + COL_TRIGGER_NAME + ", " + COL_TRIGGER_GROUP + ", " + COL_NEXT_FIRE_TIME + ", " + COL_PRIORITY + " FROM " + TABLE_PREFIX_SUBST + TABLE_TRIGGERS + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + COL_TRIGGER_STATE + " = ? AND " + COL_NEXT_FIRE_TIME + " <= ? " + "AND (" + COL_MISFIRE_INSTRUCTION + " = -1 OR (" +COL_MISFIRE_INSTRUCTION+ " != -1 AND "+ COL_NEXT_FIRE_TIME + " >= ?)) " + "ORDER BY "+ COL_NEXT_FIRE_TIME + " ASC, " + COL_PRIORITY + " DESC";
触发触发器:
触发方法:qsRsrcs.getJobStore().triggersFired(triggers);
//JobStoreSupport public List<TriggerFiredResult> triggersFired(final List<OperableTrigger> triggers) throws JobPersistenceException { return executeInNonManagedTXLock(LOCK_TRIGGER_ACCESS, new TransactionCallback<List<TriggerFiredResult>>() { public List<TriggerFiredResult> execute(Connection conn) throws JobPersistenceException { List<TriggerFiredResult> results = new ArrayList<TriggerFiredResult>(); TriggerFiredResult result; for (OperableTrigger trigger : triggers) { try { TriggerFiredBundle bundle = triggerFired(conn, trigger); result = new TriggerFiredResult(bundle); } catch (JobPersistenceException jpe) { result = new TriggerFiredResult(jpe); } catch(RuntimeException re) { result = new TriggerFiredResult(re); } results.add(result); } return results; } }, new TransactionValidator<List<TriggerFiredResult>>() { @Override public Boolean validate(Connection conn, List<TriggerFiredResult> result) throws JobPersistenceException { try { List<FiredTriggerRecord> acquired = getDelegate().selectInstancesFiredTriggerRecords(conn, getInstanceId()); Set<String> executingTriggers = new HashSet<String>(); for (FiredTriggerRecord ft : acquired) { if (STATE_EXECUTING.equals(ft.getFireInstanceState())) { executingTriggers.add(ft.getFireInstanceId()); } } for (TriggerFiredResult tr : result) { if (tr.getTriggerFiredBundle() != null && executingTriggers.contains(tr.getTriggerFiredBundle().getTrigger().getFireInstanceId())) { return true; } } return false; } catch (SQLException e) { throw new JobPersistenceException("error validating trigger acquisition", e); } } }); }
triggerFired(conn, trigger);
//JobStoreSupport protected TriggerFiredBundle triggerFired(Connection conn, OperableTrigger trigger) throws JobPersistenceException { JobDetail job; Calendar cal = null; // Make sure trigger wasn't deleted, paused, or completed... try { // if trigger was deleted, state will be STATE_DELETED String state = getDelegate().selectTriggerState(conn, trigger.getKey()); if (!state.equals(STATE_ACQUIRED)) { return null; } } catch (SQLException e) { throw new JobPersistenceException("Couldn't select trigger state: " + e.getMessage(), e); } try { //获取到触发器对应的Job实例 job = retrieveJob(conn, trigger.getJobKey()); if (job == null) { return null; } } catch (JobPersistenceException jpe) { try { getLog().error("Error retrieving job, setting trigger state to ERROR.", jpe); getDelegate().updateTriggerState(conn, trigger.getKey(), STATE_ERROR); } catch (SQLException sqle) { getLog().error("Unable to set trigger state to ERROR.", sqle); } throw jpe; } if (trigger.getCalendarName() != null) { cal = retrieveCalendar(conn, trigger.getCalendarName()); if (cal == null) { return null; } } try { // 更新qrtz_fired_triggers表STATE为EXECUTING getDelegate().updateFiredTrigger(conn, trigger, STATE_EXECUTING, job); } catch (SQLException e) { throw new JobPersistenceException("Couldn't insert fired trigger: " + e.getMessage(), e); } Date prevFireTime = trigger.getPreviousFireTime(); // call triggered - to update the trigger's next-fire-time state... trigger.triggered(cal); String state = STATE_WAITING; boolean force = true; //判断是否允许job并发执行,如果不允许,则把当前QRTZ_TRIGGERS表中TRIGGER_STATE的值为WAITING,ACQUIRED,PAUSED的trigger的该列的值改为BLOCKED if (job.isConcurrentExectionDisallowed()) { state = STATE_BLOCKED; force = false; try { getDelegate().updateTriggerStatesForJobFromOtherState(conn, job.getKey(), STATE_BLOCKED, STATE_WAITING); getDelegate().updateTriggerStatesForJobFromOtherState(conn, job.getKey(), STATE_BLOCKED, STATE_ACQUIRED); getDelegate().updateTriggerStatesForJobFromOtherState(conn, job.getKey(), STATE_PAUSED_BLOCKED, STATE_PAUSED); } catch (SQLException e) { throw new JobPersistenceException( "Couldn't update states of blocked triggers: " + e.getMessage(), e); } } //nextFireTime为null,说明是最后一次触发,将状态设置为COMPLETE if (trigger.getNextFireTime() == null) { state = STATE_COMPLETE; force = true; } //更新 storeTrigger(conn, trigger, job, true, state, force, false); job.getJobDataMap().clearDirtyFlag(); return new TriggerFiredBundle(job, trigger, cal, trigger.getKey().getGroup() .equals(Scheduler.DEFAULT_RECOVERY_GROUP), new Date(), trigger .getPreviousFireTime(), prevFireTime, trigger.getNextFireTime()); }
执行Job:
ThreadPool.runInThread()-->WorkerThread.run()-->JobRunShell.run()
ThreadPool.runInThread() :
//SimpleThreadPool public boolean runInThread(Runnable runnable) { if (runnable == null) { return false; } synchronized (nextRunnableLock) { handoffPending = true; // Wait until a worker thread is available while ((availWorkers.size() < 1) && !isShutdown) { try { nextRunnableLock.wait(500); } catch (InterruptedException ignore) { } } if (!isShutdown) { WorkerThread wt = (WorkerThread)availWorkers.removeFirst(); busyWorkers.add(wt); wt.run(runnable); } else { // If the thread pool is going down, execute the Runnable // within a new additional worker thread (no thread from the pool). WorkerThread wt = new WorkerThread(this, threadGroup, "WorkerThread-LastJob", prio, isMakeThreadsDaemons(), runnable); busyWorkers.add(wt); workers.add(wt); wt.start(); } nextRunnableLock.notifyAll(); handoffPending = false; } return true; }
WorkerThread.run():
//WorkerThread try { synchronized(lock) { while (runnable == null && run.get()) { lock.wait(500); } if (runnable != null) { ran = true; runnable.run(); } } } catch (InterruptedException unblock) { // do nothing (loop will terminate if shutdown() was called try { getLog().error("Worker thread was interrupt()'ed.", unblock); } catch(Exception e) { // ignore to help with a tomcat glitch } }
JobRunShell.run():
//JobRunShell try { log.debug("Calling execute on job " + jobDetail.getKey()); job.execute(jec); endTime = System.currentTimeMillis(); } catch (JobExecutionException jee) { endTime = System.currentTimeMillis(); jobExEx = jee; getLog().info("Job " + jobDetail.getKey() + " threw a JobExecutionException: ", jobExEx); }
job.execute(jec); 调用MyJob的execute()方法
QuartzSchedulerThread.run()逻辑