Quartz使用问题记录

概述

本文记录历史遗留项目在生产环境中使用Quartz时遇到的问题,有些问题并未解决,请知悉。

背景:项目虽然立项时间并不早(2018年),但是依然没有使用分布式的任务调度系统,如xxl-job,elastic-job等开源产品。而是使用quartz这个算是最古老的工具,Quartz虽然可以实现分布式调度,并依赖于数据库表实现的。

所谓的分布式调度,即在应用多节点分布式部署的情况下,一个任务不会重复在多个节点上面跑。

问题

配置文件

quartz.properties配置文件:

org.quartz.scheduler.instanceId=AUTO
org.quartz.scheduler.makeSchedulerThreadDaemon=true
org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.makeThreadsDaemons=true
org.quartz.threadPool.threadCount:20
org.quartz.threadPool.threadPriority:5
org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.tablePrefix=QRTZ_
org.quartz.jobStore.isClustered=true
org.quartz.jobStore.misfireThreshold=25000

注意上面的isClustered配置项。

新旧两个集群同时共存

项目最初是用虚拟机部署方式,即Jenkins构建,然后通过脚本替换tomcat下面的war包。现考虑接入公司的容器云部署平台,接入迁移的过程中,使用的数据库还是同一个。故而存在新旧集群同时存在的问题。

tags.HOST_IP=11.55.44.66,新的服务器集群其中一个节点,记录的报错信息:

[SchedulerFactory_QuartzSchedulerThread] ERROR o.s.s.quartz.LocalDataSourceJobStore - Error retrieving job, setting trigger state to ERROR.
org.quartz.JobPersistenceException: Couldn't retrieve job because a required class was not found: com.aaa.channelcore.business.job.strategyjob.AddAdsetToGuoJi
	at org.quartz.impl.jdbcjobstore.JobStoreSupport.retrieveJob(JobStoreSupport.java:1388)
	at org.quartz.impl.jdbcjobstore.JobStoreSupport.acquireNextTrigger(JobStoreSupport.java:2818)
	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:3803)
	at org.quartz.impl.jdbcjobstore.JobStoreSupport.acquireNextTriggers(JobStoreSupport.java:2756)
	at org.quartz.core.QuartzSchedulerThread.run(QuartzSchedulerThread.java:272)
Caused by: java.lang.ClassNotFoundException: com.aaa.channelcore.business.job.strategyjob.AddAdsetToGuoJi
	at java.net.URLClassLoader.findClass(URLClassLoader.java:382)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
	at org.springframework.boot.loader.LaunchedURLClassLoader.loadClass(LaunchedURLClassLoader.java:94)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
	at org.springframework.scheduling.quartz.ResourceLoaderClassLoadHelper.loadClass(ResourceLoaderClassLoadHelper.java:76)
	at org.springframework.scheduling.quartz.ResourceLoaderClassLoadHelper.loadClass(ResourceLoaderClassLoadHelper.java:81)
	at org.quartz.impl.jdbcjobstore.StdJDBCDelegate.selectJobDetail(StdJDBCDelegate.java:852)
	at org.quartz.impl.jdbcjobstore.JobStoreSupport.retrieveJob(JobStoreSupport.java:1385)

上面记录的是找不到job执行时需要依赖的class,下面的报错则是根据class来存在job信息失败(同名的job已存在):

ERROR o.s.boot.SpringApplication - Application startup failed
java.lang.IllegalStateException: Failed to execute ApplicationRunner
	at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:770)
	at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:757)
	at org.springframework.boot.SpringApplication.afterRefresh(SpringApplication.java:747)
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:315)
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1162)
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1151)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
Caused by: org.quartz.ObjectAlreadyExistsException: Unable to store Job : 'facebook.facebook_14', because one already exists with this identification.
	at org.quartz.impl.jdbcjobstore.JobStoreSupport.storeJob(JobStoreSupport.java:1108)
	at org.quartz.impl.jdbcjobstore.JobStoreSupport$2.executeVoid(JobStoreSupport.java:1062)
	at org.quartz.impl.jdbcjobstore.JobStoreSupport$VoidTransactionCallback.execute(JobStoreSupport.java:3719)
	at org.quartz.impl.jdbcjobstore.JobStoreSupport$VoidTransactionCallback.execute(JobStoreSupport.java:3717)
	at org.quartz.impl.jdbcjobstore.JobStoreCMT.executeInLock(JobStoreCMT.java:245)
	at org.quartz.impl.jdbcjobstore.JobStoreSupport.storeJobAndTrigger(JobStoreSupport.java:1058)
	at org.quartz.core.QuartzScheduler.scheduleJob(QuartzScheduler.java:886)
	at org.quartz.impl.StdScheduler.scheduleJob(StdScheduler.java:249)
	at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:767)

