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次,结果都是一样的。

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

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

时间: 2024-10-26 01:47:58

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

Spring 整合Quartz 2实现定时任务五:集群、分布式架构实现探讨

到这里,功能上我们已经全实现了. 但是有时候我们的项目不是部署在一台机器上的,而是一个集群环境,往往我们的定时任务只需要一台机器执行就够了. 那么我们怎么样来实现这种集群环境下的定时任务运行呢? 前面说的支持幂等性可以在一定程序上解决这个问题,网上有版本使用数据库加锁的方式也可以,当然,还可以借助zookeeper等方式来实现更强大的分布式锁. 我在这里主要说的方式并不直接涉及到这个集群的问题,而是讨论这个定时任务运行的架构该如何来搭建,当然集群问题将自然而然得到解决. 在我的思维中,定时任务的

Spring整合Quartz实现定时任务调度的方法_java

最近项目中需要实现定时执行任务,比如定时计算会员的积分.调用第三方接口等,由于项目采用spring框架,所以这里结合spring框架来介绍. 编写作业类 即普通的pojo,如下: package com.pcmall.task; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class TaskA { private static Logger logger = LoggerFactory.getLogger(Ta

Spring 3整合Quartz 2实现定时任务二:动态添加任务

前面,我们已经对Spring 3和Quartz 2用配置文件的方式进行了整合,如果需求比较简单的话应该已经可以满足了.但是很多时候,我们常常会遇到需要动态的添加或修改任务,而spring中所提供的定时任务组件却只能够通过修改xml中trigger的配置才能控制定时任务的时间以及任务的启用或停止,这在带给我们方便的同时也失去了动态配置任务的灵活性.我搜索了一些网上的解决方法,都没有很好的解决这个问题,而且大多数提到的解决方案都停留在Quartz 1.x系列版本上,所用到的代码和API已经不能适用于

Spring 3整合Quartz 2实现定时任务三:动态暂停 恢复 修改和删除任务

前面我们已经完成了spring 3和quartz 2的整合以及动态添加定时任务,我们接着来完善它,使之能支持更多的操作,例如暂停.恢复.修改等. 在动态添加定时任务中其实已经涉及到了其中的一些代码,这里我们再来细化的理一理.先来看一下我们初步要实现的目标效果图,这里我们只在内存中操作,并没有把quartz的任何信息保存到数据库,即使用的是RAMJobStore,当然如果你有需要,可以实现成JDBCJobStore,那样任务信息将会更全面,貌似还有专门的监控工具,不过本人没有用过: 如上图,我们要

Spring 3整合Quartz 2实现定时任务一:常规整合

最近工作中需要用到定时任务的功能,虽然Spring3也自带了一个轻量级的定时任务实现,但感觉不够灵活,功能也不够强大.在考虑之后,决定整合更为专业的Quartz来实现定时任务功能. 首先,当然是添加依赖的jar文件,我的项目是maven管理的,以下的我项目的依赖: <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core&l

spring学习笔记(25)spring整合quartz多版本实现企业级任务调度

在我们的另一个专栏<深入浅出Quartz任务调度>详细的讲解了使用Quartz适用于从普通门户至网站企业级系统的任务调度实现方法.在下面我们结合实例来完整spring和quartz的整合工作,将我们对quartz的配置统一交给spring容器进行管理.quartz1与quartz2两个版本的差别较大,他们的具体差别可参考我的另一篇文章Quartz任务调度(1)概念例析快速入门.鉴于我们的实际项目中很多依旧使用着quartz1版本,下面我们会针对quartz1和quartz2的配置分别进行分析.

Spring整合Quartz 疑问

问题描述 spring配置:<beanid="workLoadSchedule"class="org.springframework.scheduling.quartz.SchedulerFactoryBean"><propertyname="triggers"><list><refbean="workLoadTrigger"/></list></propert

spring整个quartz CronTriggerBean里面是否可以配置多个jobDetail

问题描述 spring整个quartz CronTriggerBean里面是否可以配置多个jobDetail spring整合quartz org.springframework.scheduling.quartz.CronTriggerBean里面是否可以配置多个jobDetail 比如两个jobDetail可以用一个CronTriggerBean?大神帮帮忙 解决方案 spring Quartz多个定时任务的配置spring多个定时任务quartz配置spring多个定时任务quartz配置

khj-spring整合quartz定时任务报错,请大神们解救

问题描述 spring整合quartz定时任务报错,请大神们解救 web.xml中的配置 spring_mvcorg.springframework.web.servlet.DispatcherServlet contextConfigLocationclasspath:applicationContext spring_mvc/ applicationContext.xml的配置 <!-- 要调用的工作类 --> <!-- 定义调用对象和调用对象的方法 --> <!-- 调