确保Spring Quartz任务执行不重叠

49

我有一个Java程序,每20秒从Spring Qquartz执行一次。有时它只需要几秒钟就能执行完,但随着数据变得越来越大,我确信它会运行20秒甚至更长时间。

如何防止Quartz在一个实例仍在执行时触发作业/任务?同时执行相同的数据库操作可能不是很好。我能否进行某种形式的同步?

7个回答

147

Quartz 1

如果你将你的类改为实现StatefulJob而不是Job,Quartz会自动处理这个问题。从StatefulJob javadoc中可以看到:

有状态作业不允许同时执行,这意味着在execute(xx)方法完成之前发生的新触发器将会被延迟。

StatefulJob扩展了Job,并没有添加任何新方法,因此,为了获得所需的行为,你只需要更改如下内容:

public class YourJob implements org.quartz.Job {
    void execute(JobExecutionContext context) {/*implementation omitted*/}
}

变成这样:

public class YourJob implements org.quartz.StatefulJob {
    void execute(JobExecutionContext context) {/*implementation omitted*/}
}

Quartz 2

在Quartz 2.0版本中,StatefulJob已被弃用。现在建议使用注释来代替,例如:

@DisallowConcurrentExecution
public class YourJob implements org.quartz.Job {
    void execute(JobExecutionContext context) {/*implementation omitted*/}
}

5
使用这个解决方案,尝试的作业实例会排队等候,对吗?你如何防止它们排队等候? - Jay Sullivan
8
正确答案是这个。为什么楼主接受了另一个只是提出备选方法的答案? - Saeed Neamati
2
@Donal DisallowConcurrentExecution注解如何在多个服务器实例之间维护非并发性,或者它不会这样做? - greperror
1
如果您想要防止多个服务器实例之间的并发执行,请查看@PersistJobDataAfterExecution - Dónal

32
如果你只需要每20秒执行一次,那么Quartz就过于复杂了。对于这项工作,java.util.concurrent.ScheduledExecutorService应该是完全足够的。 ScheduledExecutorService还提供了两种调度语义。"fixed rate"将尝试每20秒运行一次作业,而不考虑重叠,而"fixed delay"将尝试在第一个作业结束和下一个作业开始之间留出20秒的时间。如果你想避免重叠,那么使用fixed-delay是最安全的。

1
或者,当然,如果您想保持在Spring中,则可以使用Spring TaskScheduler - Michael Piefel
10
如果不在一个集群中,则为真。 - David Mann
根据文档,似乎ScheduledExecutorService的后续版本将不会并发执行。"如果此任务的任何执行时间超过其周期,则随后的执行可能会延迟开始,但不会同时执行。" - Sovietaced

21

提醒一下,如果有人参考这个问题,StatefulJob已被弃用。现在他们建议使用注释代替...

@PersistJobDataAfterExecution
@DisallowConcurrentExecution
public class TestJob implements Job {

这将解释那些注释的含义...

这些注释会导致行为就像它们的名字描述的那样 - 不允许多个实例并发运行(考虑一种情况,其中一个作业在其execute()方法中有需要34秒才能完成的代码,但它被安排在每30秒重复一次的触发器中),并且在每次执行后,它的JobDataMap内容将重新持久化到调度程序的JobStore中。对于本示例,只有@PersistJobDataAfterExecution注释是真正相关的,但最好始终将@DisallowConcurrentExecution注释与其一起使用,以防止保存数据时出现竞争条件。


4

如果您使用Spring Quartz,我认为您需要进行以下配置:

    <bean id="batchConsumerJob"class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
        <property name="targetObject" ref="myScheduler" />
        <property name="targetMethod" value="execute" />
        <property name="concurrent" value="false" />
    </bean>

运行得非常好,可能的代码如下:MethodInvokingJobDetailFactoryBean jobDetail = new MethodInvokingJobDetailFactoryBean(); jobDetail.setConcurrent(false); jobDetail.setBeanName("Job_" + jobId); jobDetail.setTargetObject(job); jobDetail.setTargetMethod("execute"); jobDetail.setConcurrent(false); - Amare
甜的。谢谢。 - Joel

3
“我不确定你是否想要同步,因为第二个任务将会被阻塞,直到第一个任务完成,你最终会有积压的任务。你可以把这些任务放在队列里,但是从你的描述来看,队列可能会无限增长。

我建议研究读写锁,并让你的任务在运行时设置锁。未来的任务可以检查此锁,如果旧任务仍在运行,则立即退出。从我的经验来看,这是最可靠的方法。

或许还可以发出警告,以便你知道遇到问题并相应地增加时间间隔?


1

你可以使用信号量。当信号量被占用时,放弃第二个任务并等待下一次触发时间。


0
将它们放入队列中。
即使时间超过20秒,当前的作业也应该完成,然后从队列中获取下一个作业。
或者您也可以将时间增加到一些合理的数量。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接