Spring 整合Quartz 2实现定时任务四:细化调整及一些已知的问题

简介:

之前已经把功能基本都实现了,这里我们再来优化一下代码。

我们发现,在创建、修改、和删除定时任务时,对于quartz的操作其实是可以封装成一个简单的工具辅助类的,如创建的代码可以抽取成:

/**
 * 创建定时任务
 *
 * @param scheduler the scheduler
 * @param jobName the job name
 * @param jobGroup the job group
 * @param cronExpression the cron expression
 * @param isSync the is sync
 * @param param the param
 */
public static void createScheduleJob(Scheduler scheduler, String jobName, String jobGroup,
                                     String cronExpression, boolean isSync, Object param) {
    //同步或异步
    Class<? extends Job> jobClass = isSync ? JobSyncFactory.class : JobFactory.class;
    //构建job信息
    JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(jobName, jobGroup).build();
    //放入参数,运行时的方法可以获取
    jobDetail.getJobDataMap().put(ScheduleJobVo.JOB_PARAM_KEY, param);
    //表达式调度构建器
    CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);
    //按新的cronExpression表达式构建一个新的trigger
    CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(jobName, jobGroup)
        .withSchedule(scheduleBuilder).build();
    try {
        scheduler.scheduleJob(jobDetail, trigger);
    } catch (SchedulerException e) {
        LOG.error("创建定时任务失败", e);
        throw new ScheduleException("创建定时任务失败");
    }
}

把任务的具体信息包括Scheduler都使用参数方式传入。

看过前面文章的同学或许还记得,quartz在spring中需要声明的对象只剩下一行:

<bean id="schedulerFactoryBean" class="org.springframework.scheduling.quartz.SchedulerFactoryBean" />

既然这个schedulerFactoryBean只是在spring中声明一下,并没有做特殊的操作,在辅助的工具类中直接使用单例模式创建一个不是更好,还能少传一个参数?

你想的没错,这种方式的确更好还能解耦,但是我们来看一下SchedulerFactoryBean类的代码:

public class SchedulerFactoryBean extends SchedulerAccessor implements FactoryBean<Scheduler>, BeanNameAware, ApplicationContextAware, InitializingBean, DisposableBean, SmartLifecycle {
//......
}

它实现了spring的FactoryBean接口,也就是说它在创建时并不是简单的new而已,还夹杂了一些其它的复杂行为,所以我们也没必要特地的去怎么怎么样,还是在spring中声明一下吧。

另外,我们看它的getObject()方法:

public Scheduler getObject() {
	return this.scheduler;
}

发现它实际返回的已经是Scheduler对象,既然如此在我们类中就不必注入schedulerFactoryBean再调用getScheduler()这么麻烦了,可以直接声明Scheduler对象:
@Service
public class ScheduleJobServiceImpl implements ScheduleJobService {
    /** 调度Bean */
    @Autowired
    private Scheduler scheduler;
	//......
}

当然你注入schedulerFactoryBean也不会有错,看过spring源码的同学应该立马就能明白这是getBean("bean")和getBean("&bean")的区别了。

另外说说前面漏掉的两个地方。

一、更新任务

先前我们在更新任务时,虽然更新了定时任务的执行时间,但是并没有对参数进行更新,即使用context.getMergedJobDataMap().get(...)方法获取到的参数还是旧的。

假设我们更新了任务的时间表达式,任务已按新的时间表达式在执行,但在获取到参数后发现时间表达式还是原来的。

尝试对参数进行更新,使用如下代码:

JobDetail jobDetail = scheduler.getJobDetail(getJobKey(jobName, jobGroup));
//jobDetail = jobDetail.getJobBuilder().ofType(jobClass).build();
//更新参数 实际测试中发现无法更新
JobDataMap jobDataMap = jobDetail.getJobDataMap();
jobDataMap.put(ScheduleJobVo.JOB_PARAM_KEY, param);
jobDetail.getJobBuilder().usingJobData(jobDataMap);

