2016-05-11 31 views
0

Related: Quartz Clustering - triggers duplicated when the server starts在集群环境中

创建石英触发器我使用Quartz调度在基于Java的集群环境管理计划的作业。群集中有几个节点在任何时候都运行Quartz,由所有节点连接到的postgresql数据库中的数据存储支持。

当一个实例初始化,它试图创建或更新工作,并通过执行该代码在石英数据存储触发:

private void createOrUpdateJob(JobKey jobKey, Class<? extends org.quartz.Job> clazz, Trigger trigger) throws SchedulerException { 
    JobBuilder jobBuilder = JobBuilder.newJob(clazz).withIdentity(jobKey); 
    if (!scheduler.checkExists(jobKey)) { 
     // if the job doesn't already exist, we can create it, along with its trigger. this prevents us 
     // from creating multiple instances of the same job when running in a clustered environment 
     scheduler.scheduleJob(jobBuilder.build(), trigger); 
     log.error("SCHEDULED JOB WITH KEY " + jobKey.toString()); 
    } else { 
     // if the job has exactly one trigger, we can just reschedule it, which allows us to update the schedule for 
     // that trigger. 
     List<? extends Trigger> triggers = scheduler.getTriggersOfJob(jobKey); 
     if (triggers.size() == 1) { 
      scheduler.rescheduleJob(triggers.get(0).getKey(), trigger); 
      return; 
     } 

     // if for some reason the job has multiple triggers, it's easiest to just delete and re-create the job, 
     // since we want to enforce a one-to-one relationship between jobs and triggers 
     scheduler.deleteJob(jobKey); 
     scheduler.scheduleJob(jobBuilder.build(), trigger); 
    } 
} 

这种方法解决了许多问题:

  1. 如果环境配置不正确(即作业/触发器不存在),那么它们将通过第一个实例创建,它将启动
  2. 如果作业已经存在,但我想修改其时间表(更改jo b每7分钟运行一次,现在每5分钟运行一次),我可以为它定义一个新的触发器,重新部署将重新安排数据库中的触发器
  3. 将创建一个作业的实例,因为我们始终使用由作业本身定义的指定JobKey来引用作业。这意味着作业(及其关联的触发器)只创建一次,而不管集群中有多少个节点,或者部署多少次。

这一切都很好,但我关心两个实例在同一时间启动时潜在的竞争条件。因为这个代码没有集群中所有节点都会尊重的全局锁定,所以如果两个实例同时联机,那么最终可能会出现重复的作业或触发器,这会破坏此代码的重点。

在群集环境中自动定义Quartz作业和触发器是否有最佳做法?或者我需要诉诸设置自己的锁?

回答

1

我不确定是否有更好的方法在Quartz中做到这一点。但是,如果您已经在使用Redis或Memcache,我会建议让所有实例针对众所周知的密钥执行atomic increment。如果粘贴的代码应该运行每个集群每小时只有一个工作,你可以做到以下几点:

long timestamp = System.currentTimeMillis()/1000/60/60; 
String key = String.format("%s_%d", jobId, timestamp); 

// this will only be true for one instance in the cluster per (job, timestamp) tuple 
bool shouldExecute = redis.incr(key) == 1 

if (shouldExecute) { 
    // run the mutually exclusive code 
} 

时间戳给你一个移动窗口内的作业都争相来执行这项工作。

+0

这并不是我正在寻找的东西,因为它不会阻止Quartz为同一个作业创建多个触发器,但它确保只有其中一个触发器会在给定的窗口,所以我认为它确实解决了问题,如果以一种迂回的方式。 – MusikPolice

0

我有(几乎)同样的问题:如何在集群环境中为每个软件版本创建一次触发器和作业。我通过在启动过程中将其中一个群集节点指定为主节点并让它重新创建Quartz作业来解决问题。主节点是首先成功地将正在运行的软件的git修订版号插入数据库的节点。其他节点使用由主节点创建的Quartz配置。这里有完整的解决方案:https://github.com/perttuta/quartz