重新启动quartz调度程序而不出错

8

背景

我正在尝试使用jdbc在集群模式下使用石英调度器。

问题

在开始使用jdbc集群模式之前,我只是测试了RAM存储中的调度程序。那个没有问题,我能够重新启动调度程序(主类)而不出现任何错误。现在我的问题是,当我停止执行(ctrl+c),然后重新启动它时,我总是收到错误消息:

org.quartz.ObjectAlreadyExistsException: Unable to store Job : 'MyTestJob', because one already exists with this identification.

我不明白这里发生了什么。Quartz是否不支持重新启动调度程序?我的意思是,如果发生崩溃并在恢复后重新启动调度程序,会发生什么?难道唯一的选择就是从Quartz数据库中删除作业吗?也许我错过了其他方法或东西。我不太喜欢使用不能处理重新启动的库。
另一个奇怪的事情是,当切换到JDBC时,我的作业不再被触发,我只在数据库中看到WAITING状态。这可能是什么原因?作业(cron-schedule)在RAM模式下没有问题。
我对这个简单任务的文档水平和遇到的问题有些惊讶,因为我多年来一直听说过Quartz调度程序,但从未开始使用过。Google建议我不是唯一遇到这个问题的人。我希望这只是我自己的问题,而且我的问题有一个简单的解决方案,否则,在2.2.x版本中尝试这个库并已经需要寻找其他东西将会非常令人失望。
以下是我的配置:
#============================================================================
# Configure Main Scheduler Properties  
#============================================================================

org.quartz.scheduler.skipUpdateCheck = true
org.quartz.scheduler.instanceName = Test-Scheduler
org.quartz.scheduler.instanceId = AUTO

#============================================================================
# Configure ThreadPool  
#============================================================================

org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount = 25
org.quartz.threadPool.threadPriority = 5

#============================================================================
# Configure JobStore  
#============================================================================

org.quartz.jobStore.misfireThreshold = 60000

org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.useProperties = true
org.quartz.jobStore.dataSource = quartzDS
org.quartz.jobStore.tablePrefix = QRTZ_

org.quartz.jobStore.isClustered = true
org.quartz.jobStore.clusterCheckinInterval = 20000

这是我的代码:
    SchedulerFactory sf = new StdSchedulerFactory();
    Scheduler scheduler = sf.getScheduler();

    scheduler.start();

                JobDetail jobDetail = newJob(job.getClass())
                    .withIdentity("test-name", "test-group")
                    .build();

                CronTrigger trigger = newTrigger()
                    .withIdentity("test-name-trigger", "test-group")
                    .withSchedule(cronSchedule("0 0/1 * * * ?"))
                    .build();

                scheduler.scheduleJob(jobDetail, trigger);

                System.out.println(trigger.getNextFireTime());

编辑

这很有趣。

1)RAM模式可行。

2)启用集群的jdbc不可行并且会默默失败 - 即使启用了日志记录。在日志输出中,我看到以下内容:

19:57:29,913  INFO StdSchedulerFactory:1184 - Using default implementation for ThreadExecutor
19:57:29,936  INFO SchedulerSignalerImpl:61 - Initialized Scheduler Signaller of type: class org.quartz.core.SchedulerSignalerImpl
19:57:29,936  INFO QuartzScheduler:240 - Quartz Scheduler v.2.2.1 created.
19:57:29,938  INFO JobStoreTX:667 - Using db table-based data access locking (synchronization).
19:57:29,940  INFO JobStoreTX:59 - JobStoreTX initialized.
19:57:29,941  INFO QuartzScheduler:305 - Scheduler meta-data: Quartz Scheduler (v2.2.1) 'Test-Scheduler' with instanceId 'Michael-PC1405447049916'
  Scheduler class: 'org.quartz.core.QuartzScheduler' - running locally.
  NOT STARTED.
  Currently in standby mode.
  Number of jobs executed: 0
  Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 25 threads.
  Using job-store 'org.quartz.impl.jdbcjobstore.JobStoreTX' - which supports persistence. and is clustered.