发现无法更新,试过其它几个api发现都不行,没有办法,最后采用了先删除任务再进行创建的方式来迂回实现参数的更新。demo中更新任务有直接修改方式和删除修改方式,区别就在这里。

二、任务的同步和异步

同步和异步在quartz 2.2的版本中对于使用者来说区别只在于是否在job类上添加了@DisallowConcurrentExecution注解。

按时这个特点我们建立两个job的实现工厂类,在其中一个类上添加注解@DisallowConcurrentExecution,然后可以根据添加任务时的参数来确定具体使用哪个:

//同步或异步
Class<? extends Job> jobClass = isSync ? JobSyncFactory.class : JobFactory.class;
需要注意在定时任务运行时更新是没有办法改变同步和异步的。

接下来说说我在整合使用时碰到的一些已知问题。

一、更新任务时参数问题。也就是前面说的无法更新任务中传入的参数。

二、同步或异步在定时任务运行时修改是不能改变的,这个在前面也提到了。

三、在定时任务运行时修改,可能会该让任务长时间处于线程阻塞状态,即BLOCKED状态,即使你的任务中只有简单的一行System.out输出。要使它恢复也很简单,删除重建即可。

四、定时任务运行两次的问题。这个也是网上传的最多的问题,这里来着重的说一下。

网上流传引起该问题的原因目前主要有两个说法:

1 spring配置文件加载了多次,导致quartz的bean被实例化多次而导致任务多次执行。

2 tomcat的webapps目录问题。tomcat运行时加载了两次配置文件导致任务多次执行。

这两个说法在我的demo中应该并不存在,但为了验证我也尝试了下。

不使用tomcat,在main方法中用编程的方式启动spring,甚至不使用spring,直接用quartz官方给出的代码:

try {
    // Grab the Scheduler instance from the Factory 
    Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
    // and start it off
    scheduler.start();
    scheduler.shutdown();
} catch (SchedulerException se) {
    se.printStackTrace();
}

问题还是存在,这就说明不是配置文件加载的问题了,这应该是quartz本身存在的一个bug,而且这个多次运行是很有规律的,基本按如下套路走:

  • 定为5秒运行一次,一切正常,没有多次执行现象发生。
  • 定为10秒运行一次,一切正常,没有多次执行现象发生。
  • 定为29秒运行一次,运行时一次正常,一次不正常。
  • 定为59秒运行一次,运行时一次正常,一次不正常。

以上是我实测得出的,再长时间就没测了,毕竟太耗时。在有运行两次的现象时都是间隔的,即一次正常一次不正常这种方式。

既然推断是quartz本身存在bug,那我们又要如何解决这个问题了?

其实在我个人看来,这个问题是无关紧要的,为什么说无关紧要呢?这就涉及到你项目业务设计的是否完善,代码是否健壮了。

一个设计良好的业务方法,特别是那些供外部调用的接口或方法,应该都支持幂等性,何为幂等性?即这个方法同样的参数至少在一个时间区间内,我调用1次和调用10次100次,结果都是一样的。

支持了幂等性,前面说的运行两次的情况是不是就无关紧要了?在有些定时任务为分布式设计的系统(后面会探讨)中,为了确保定时任务的执行甚至会故意人为的去调用两次。

当然支持幂等性最好是在进入方法时就判断,发现已经执行过时就立即返回而不是真的再去同样的结果再执行一遍,以节省资源。