tags.HOST_IP=111.222.222.111服务器节点记录的报错信息,旧的站点日志:

[SchedulerFactory_QuartzSchedulerThread] ERROR o.s.s.quartz.LocalDataSourceJobStore - Error retrieving job, setting trigger state to ERROR.
org.quartz.JobPersistenceException: Couldn't retrieve job because a required class was not found: com.aaa.cbd.platform.job.google.getAccountSpendDataJob
	at org.quartz.impl.jdbcjobstore.JobStoreSupport.retrieveJob(JobStoreSupport.java:1388)
	at org.quartz.impl.jdbcjobstore.JobStoreSupport.acquireNextTrigger(JobStoreSupport.java:2818)
	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:3803)
	at org.quartz.impl.jdbcjobstore.JobStoreSupport.acquireNextTriggers(JobStoreSupport.java:2756)
	at org.quartz.core.QuartzSchedulerThread.run(QuartzSchedulerThread.java:272)
Caused by: java.lang.ClassNotFoundException: com.ppdai.cbd.platform.job.google.getAccountSpendDataJob
	at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1282)
	at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1116)
	at java.lang.Class.forName0(Native Method)
	at java.lang.Class.forName(Class.java:348)
	at org.springframework.util.ClassUtils.forName(ClassUtils.java:275)
	at org.springframework.scheduling.quartz.ResourceLoaderClassLoadHelper.loadClass(ResourceLoaderClassLoadHelper.java:81)
	at org.springframework.scheduling.quartz.ResourceLoaderClassLoadHelper.loadClass(ResourceLoaderClassLoadHelper.java:86)
	at org.quartz.impl.jdbcjobstore.StdJDBCDelegate.selectJobDetail(StdJDBCDelegate.java:852)
	at org.quartz.impl.jdbcjobstore.JobStoreSupport.retrieveJob(JobStoreSupport.java:1385)

未找到
Caused by: org.quartz.ObjectAlreadyExistsException: Unable to store Job : 'facebook.facebook_14', because one already exists with this identification.

java.lang.InterruptedException: null

java.lang.InterruptedException: null
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.reportInterruptAfterWait(AbstractQueuedSynchronizer.java:2014)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:2088)
	at java.util.concurrent.ThreadPoolExecutor.awaitTermination(ThreadPoolExecutor.java:1475)
	at org.eclipse.jetty.util.thread.ExecutorThreadPool.join(ExecutorThreadPool.java:182)
	at org.eclipse.jetty.server.Server.join(Server.java:617)
	at com.aaa.job.core.rpc.netcom.jetty.server.JettyServer$1.run(JettyServer.java:55)
	at java.lang.Thread.run(Thread.java:748)
2021-08-26 14:02:02,980 [QuartzScheduler_SchedulerFactory-PPC-02020001181629957363873_ClusterManager] WARN  o.s.s.quartz.LocalDataSourceJobStore- This scheduler instance (PPC-02020001181629957363873) is still active but was recovered by another instance in the cluster.  This may cause inconsistent behavior.

job class

至于把旧版集群下线,新版应用发布,启动时,quartz会自动清空数据表,然后重新insert数据,主要是记录着class和job的对应关系那张表。

问题:通过什么字段来标志旧的集群依然在线呢???

本地测试Job

在本地以及测试环境下,需要把下面的配置改为false:org.quartz.jobStore.isClustered=false,否则会出现的一个情况是:开发机器和测试机器组成一个集群,随机选择一个节点来执行任务,如果想在本地单元测试断点调试应用时,会发现进不到任务执行逻辑内。

SchedulerException: Job threw an unhandled exception

报错日志:

[schedulerFactoryBean_Worker-78] ERROR org.quartz.core.ErrorLogger - Job (DEFAULT.1568 threw an exception.
org.quartz.SchedulerException: Job threw an unhandled exception.
	at org.quartz.core.JobRunShell.run(JobRunShell.java:213)
	at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:573)