19:57:29,941  INFO StdSchedulerFactory:1339 - Quartz scheduler 'Test-Scheduler' initialized from default resource file in Quartz package: 'quartz.properties'
19:57:29,941  INFO StdSchedulerFactory:1343 - Quartz scheduler version: 2.2.1
19:57:29,995  INFO AbstractPoolBackedDataSource:462 - Initializing c3p0 pool... com.mchange.v2.c3p0.ComboPooledDataSource [ acquireIncrement -> 3, acquireRetryAttempts -> 30, acquireRetryDelay -> 1000, autoCommitOnClose -> false, automaticTestTable -> null, breakAfterAcquireFailure -> false, checkoutTimeout -> 0, connectionCustomizerClassName -> null, connectionTesterClassName -> com.mchange.v2.c3p0.impl.DefaultConnectionTester, dataSourceName -> 1hgeby993gf1xpdmdc44s|7ec4d0, debugUnreturnedConnectionStackTraces -> false, description -> null, driverClass -> com.mysql.jdbc.Driver, factoryClassLocation -> null, forceIgnoreUnresolvedTransactions -> false, identityToken -> 1hgeby993gf1xpdmdc44s|7ec4d0, idleConnectionTestPeriod -> 50, initialPoolSize -> 3, jdbcUrl -> jdbc:mysql://localhost:3306/scheduler, maxAdministrativeTaskTime -> 0, maxConnectionAge -> 0, maxIdleTime -> 0, maxIdleTimeExcessConnections -> 0, maxPoolSize -> 5, maxStatements -> 0, maxStatementsPerConnection -> 120, minPoolSize -> 1, numHelperThreads -> 3, numThreadsAwaitingCheckoutDefaultUser -> 0, preferredTestQuery -> SELECT 1 FROM QRTZ_JOB_DETAILS, properties -> {user=******, password=******}, propertyCycle -> 0, testConnectionOnCheckin -> true, testConnectionOnCheckout -> false, unreturnedConnectionTimeout -> 0, usesTraditionalReflectiveProxies -> false ]
19:57:30,243 DEBUG StdRowLockSemaphore:107 - Lock 'TRIGGER_ACCESS' is desired by: main
19:57:30,262 DEBUG StdRowLockSemaphore:92 - Lock 'TRIGGER_ACCESS' is being obtained: main
19:58:21,328 DEBUG StdRowLockSemaphore:141 - Lock 'TRIGGER_ACCESS' was not obtained by: main - will try again.
19:58:22,329 DEBUG StdRowLockSemaphore:92 - Lock 'TRIGGER_ACCESS' is being obtained: main
19:59:13,389 DEBUG StdRowLockSemaphore:141 - Lock 'TRIGGER_ACCESS' was not obtained by: main - will try again.
19:59:14,389 DEBUG StdRowLockSemaphore:92 - Lock 'TRIGGER_ACCESS' is being obtained: main

虽然,就在我准备再次启用集群模式的时候,我看到了异常:

Exception in thread "main" org.quartz.impl.jdbcjobstore.LockException: Failure obtaining db row lock: Lock wait timeout exceeded; try restarting transaction [See nested exception: java.sql.SQLException: Lock wait timeout exceeded; try restarting transaction]
    at org.quartz.impl.jdbcjobstore.StdRowLockSemaphore.executeSQL(StdRowLockSemaphore.java:157)
    at org.quartz.impl.jdbcjobstore.DBSemaphore.obtainLock(DBSemaphore.java:113)
    at org.quartz.impl.jdbcjobstore.JobStoreSupport.executeInNonManagedTXLock(JobStoreSupport.java:3780)
    at org.quartz.impl.jdbcjobstore.JobStoreTX.executeInLock(JobStoreTX.java:93)
    at org.quartz.impl.jdbcjobstore.JobStoreSupport.clearAllSchedulingData(JobStoreSupport.java:1956)
    at org.quartz.core.QuartzScheduler.clear(QuartzScheduler.java:1572)
    at org.quartz.impl.StdScheduler.clear(StdScheduler.java:239)
    at com.scs.core.cron.TaskRunner.main(TaskRunner.java:52)

3) 在禁用集群的jdbc模式下,也无法正常工作, 但是会收到一个异常:

  20:04:15,993 DEBUG SimpleSemaphore:132 - Lock 'TRIGGER_ACCESS' retuned by: main
  20:04:15,993 DEBUG JobStoreTX:703 - JobStore background threads started (as scheduler was started).
  20:04:15,994  INFO QuartzScheduler:575 - Scheduler Test-Scheduler_$_NON_CLUSTERED started.
  20:04:15,994 DEBUG JobStoreTX:3933 - MisfireHandler: scanning for misfires...
  20:04:16,000 DEBUG JobStoreTX:3182 - Found 0 triggers that missed their scheduled fire-time.
  20:04:16,004 DEBUG QuartzSchedulerThread:276 - batch acquisition of 0 triggers
  20:04:16,008 DEBUG SimpleSemaphore:81 - Lock 'TRIGGER_ACCESS' is desired by: main
  20:04:16,008 DEBUG SimpleSemaphore:88 - Lock 'TRIGGER_ACCESS' is being obtained: main
  20:04:16,008 DEBUG SimpleSemaphore:105 - Lock 'TRIGGER_ACCESS' given to: main
        20:04:16,052 DEBUG SimpleSemaphore:132 - Lock 'TRIGGER_ACCESS' retuned by: main
        Found job: class to.test.cron.ImportProducts
        Tue Jul 15 20:05:00 CEST 2014
        isStarted=true
        isShutdown=false
        isInStandbyMode=false
        20:04:16,058 DEBUG QuartzSchedulerThread:276 - batch acquisition of 0 triggers
        20:04:42,961 ERROR ErrorLogger:2425 - An error occurred while scanning for the next triggers to fire.
        org.quartz.JobPersistenceException: Couldn't acquire next trigger: to.test.cron.ImportProducts [See nested exception: java.lang.ClassNotFoundException: to.test.cron.ImportProducts]
            at org.quartz.impl.jdbcjobstore.JobStoreSupport.acquireNextTrigger(JobStoreSupport.java:2848)
            at org.quartz.impl.jdbcjobstore.JobStoreSupport$40.execute(JobStoreSupport.java:2759)
            at org.quartz.impl.jdbcjobstore.JobStoreSupport$40.execute(JobStoreSupport.java:2757)
            at org.quartz.impl.jdbcjobstore.JobStoreSupport.executeInNonManagedTXLock(JobStoreSupport.java:3787)
            at org.quartz.impl.jdbcjobstore.JobStoreSupport.acquireNextTriggers(JobStoreSupport.java:2756)
            at org.quartz.core.QuartzSchedulerThread.run(QuartzSchedulerThread.java:272)
        Caused by: java.lang.ClassNotFoundException: to.test.cron.ImportProducts
            at java.net.URLClassLoader$1.run(Unknown Source)
            at java.net.URLClassLoader$1.run(Unknown Source)
            at java.security.AccessController.doPrivileged(Native Method)
            at java.net.URLClassLoader.findClass(Unknown Source)
            at java.lang.ClassLoader.loadClass(Unknown Source)
            at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
            at java.lang.ClassLoader.loadClass(Unknown Source)
            at org.quartz.simpl.InitThreadContextClassLoadHelper.loadClass(InitThreadContextClassLoadHelper.java:72)
            at org.quartz.simpl.CascadingClassLoadHelper.loadClass(CascadingClassLoadHelper.java:114)
            at org.quartz.simpl.CascadingClassLoadHelper.loadClass(CascadingClassLoadHelper.java:138)
            at org.quartz.impl.jdbcjobstore.StdJDBCDelegate.selectJobDetail(StdJDBCDelegate.java:852)
            at org.quartz.impl.jdbcjobstore.JobStoreSupport.acquireNextTrigger(JobStoreSupport.java:2816)
            ... 5 more

