在集群环境中创建Quartz触发器

时间:2016-05-11 18:58:42

标签: java quartz-scheduler race-condition

Related: Quartz Clustering - triggers duplicated when the server starts

我正在使用Quartz Scheduler来管理基于java的集群环境中的预定作业。在任何给定时间,集群中都有一些节点,它们都运行Quartz,由postgresql数据库中的数据存储支持,所有节点都连接到该数据库。

初始化实例时,它会尝试通过执行以下代码在Quartz数据存储中创建或更新作业和触发器:

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. 如果作业已存在,但我想修改其日程安排(将过去每7分钟运行一次的作业更改为现在每5分钟运行一次),我可以为其定义一个新的触发器,重新部署将重新安排数据库中的触发器
  3. 将创建一个作业实例,因为我们总是通过指定的JobKey引用作业,JobKey由作业本身定义。这意味着无论集群中有多少节点,或者部署了多少次,作业(及其关联的触发器)都只创建一次。
  4. 这一切都很好,但是当两个实例在同一时间启动时,我担心潜在的竞争条件。因为群集中的所有节点都不会对此代码进行全局锁定,如果两个实例同时联机,我最终可能会遇到重复的作业或触发器,这会破坏此代码的重点。

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

2 个答案:

答案 0 :(得分: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
}

时间戳为您提供了一个移动窗口,在该窗口中,作业可以竞争执行此作业。

答案 1 :(得分:0)

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