Caused by: java.lang.NullPointerException: null

qrtz_cron_triggers表里面有trigger_name='1568'这条数据:
在这里插入图片描述
然后1568对应到的业务表,那条数据已经被逻辑删除:
在这里插入图片描述

Couldn’t acquire next trigger: Deadlock found when trying to get lock; try restarting transaction

报错日志:

[schedulerFactoryBean_QuartzSchedulerThread] ERROR org.quartz.core.ErrorLogger - An error occurred while scanning for the next triggers to fire.
org.quartz.JobPersistenceException: Couldn't acquire next trigger: Deadlock found when trying to get lock; try restarting transaction
	at org.quartz.impl.jdbcjobstore.JobStoreSupport.acquireNextTrigger(JobStoreSupport.java:2864)
	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:3803)
	at org.quartz.impl.jdbcjobstore.JobStoreSupport.acquireNextTriggers(JobStoreSupport.java:2756)
	at org.quartz.core.QuartzSchedulerThread.run(QuartzSchedulerThread.java:272)
Caused by: com.mysql.cj.jdbc.exceptions.MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction
	at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:123)
	at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:97)
	at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:122)
	at com.mysql.cj.jdbc.ClientPreparedStatement.executeInternal(ClientPreparedStatement.java:953)
	at com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdateInternal(ClientPreparedStatement.java:1092)
	at com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdateInternal(ClientPreparedStatement.java:1040)
	at com.mysql.cj.jdbc.ClientPreparedStatement.executeLargeUpdate(ClientPreparedStatement.java:1347)
	at com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdate(ClientPreparedStatement.java:1025)
	at com.alibaba.druid.filter.FilterChainImpl.preparedStatement_executeUpdate(FilterChainImpl.java:2723)
	at com.alibaba.druid.filter.FilterAdapter.preparedStatement_executeUpdate(FilterAdapter.java:1069)
	at com.alibaba.druid.filter.FilterEventAdapter.preparedStatement_executeUpdate(FilterEventAdapter.java:491)
	at com.alibaba.druid.filter.FilterChainImpl.preparedStatement_executeUpdate(FilterChainImpl.java:2721)
	at com.alibaba.druid.filter.FilterAdapter.preparedStatement_executeUpdate(FilterAdapter.java:1069)
	at com.alibaba.druid.filter.FilterEventAdapter.preparedStatement_executeUpdate(FilterEventAdapter.java:491)
	at com.alibaba.druid.filter.FilterChainImpl.preparedStatement_executeUpdate(FilterChainImpl.java:2721)
	at com.alibaba.druid.proxy.jdbc.PreparedStatementProxyImpl.executeUpdate(PreparedStatementProxyImpl.java:158)
	at com.alibaba.druid.pool.DruidPooledPreparedStatement.executeUpdate(DruidPooledPreparedStatement.java:253)
	at org.quartz.impl.jdbcjobstore.StdJDBCDelegate.updateTriggerStateFromOtherState(StdJDBCDelegate.java:1439)
	at org.quartz.impl.jdbcjobstore.JobStoreSupport.acquireNextTrigger(JobStoreSupport.java:2842)
	... 5 common frames omitted

任务不执行

任务配置的执行时间为每天9点40分:
在这里插入图片描述
在这里插入图片描述
然后发现任务未执行,执行日志没有12号到14号的数据。

用户反馈过不止一次这样的生产事故。怀疑是发生上面的死锁问题。

SQLSyntaxErrorException: Table ‘shit.QRTZ_LOCKS’ doesn’t exist

应用启动正常,但是控制台在疯狂打印如下报错信息:

ERROR sql.Statement: {conn-10001, pstmt-20003} execute error. SELECT * FROM QRTZ_LOCKS WHERE SCHED_NAME = 'schedulerFactoryBean' AND LOCK_NAME = ? FOR UPDATE
java.sql.SQLSyntaxErrorException: Table 'shit.QRTZ_LOCKS' doesn't exist

配置文件:org.quartz.jobStore.tablePrefix = QRTZ_。此配置文件只有一份。

原理就在于MySQL有个配置属性:lower_case_table_names
show variables like '%lower_case_table_names%';

Windows上安装MySQL,默认是1,代表忽略大小写
Linux上安装MySQL,默认是0,代表不忽略大小写

