Quartz Java 恢复作业会执行多次

Posted

技术标签:

【中文标题】Quartz Java 恢复作业会执行多次【英文标题】:Quartz Java resuming a job executes it many times 【发布时间】:2010-12-28 08:49:15 【问题描述】:

对于我的应用程序,我创建作业并使用 CronTriggers 安排它们。每个作业只有一个触发器,并且作业名称和触发器名称都相同。没有作业共享触发器。

现在,当我创建像这样的 "0/1 * * * * ?" 指示作业每秒执行一次的 cron 触发器时,它工作得很好。

当我第一次通过调用暂停工作时,问题就出现了:

scheduler.pauseJob(jobName, jobGroup);

然后在 50 秒后恢复工作:

scheduler.resumeJob(jobName, jobGroup);

我看到的是,在这 50 秒内,作业没有按要求执行。但是在我恢复工作的那一刻,我看到同时执行了 50 次工作!!!

我认为这是由于失火指令的默认设置,但即使在创建时将触发器的失火指令设置为这样:

trigger.setMisfireInstruction(CronTrigger.MISFIRE_INSTRUCTION_DO_NOTHING);

同样的事情也会发生。任何人都可以提出解决此问题的方法吗?

【问题讨论】:

【参考方案1】:

CronTrigger 的工作原理是记住 nextFireTime。创建触发器后,nextFireTime 被初始化。每次触发作业时,nextFireTime 都会更新。由于暂停时不会触发作业nextFireTime 仍然是“旧的”。因此,在您恢复作业后,触发器将返回每个旧触发器时间。

问题是,触发器不知道它正在暂停。为了克服这个问题,有这种失火处理。恢复作业后,触发器的updateAfterMisfire() 方法将被调用,从而更正nextFireTime。但如果 nextFireTime 和 now 之间的差异小于 misfireThreshold,则不会。然后永远不会调用该方法。此阈值的默认值为 60,000。因此,如果您的暂停时间超过 60 秒,一切都会好起来的。

既然你有问题,我认为不是。 ;) 要解决此问题,您可以修改阈值或使用 CronTrigger 周围的简单包装器:

public class PauseAwareCronTrigger extends CronTrigger 
    // constructors you need go here

    @Override
    public Date getNextFireTime() 
        Date nextFireTime = super.getNextFireTime();
        if (nextFireTime.getTime() < System.currentTimeMillis()) 
            // next fire time after now
            nextFireTime = super.getFireTimeAfter(null);
            super.setNextFireTime(nextFireTime);
        
        return nextFireTime;
    

【讨论】:

非常感谢 :) 这就像一个魅力。暂停工作这样简单的任务会产生这样的问题,这似乎很奇怪。【参考方案2】:

如果您暂停作业,触发器将继续触发,但执行将排队等待作业恢复。这不是一个误触发的触发器,因此该设置将无效。

我认为,您想做的是以编程方式禁用或删除 cron 触发器,而不是暂停作业。当你想恢复时,再重新添加触发器。

【讨论】:

pauseJob 方法的 javadoc 显示“使用给定名称暂停 JobDetail - 通过暂停其所有当前触发器。”所以我猜触发器正在暂停。触发器本身也没有暂停方法。或者任何类似的东西。只是从作业中删除触发器并重新插入它是我暂停作业的唯一选择吗?我的意思是要让你的调度程序做一件非常微不足道的事情。为什么它不能简单地工作? 嗯,好点子。我发现使用 Quartz 时,尝试各种方法直到某些东西奏效为止是最有成效的方法,因为它并不总是按照锡上所说的那样做。 这非常令人沮丧,因为也没有简单的方法可以简单地从作业中删除触发器。我可以从事无触发的工作吗?如果是,如何?有任何想法吗?我开始对此失去耐心,我已经尝试了几个小时:)【参考方案3】:

至少从 1.6.5 开始(触手可及的最早的石英版本),调度程序有一个 pauseTrigger 方法,该方法将名称/组作为参数。这意味着您不必拥有您使用的每种触发器类型的子类,也不必做时髦的删除/插入技巧。

这两个对我来说都很重要,因为 1) 我们的数据库有严格的禁止删除政策,2) 我使用的自定义数据存储不支持触发器子类。

【讨论】:

【参考方案4】:

您可以在 org.quartz.impl.jdbcjobstore.JobStoreSupport#resumeTrigger(Connection conn, TriggerKey key) 中添加这些代码

OperableTrigger trigger = getDelegate().selectTrigger(conn, key);if (trigger.getNextFireTime().getTime() &lt; System.currentTimeMillis()) trigger.setNextFireTime(trigger.getFireTimeAfter(null)); JobDetail job = retrieveJob(conn, status.getJobKey());storeTrigger(conn, trigger, job, true, status.getStatus(), false, false);

使用这些代码,当暂停的作业被恢复时,它不会立即被触发。另一方面,它将在下一个触发时间被触发,该时间由恢复时间计算

【讨论】:

以上是关于Quartz Java 恢复作业会执行多次的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Quartz 安排作业在一天内多次但固定的时间运行

Quartz使用

quartz作业调度的应用和原理

quartz任务调度框架与spring整合

如何从 Quartz 作业中执行 Struts 2 Action。如何获得容器?

详细讲解Quartz如何从入门到精通