ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

若依框架Quartz定时任务系统源码解析与实践

若依框架Quartz定时任务系统源码解析与实践 1. 若依框架与Quartz定时任务系统深度解析若依RuoYi作为国内广泛使用的开源管理系统脚手架其内置的Quartz定时任务模块为开发者提供了开箱即用的任务调度能力。这次我们将从源码层面彻底拆解这套定时任务系统的实现机制通过图解代码的方式展示从初始化到任务执行的全链路过程。提示本文基于若依4.7.5版本和Quartz 2.3.2进行分析核心原理适用于大多数Spring整合Quartz的场景。1.1 核心架构设计图解先来看整体架构关系图示说明[若依应用] ├── Spring Scheduler (抽象层) └── Quartz Scheduler (实现层) ├── JobStore (RAMJobStore/JDBCJobStore) ├── ThreadPool (SimpleThreadPool) └── Listener体系关键设计特点通过Scheduled注解与Quartz API双轨并行采用JDBCJobStore实现任务持久化线程池配置与Spring环境隔离扩展了任务日志记录功能2. 初始化流程源码逐行解析2.1 配置加载阶段在application.yml中可以看到关键配置项quartz: job-store-type: jdbc properties: org.quartz.scheduler.instanceName: RuoYiScheduler org.quartz.threadPool.threadCount: 10对应的配置类SchedulerConfig中Bean public SchedulerFactoryBean schedulerFactoryBean(DataSource dataSource) { SchedulerFactoryBean factory new SchedulerFactoryBean(); factory.setSchedulerName(RuoYiScheduler); factory.setDataSource(dataSource); factory.setAutoStartup(true); factory.setStartupDelay(10); // 延迟10秒启动 return factory; }2.2 数据库表结构解析若依扩展了以下核心表QRTZ_JOB_DETAILS任务明细表QRTZ_TRIGGERS触发器表sys_job自定义任务扩展表关键字段job_id BIGINT 主键 job_name VARCHAR 任务名称 job_group VARCHAR 任务组别 invoke_target VARCHAR 调用目标字符串 cron_expression VARCHAR cron表达式 concurrent CHAR 是否并发执行 status CHAR 状态0暂停 1正常 create_time DATETIME 创建时间2.3 任务初始化关键路径跟踪JobInitUtil.init()方法从sys_job表加载有效任务通过ScheduleUtils.createScheduleJob()创建任务核心创建逻辑// 构建JobDetail JobDetail jobDetail JobBuilder.newJob(ScheduleJob.class) .withIdentity(jobKey) .build(); // 构建CronTrigger CronTrigger cronTrigger TriggerBuilder.newTrigger() .withIdentity(triggerKey) .withSchedule(CronScheduleBuilder.cronSchedule(job.getCronExpression())) .build(); // 注册到调度器 scheduler.scheduleJob(jobDetail, cronTrigger);3. 任务执行链路深度剖析3.1 任务执行时序图[Quartz Worker Thread] - [ScheduleJob.execute()] - [ReflectUtil.invokeMethod()] - [实际业务方法]3.2 反射调用实现细节在ScheduleJob中可以看到protected void executeInternal(JobExecutionContext context) { // 从context获取任务信息 JobDataMap jobDataMap context.getJobDetail().getJobDataMap(); String invokeTarget jobDataMap.getString(invokeTarget); // 解析目标字符串格式beanName.methodName(params) String[] split invokeTarget.split(\\.); String beanName split[0]; String methodName split[1]; // 通过Spring容器获取bean实例 Object target SpringUtils.getBean(beanName); // 反射调用 Method method target.getClass().getMethod(methodName); method.invoke(target); }3.3 并发控制机制通过DisallowConcurrentExecution注解实现DisallowConcurrentExecution public class ScheduleJob extends QuartzJobBean { // ... }配合数据库的concurrent字段实现双重控制UPDATE sys_job SET concurrent0 WHERE job_id?4. 生产环境实战经验4.1 性能优化建议线程池配置公式理想线程数 (任务平均耗时(ms) × QPS) / 1000建议在application.yml中调整org.quartz.threadPool.threadCount: 15-25 (根据服务器核心数)避免长时间任务阻塞// 在业务方法中添加超时控制 Scheduled public void processData() { Future? future executor.submit(() - { // 业务逻辑 }); try { future.get(30, TimeUnit.SECONDS); } catch (TimeoutException e) { future.cancel(true); } }4.2 常见问题排查指南现象可能原因解决方案任务不触发1. cron表达式错误2. 数据库状态不一致1. 使用在线校验工具检查cron2. 核对sys_job.status与QRTZ_TRIGGERS状态重复执行1. 集群配置问题2. 并发控制失效1. 检查quartz.properties的instanceId配置2. 确认DisallowConcurrentExecution生效日志不记录1. AOP拦截失败2. 事务配置问题1. 检查SysJobLog切面配置2. 添加Transactional(propagationREQUIRES_NEW)4.3 集群部署注意事项必须配置org.quartz.jobStore.isClusteredtrue org.quartz.jobStore.clusterCheckinInterval20000数据库连接池建议spring: datasource: hikari: max-lifetime: 120000 connection-timeout: 30000时钟同步要求# 所有节点执行 ntpdate ntp.aliyun.com5. 扩展开发指南5.1 自定义任务类型继承AbstractJob实现public class HttpJob extends AbstractJob { Override protected void doExecute(JobExecutionContext context) { String url context.getMergedJobDataMap().getString(url); RestTemplate rest new RestTemplate(); rest.getForObject(url, String.class); } }注册方式JobDetail job JobBuilder.newJob(HttpJob.class) .usingJobData(url, https://api.example.com) .build();5.2 动态任务管理API示例// 暂停任务 scheduler.pauseJob(JobKey.jobKey(jobName, jobGroup)); // 恢复任务 scheduler.resumeJob(JobKey.jobKey(jobName, jobGroup)); // 立即运行一次 scheduler.triggerJob(JobKey.jobKey(jobName, jobGroup)); // 更新cron表达式 Trigger newTrigger TriggerBuilder.newTrigger() .withIdentity(triggerKey) .withSchedule(CronScheduleBuilder.cronSchedule(0/5 * * * * ?)) .build(); scheduler.rescheduleJob(triggerKey, newTrigger);5.3 监控集成方案通过JMX暴露指标Bean public MBeanServerConnection mBeanServerConnection() { return ManagementFactory.getPlatformMBeanServer(); } Scheduled(fixedRate 5000) public void monitor() { ObjectName name new ObjectName(quartz:typeQuartzScheduler); int runningJobs (int) mBeanServer.getAttribute(name, NumberOfJobsExecuted); metrics.gauge(quartz.running.jobs, runningJobs); }我在实际使用中发现当任务执行频率超过每分钟50次时建议改用RAMJobStore模式并配合Redis实现分布式锁可以显著降低数据库压力。具体实现可以通过自定义JobStore替换默认实现public class RedisJobStore extends RAMJobStore { private RedisTemplateString, Object redisTemplate; Override public void storeJob(JobDetail jobDetail, boolean replaceExisting) { super.storeJob(jobDetail, replaceExisting); redisTemplate.opsForValue().set( quartz:job: jobDetail.getKey().toString(), jobDetail, 6, TimeUnit.HOURS ); } }
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进