解决方法:表重命名。借助于IDEA强大的快捷键功能,选中表,Shift + F6 重命名,Ctrl + Shift + U大小写转换。

参考

Quartz里job不执行
quartz定时任务不执行
https://www.jianshu.com/p/2ae101916337

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Quartz是功能强大的开源作业调度库,几乎可以集成到任何Java应用程序中-从最小的独立应用程序到最大的电子商务系统。Quartz可用于创建简单或复杂的计划,以执行数以万计,数以万计的工作。任务定义为标准Java组件的作业,它们实际上可以执行您可以对其执行的任何编程操作。Quartz Scheduler包含许多企业级功能,例如对JTA事务和集群的支持。 Quartz是免费使用的,并根据Apache 2.0许可获得许可。 Quartz作业调度的示例用法: 1、推动流程工作流程:最初下达新订单时,安排一个Job在正好2个小时内触发,这将检查该订单的状态,并在尚未收到该订单的订单确认消息时触发警告通知,并将订单状态更改为“等待干预”。 2、系统维护:计划一个工作,以便在每个工作日(除节假日以外的所有工作日)晚上11:30将数据库的内容转储到XML文件中。 3、在应用程序内提供提醒服务。 Quartz特征: 一、运行环境 1、Quartz可以嵌入另一个独立应用程序中运行 2、Quartz可以在应用服务器(或servlet容器)中实例化,并参与XA事务 3、Quartz可以作为独立程序运行(在其自己的Java虚拟机中),可以通过RMI使用 4、Quartz可以实例化为独立程序的集群(具有负载平衡和故障转移功能),以执行作业 二、作业调度 计划在给定触发器发生时运行作业。几乎可以使用以下指令的任意组合来创建触发器: 1、在一天中的特定时间(以毫秒为单位) 2、在一周的某些日子 3、在每月的某些天 4、在一年中的某些日子 5、不在注册日历中列出的某些日期(例如工作日) 6、重复特定的次数 7、重复直到特定的时间/日期 8、无限重复 9、延迟间隔重复 作业由其创建者命名,也可以分为命名组。还可以给触发器指定名称并将其分组,以便在调度程序中轻松组织触发器。作业可以一次添加到调度程序,但可以通过多个触发器注册。在企业Java环境中,乔布斯可以将其工作作为分布式(XA)事务的一部分进行。 三、工作执行 1、Jobs可以是实现简单Job接口的任何Java类,为Jobs可以执行的工作留下无限的可能性。 2、作业类实例可以由Quartz或由您的应用程序的框架实例化。 3、发生触发器时,调度程序会通知零个或多个实现JobListener和TriggerListener接口的Java对象(侦听器可以是简单的Java对象,EJB或JMS发布者,等等)。作业执行后,也会通知这些侦听器。 4、作业完成后,它们将返回JobCompletionCode,以通知调度程序成功或失败。JobCompletionCode还可以根据成功/失败代码指示调度程序应采取的任何操作,例如立即重新执行Job。 四、工作持久性 1、Quartz的设计包括一个JobStore接口,可以实现该接口以提供用于存储作业的各种机制。 2、通过使用随附的JDBCJobStore,所有配置为“非易失性”的作业和触发器都将通过JDBC存储在关系数据库中。 3、通过使用随附的RAMJobStore,所有作业和触发器都存储在RAM中,因此不会在程序执行之间持久存在-但这具有不需要外部数据库的优点。 五、交易次数 1、Quartz可以通过使用JobStoreCMT(JDBCJobStore的子类)来参与JTA事务。 2、Quartz可以在Job执行过程中管理JTA事务(开始并提交它们),以便Job所执行的工作自动在JTA事务中进行。 六、聚类 1、故障转移。 2、负载均衡。 3、Quartz的内置集群功能依赖于通过JDBCJobStore进行数据库持久化(如上所述)。 4、Quartz的Terracotta扩展无需群集数据库即可提供群集功能。 七、听众和插件: 1、应用程序可以通过实现一个或多个侦听器接口来捕获调度事件,以监视或控制作业/触发器的行为。 2、可以使用Plug-In机制为Quartz添加功能,例如保留作业执行的历史记录,或从文件加载作业和触发器定义。 3、Quartz附带了许多“工厂内置”的插件和监听器。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

johnny233

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值