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

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);
    }
}

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

>如果未正确配置环境(即作业/触发器不存在),则它们将由启动的第一个实例创建
>如果作业已存在,但我想修改其计划(将过去每7分钟运行一次的作业更改为现在每5分钟运行一次),我可以为其定义新的触发器,重新部署将重新安排触发器数据库
>将创建一个作业实例,因为我们总是通过指定的JobKey引用作业,JobKey由作业本身定义.这意味着无论集群中有多少节点,或者我们部署了多少次,作业(及其关联的触发器)都只创建一次.

这一切都很好,但我担心两个实例在同一时间启动时可能存在竞争条件.因为群集中的所有节点都不会对此代码进行全局锁定,如果两个实例同时联机,我最终可能会遇到重复的作业或触发器,这会破坏此代码的重点.

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

最佳答案 我不确定在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
}

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

点赞