目录
相关文章
|
4月前
|
资源调度 Java 调度
Spring Cloud Alibaba 集成分布式定时任务调度功能
定时任务在企业应用中至关重要,常用于异步数据处理、自动化运维等场景。在单体应用中,利用Java的`java.util.Timer`或Spring的`@Scheduled`即可轻松实现。然而,进入微服务架构后,任务可能因多节点并发执行而重复。Spring Cloud Alibaba为此发布了Scheduling模块,提供轻量级、高可用的分布式定时任务解决方案,支持防重复执行、分片运行等功能,并可通过`spring-cloud-starter-alibaba-schedulerx`快速集成。用户可选择基于阿里云SchedulerX托管服务或采用本地开源方案(如ShedLock)
143 1
|
2月前
|
Java BI 调度
Java Spring的定时任务的配置和使用
遵循上述步骤,你就可以在Spring应用中轻松地配置和使用定时任务,满足各种定时处理需求。
158 1
|
2月前
|
存储 Java API
简单两步,Spring Boot 写死的定时任务也能动态设置:技术干货分享
【10月更文挑战第4天】在Spring Boot开发中,定时任务通常通过@Scheduled注解来实现,这种方式简单直接,但存在一个显著的限制:任务的执行时间或频率在编译时就已经确定,无法在运行时动态调整。然而,在实际工作中,我们往往需要根据业务需求或外部条件的变化来动态调整定时任务的执行计划。本文将分享一个简单两步的解决方案,让你的Spring Boot应用中的定时任务也能动态设置,从而满足更灵活的业务需求。
163 4
|
5月前
|
资源调度 Java 调度
Spring Cloud Alibaba 集成分布式定时任务调度功能
Spring Cloud Alibaba 发布了 Scheduling 任务调度模块 [#3732]提供了一套开源、轻量级、高可用的定时任务解决方案,帮助您快速开发微服务体系下的分布式定时任务。
15052 32
|
4月前
|
Java 关系型数据库 MySQL
SpringBoot 集成 Quartz + MySQL
SpringBoot 集成 Quartz + MySQL
123 1
|
4月前
|
Java 开发者 Spring
Spring Boot实战宝典:揭秘定时任务的幕后英雄,让业务处理如流水般顺畅,轻松驾驭时间管理艺术!
【8月更文挑战第29天】在现代应用开发中,定时任务如数据备份、报告生成等至关重要。Spring Boot作为流行的Java框架,凭借其强大的集成能力和简洁的配置方式,为开发者提供了高效的定时任务解决方案。本文详细介绍了如何在Spring Boot项目中启用定时任务支持、编写定时任务方法,并通过实战案例展示了其在业务场景中的应用,同时提供了注意事项以确保任务的正确执行。
57 0
|
4月前
|
Dubbo Java 调度
揭秘!Spring Cloud Alibaba的超级力量——如何轻松驾驭分布式定时任务调度?
【8月更文挑战第20天】在现代微服务架构中,Spring Cloud Alibaba通过集成分布式定时任务调度功能解决了一致性和可靠性挑战。它利用TimerX实现任务的分布式编排与调度,并通过`@SchedulerLock`确保任务不被重复执行。示例代码展示了如何配置定时任务及其分布式锁,以实现每5秒仅由一个节点执行任务,适合构建高可用的微服务系统。
75 0
|
5月前
|
SQL Java 调度
实时计算 Flink版产品使用问题之使用Spring Boot启动Flink处理任务时,使用Spring Boot的@Scheduled注解进行定时任务调度,出现内存占用过高,该怎么办
实时计算Flink版作为一种强大的流处理和批处理统一的计算框架,广泛应用于各种需要实时数据处理和分析的场景。实时计算Flink版通常结合SQL接口、DataStream API、以及与上下游数据源和存储系统的丰富连接器,提供了一套全面的解决方案,以应对各种实时计算需求。其低延迟、高吞吐、容错性强的特点,使其成为众多企业和组织实时数据处理首选的技术平台。以下是实时计算Flink版的一些典型使用合集。
|
5月前
|
XML Java Linux
Spring Task 定时任务没有定时执行是为什么?
Spring Task 定时任务没有定时执行是为什么?
80 2
|
6月前
|
Java Spring
解密Spring Boot的定时任务
解密Spring Boot的定时任务
31 0