我不太理解为什么在三种不同的模式下会出现三种完全不同的行为。如果类可以在RAM模式下找到,为什么不能在jdbc模式下找到?而在集群模式下为什么没有被记录?该类实际上是在OSGI类型的模块中。这会引起问题吗(在jdbc模式下)?是否有什么我可以做的,使得该类可以被找到,例如通过将类加载器传递给quartz等方式?
我现在很迷茫,非常感谢任何帮助。如果只能回到标准的cron作业,那将是一件遗憾的事情,特别是因为quartz有更多的优势。
提前感谢您提供的任何帮助,
迈克尔
1个回答

10

对于持久化作业存储,这是一个常见的“问题”。您的应用程序似乎尝试添加已经存在于作业存储中的作业,因为它已经被您的应用程序在过去添加过了。你有两个选择:

  1. 在尝试添加作业/触发器之前,在应用程序初始化期间清除作业存储的内容。从Quartz 2.x开始,有一个新方法Scheduler.clear()可供使用。

  2. 修改应用程序代码以处理您正在尝试添加的作业/触发器可能已经存在于作业存储中的事实。如果存在,则根据需要更新作业/触发器,或完全跳过作业/触发器。

当您考虑此Quartz行为时,实际上是有道理的,因为作业/触发器可以从应用程序外部进行修改(例如,使用Quartz远程API的外部系统)。

您还可以查看XMLSchedulingDataProcessorPlugin,它允许您将作业和触发器定义从应用程序外部化到XML文件/资源,并且可以处理作业/触发器名称冲突。这文章提供了XML文件结构的示例。

希望这可以帮助到您。


谢谢你的建议。目前看来,这似乎已经解决了我的错误(提示#1),并且可以作为一个开始。我稍后会用#2进行改进。你可能有其他问题的提示吗?当我处于RAM模式时,我的任务每分钟执行一次,但是在添加了quartz.properties和jdbc配置之后,我可以看到作业已在数据库中创建,但不再被触发。 "trigger.getNextFireTime()"也返回正确的时间。在数据库中,prev_fire_time仍为-1。 - michaeldd
我首先会仔细检查调度程序是否已经启动(即Scheduler.isStarted()返回true)。如果这不能解决问题,您应该在应用程序的日志配置中为“org.quartz”类别启用日志记录。如果这没有告诉您任何有用信息,那么您就需要调试QuartzSchedulerThread以查明问题所在。 - Jan Moravec
非常感谢您迄今为止的帮助!请查看我在描述中的编辑。 - michaeldd
1
关于3),看起来似乎Quartz无法加载作业类。Quartz API是否由与加载您的作业类相同的类加载器加载?当Quartz API由共享应用程序服务器类加载器(例如Tomcat的Common类加载器)加载时,通常会出现这些错误,该类加载器无法看到部署应用程序中捆绑的作业类(例如在WEB-INF/classes、WEB-INF/lib中)。Quartz提供了几种策略来加载类(例如作业详情)-请参见org.quartz.scheduler.classLoadHelper.class Quartz属性。 - Jan Moravec
1
没问题。我们使用自己的产品来完成这个 :-) 请查看QuartzDesk(www.quartzdesk.com)。即使是Lite(免费)版本,也可以让您按需执行定期计划之外的作业。 - Jan Moravec
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接