Quartz 定时任务 NoSuchMethodException 排查记录:一个「幽灵实例」引发的血案

发布时间:2026/10/14 15:45:24

Quartz 定时任务 NoSuchMethodException 排查记录:一个「幽灵实例」引发的血案 Quartz 定时任务 NoSuchMethodException 排查记录一个「幽灵实例」引发的血案问题一句话定时任务运行报NoSuchMethodException: dailySend()代码和sys_job配置都正确最终定位为Quartz 集群里混入了一台跑旧代码、却一直在跳心跳的「幽灵实例」。一、问题现象定时任务执行时抛出如下异常java.lang.NoSuchMethodException:com.hnjl.inventory.core.InvWarningCronJob.dailySend()atjava.lang.Class.getMethod(Class.java:1786)atcom.hnjl.quartz.util.JobInvokeUtil.invokeMethod(JobInvokeUtil.java:60)atcom.hnjl.quartz.util.JobInvokeUtil.invokeMethod(JobInvokeUtil.java:33)atcom.hnjl.quartz.util.QuartzDisallowConcurrentExecution.doExecute(QuartzDisallowConcurrentExecution.java:19)atcom.hnjl.quartz.util.AbstractQuartzJob.execute(AbstractQuartzJob.java:43)atorg.quartz.core.JobRunShell.run(JobRunShell.java:202)...表面含义反射调用InvWarningCronJob.dailySend()时找不到这个方法。二、环境信息项目值框架RuoYi 3.8.8 二开Spring Boot 2.5 Quartz涉及类com.hnjl.inventory.core.InvWarningCronJobScheduler 名HNJLCompassScheduler集群模式isClustered true实例 ID 生成instanceId AUTOSimpleInstanceIdGenerator三、排查过程第一步确认代码里到底有没有dailySend()方法grep-rndailySendhnjl-api/src/main/java/com/hnjl/inventory/结果方法存在且是 public 无参。Component(invWarningCronJob)publicclassInvWarningCronJob{ResourceprivateIInvWarningServiceinvWarningService;publicvoiddailySend(){// ← 确实存在public、无参try{invWarningService.dailySend();}catch(Exceptione){log.error([每天指定时间] 执行异常: {},e.getMessage(),e);}}}结论代码没问题。方法名、可见性、参数列表都对得上反射调用的要求。第二步确认sys_job表的维护值结论已确认无问题线上代码已确认是新代码。第三步定位「方法是谁加的」gitlog--oneline-- hnjl-api/src/main/java/com/hnjl/inventory/core/InvWarningCronJob.javagitshow 20fa4b7ae1 -- hnjl-api/src/main/java/com/hnjl/inventory/core/InvWarningCronJob.java结果dailySend()是 commit20fa4b7ae1「通知周期改造为每天指定时间 满足即推两种模式」新增的。 -40,4 40,26 public class InvWarningCronJob { public void dailySend() { invWarningService.dailySend(); }关键线索dailySend()是「后来才加」的方法。如果运行中的某个 jar 还是加这个方法之前打的包反射自然找不到。第四步确认是「集群」并查集群里有多少实例grep-rnisClustered\|instanceId\|instanceNamehnjl-quartz/src/main/java/com/hnjl/quartz/config/ScheduleConfig.javaprop.put(org.quartz.scheduler.instanceName,HNJLCompassScheduler);prop.put(org.quartz.scheduler.instanceId,AUTO);prop.put(org.quartz.jobStore.isClustered,true);集群实例都登记在QRTZ_SCHEDULER_STATE表每个实例一行周期性更新心跳。SELECTINSTANCE_NAME,FROM_UNIXTIME(CAST(RIGHT(INSTANCE_NAME,13)ASUNSIGNED)/1000,%Y-%m-%d %H:%i:%s)ASstart_timeFROMQRTZ_SCHEDULER_STATEWHERESCHED_NAMEHNJLCompassScheduler;结果INSTANCE_NAMEstart_timeiZbp1h7way1vber0b3c1gnZ...2026-10-12 20:03:17iZbp11gpxfapiiacintzpiZ...2026-10-12 20:05:38izbp153j8cx9nm5c4a03kzz...2026-09-21 12:06:05关键线索第三台izbp153j8c...是9-21 启动、一直没重启而dailySend()是在它启动之后才加的。这台机器极可能还加载着旧 jar。第五步判断第三台到底是不是「尸体」误区启动时间早 ≠ 已死亡。启动时间只是「什么时候启动的」要判断存活必须看心跳时间。SELECTINSTANCE_NAME,CHECKIN_INTERVAL,FROM_UNIXTIME(LAST_CHECKIN_TIME/1000,%Y-%m-%d %H:%i:%s)ASlast_checkin_timeFROMQRTZ_SCHEDULER_STATEWHERESCHED_NAMEHNJLCompassSchedulerANDINSTANCE_NAMEizbp153j8cx9nm5c4a03kzz1789963565704;结果INSTANCE_NAMECHECKIN_INTERVALlast_checkin_timeizbp153j8cx9nm5c4a03kzz...150002026-10-13 09:30:59关键结论CHECKIN_INTERVAL 15000ms每 15 秒心跳一次且「今天早上」还在跳心跳 →这台不是尸体是活着的第三台实例。四、根因分析sys_job 配置 invWarningCronJob.dailySendQuartz 集群调度哪个实例抢到执行权10-12 重启的两台 / 新代码9-21 未重启的旧实例 / 旧 jardailySend 存在 / 正常执行无 dailySend 方法NoSuchMethodExceptiondailySend()是 commit20fa4b7ae1之后才存在的。集群里有3 个实例其中izbp153j8c...这台9-21 启动后从未重启加载的是旧 jar。Quartz 集群调度时只要这台旧实例抢到dailySend这个 job 的执行权反射调用就会NoSuchMethodException。表象是「找不到方法」本质是代码版本不一致集群里混入了没纳管的旧节点。五、解决方案定位旧机器INSTANCE_NAME前半段izbp153j8cx9nm5c4a03kzz即主机名阿里云 ECS 默认命名izbp...z。在阿里云 ECS 控制台按主机名搜索定位具体服务器。二选一处理旧机器不该存在→ 直接下线 / 释放实例。旧机器还要用→ 重新部署含dailySend()的新包并重启让三台代码版本一致。处理后验证等集群调度到dailySend不再报异常且QRTZ_SCHEDULER_STATE中实例心跳正常。六、常用 SQL 汇总1. 查集群所有实例含启动时间SELECTINSTANCE_NAME,FROM_UNIXTIME(CAST(RIGHT(INSTANCE_NAME,13)ASUNSIGNED)/1000,%Y-%m-%d %H:%i:%s)ASstart_timeFROMQRTZ_SCHEDULER_STATEWHERESCHED_NAMEHNJLCompassScheduler;2. 查实例心跳 存活状态SELECTINSTANCE_NAME,CHECKIN_INTERVAL,FROM_UNIXTIME(LAST_CHECKIN_TIME/1000,%Y-%m-%d %H:%i:%s)ASlast_checkin_time,ROUND(UNIX_TIMESTAMP(NOW())-LAST_CHECKIN_TIME/1000)ASseconds_since_checkinFROMQRTZ_SCHEDULER_STATEWHERESCHED_NAMEHNJLCompassScheduler;-- seconds_since_checkin 30 表示此刻还活着3. 查某个实例触发过的 job含孤儿 fired 记录SELECTTRIGGER_NAME,TRIGGER_GROUP,JOB_NAME,FROM_UNIXTIME(FIRED_TIME/1000,%Y-%m-%d %H:%i:%s)ASfired_time,STATEFROMQRTZ_FIRED_TRIGGERSWHERESCHED_NAMEHNJLCompassSchedulerANDINSTANCE_NAMEizbp153j8cx9nm5c4a03kzz1789963565704ORDERBYFIRED_TIMEDESC;4. 删除确认死亡后的残留实例DELETEFROMQRTZ_SCHEDULER_STATEWHERESCHED_NAMEHNJLCompassSchedulerANDINSTANCE_NAMEizbp153j8cx9nm5c4a03kzz1789963565704;七、知识点沉淀1.QRTZ_SCHEDULER_STATE表字段字段含义SCHED_NAMEscheduler 名INSTANCE_NAME实例名AUTO 模式 主机名 启动毫秒时间戳LAST_CHECKIN_TIME最后心跳时间毫秒CHECKIN_INTERVAL心跳间隔毫秒2. Quartz 集群的几个「坑」实例停机不会删行QRTZ_SCHEDULER_STATE记录永久残留集群靠「心跳超时」判定死亡而非删行。启动时间 ≠ 存活状态判断存活必须看LAST_CHECKIN_TIME心跳不是看启动时间。启动时间藏在实例名里instanceIdAUTO时INSTANCE_NAME 主机名 启动毫秒时间戳末尾 13 位数字反解即可得启动时间主机名以数字结尾时不准确。时间戳是毫秒LAST_CHECKIN_TIME、FIRED_TIME、SCHED_TIME都是毫秒格式化前先/ 1000。QRTZ_FIRED_TRIGGERS是临时表job 跑完会删行留下孤儿记录说明实例是「执行到一半死掉」的。3. RuoYi 反射调用任务的关键逻辑JobInvokeUtil.invokeMethod无参分支Methodmethodbean.getClass().getMethod(methodName);method.invoke(bean);NoSuchMethodException的类名是裸类名无 CGLIB 代理后缀说明bean 解析正常、分支正确纯粹是「加载到的类里没有该方法」——即运行环境跑的是旧代码。八、总结这次排查最大的教训是「代码有、配置对」不代表「运行环境有」。集群场景下NoSuchMethodException这类「本不该出现的反射错误」十有八九不是代码写错而是集群里有节点跑着旧代码。排查顺序建议先确认代码和sys_job配置正确用git log/show确认该方法「是何时加的」查QRTZ_SCHEDULER_STATE看集群实例数和各自启动时间用LAST_CHECKIN_TIME判断存活而非启动时间找出「启动时间早于方法提交时间」且「还在跳心跳」的实例——那就是罪魁祸首。文档整理时间2026-10-13
延伸阅读

更多相关文章

2026/10/14 15:45:24

MySQL 1055报错全解析:ONLY_FULL_GROUP_BY规则与修复实践

1. 问题描述与报错解析1.1 报错出现的第一现场:一次真实的统计需求几个月前,我在做某个数据报表功能时,碰到一条报错,原话是:ERROR 1055 (42000): Expression #1 of SELECT list is not in GROUP BY clause and contai…

2026/10/14 15:45:24

MySQL双主双从+MyCat读写分离高可用架构实战复盘

四年前我接手了一套业务系统的数据库改造,方案定的是MyCat中间件加双主双从MySQL,核心目标就是高可用和读写分离。当时系统长期跑在单机MySQL上——库表一百多张,读写全走同一台实例,白天高峰期主库CPU经常飘到80%以上&#xff0c…

2026/10/14 17:06:02

决策树入门:从买西瓜理解信息增益与树生长逻辑

1. 为什么一棵“树”能做决定?从菜市场买西瓜说起你有没有在菜市场挑西瓜的经历?老瓜农敲一敲、听个声儿、看个纹路、掂个分量,三秒之内就能告诉你:“这个熟了,沙瓤,甜。”而你掏出手机拍个照,发…

2026/10/14 17:06:02

ThingsBoard Gateway集成OPC-UA协议:工业数据接入与设备映射实践

简介:这是一份面向工业物联网开发者的 OPC-UA 协议集成实操文档,围绕 Thingsboard Gateway 与 OPC-UA 的对接展开,重点解决设备数据采集上云、协议配置与通信验证等问题。文档以 KEPServerEX6 模拟 OPC-UA 服务端、UaExpert 作为客户端工具&a…

2026/10/14 17:06:02

CyclicBarrier的底层实现

想了解CyclicBarrier的底层实现,你可以先去考虑2个问题。 一个是需求是什么?第二个是CyclicBarrier可以直接使用AQS来实现吗? 从这个维度出发去思考,能更好的理解CyclicBarrier的底层实现。 同时这个也是一个设计的问题,开发CyclicBarrier的…

2026/10/14 17:06:02

Qt大恒工业相机二次开发:从SDK接入到QImage显示实战

简介:一份基于Qt的大恒工业相机二次开发C示例项目,面向需要在Windows/Linux等平台快速集成大恒相机的视觉开发者和自动化工程师,解决Qt环境下相机初始化、图像采集与显示等核心问题。工程采用Qt与VS2017搭建,重写了CGXBitmap类以优…

2026/10/14 17:06:02

GEO策略咨询之后,是否支持后续效果复盘、持续迭代优化?

GEO策略咨询并不应止步于一次方案交付。生成式搜索的结果会受到内容结构、语义覆盖、知识来源和平台环境变化影响,因此更合理的方式是建立“诊断—执行—复盘—优化”的持续机制。是否支持后续复盘与迭代,应重点核对服务范围、评估指标、复盘周期和交付边…

2026/10/14 17:01:02

STM32F303基础工程避坑指南:启动文件、链接脚本与时钟配置

简介:STM32F303基础工程文件面向嵌入式开发初学者与需要快速搭建Cortex-M4开发环境的工程师,基于STM32F303芯片,提供从启动、外设驱动到应用逻辑的完整工程骨架,帮助读者跳过繁琐的底层配置,直接进入功能开发与调试。压…

2026/10/14 11:31:03

Python调用Gemini Structured Outputs实现工单路由门禁

客服工单最怕的不是模型“答错一句话”,而是它给出一段看起来合理的说明,程序却从中猜错优先级。通俗做法是:要求模型只交 JSON(JavaScript Object Notation,轻量数据格式),再让代码验证它。Gem…

2026/10/13 17:09:18

Spring Boot超市进销存系统毕设实战:从需求拆解到答辩通关

最近带的一个学生项目组里,有A同学跑来问我:选什么毕设题目最稳妥,既能让评审老师觉得工作量够,又不会在答辩时被问到语无伦次。我第一反应就是推荐基于Spring Boot的超市仓库管理系统——也就是超市进销存系统。这个题目乍一看平…

2026/10/14 2:32:55

Flutter StatefulWidget 生命周期核心解析

很多刚开始接触 Flutter 的朋友,在看完一堆“Hello World”和基础组件之后,大概率都会撞上同一堵墙:StatefulWidget 里那堆 initState、build、dispose 方法,到底什么时候被调用?为什么顺序是那样?在里面到…

2026/10/14 0:00:33

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景,也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间,确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中,这类问题直接关系到飞行安全和机…

2026/10/14 0:00:33

macOS/iOS高精度计时:mach_absolute_time原理与实战

1. 项目概述:为什么mach_absolute_time是 macOS/iOS 时间测量的“黄金标准”在 macOS 和 iOS 开发中,我见过太多人用CFAbsoluteTimeGetCurrent()或NSDate.timeIntervalSinceReferenceDate做性能打点,结果在高精度场景下被系统时钟跳变、NTP 同…

2026/10/14 0:00:33

SCORM课件演示与测试全解析:从API通信到自动化验证

简介:这是一套遵循SCORM标准的交互式课件演示与测试资源,面向E-Learning课程开发者、LMS平台测试人员及在线教学从业者,用于验证课件在不同学习管理系统中的识别、传递与跟踪能力。压缩包共28个文件,约12.07MB,以swf动…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