在集群环境中创建 Quartz 触发器
Creating Quartz Triggers in a Clustered Environment
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);
}
}
这种方法解决了一些问题:
- 如果环境配置不正确(即 jobs/triggers 不存在),那么它们将由启动的第一个实例创建
- 如果作业已经存在,但我想修改它的时间表(将以前 运行 每 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
}
时间戳为您提供了移动 window,其中作业竞争执行此作业。
我有(几乎)同样的问题:如何在集群环境中为每个软件版本创建一次触发器和作业。我通过在启动期间将集群节点之一指定为引导节点并让它重新创建 Quartz 作业来解决该问题。引导节点是第一个成功地将 运行 软件的 git 修订号插入数据库的节点。其他节点使用领导节点创建的 Quartz 配置。这是完整的解决方案:https://github.com/perttuta/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);
}
}
这种方法解决了一些问题:
- 如果环境配置不正确(即 jobs/triggers 不存在),那么它们将由启动的第一个实例创建
- 如果作业已经存在,但我想修改它的时间表(将以前 运行 每 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
}
时间戳为您提供了移动 window,其中作业竞争执行此作业。
我有(几乎)同样的问题:如何在集群环境中为每个软件版本创建一次触发器和作业。我通过在启动期间将集群节点之一指定为引导节点并让它重新创建 Quartz 作业来解决该问题。引导节点是第一个成功地将 运行 软件的 git 修订号插入数据库的节点。其他节点使用领导节点创建的 Quartz 配置。这是完整的解决方案:https://github.com/perttuta/quartz