两个 Quartz-Worker 执行同一作业两次

Abh*_*tra 6 java scheduling quartz-scheduler

我们已经实现了quartz来进行调度。每个产生的作业都有不同的key。到目前为止一切正常。昨天我们遇到了一个问题,因为同一个作业被两个不同的 Quartz-Worker 线程执行了两次或三次(没有特殊行为)。我们不能将线程池大小设为一,因为我们需要并发作业。

关于我们的计划作业,值得注意的一件事是,它会在每次运行时自行重新安排(每日、每周或每月),即,如果安排作业每天运行,那么它将在接下来的 24 小时内重新安排自己,但会随机预定义(例如 3 小时)时间窗口。例如,如果某个作业今天在 4:10(即 4:00 到 7:00 之间)运行,那么我们的作业会将其自行重新安排到明天 4:00 到 7:00 之间的某个随机时间。它可以是 4:01 或 6:59 或 5:23 或给定时间窗口中的任何其他值。这个过程也运行良好,并且在大多数情况下仍然运行良好,但在某些情况下,我们的重新安排算法无法在接下来的 24 小时内自行安排。相反,它会在接下来的 10 秒、1 小时或任何其他随机值内自行安排。但在2-3次错误的重新安排之后,它最终稳定下来,即它最终在接下来的24小时内自行安排。我们怀疑这可能是由于多个线程访问 Calendar 对象而发生的(我们使用 Calendar.getInstance() 和 cal.add(Calendar.DAY_OF_YEAR, 1) 在接下来的 24 小时内重新安排作业)。不知何故,日历实例选择了错误的时间或无法在当前时间添加一天。

因此,存在两个问题: 1. 多个 Quartz 线程获取相同的作业 2. 日历无法添加给定的间隔或在某些特定情况下选择错误的当前时间(多线程访问)

任何帮助将不胜感激。尽快回复。谢谢。


感谢您的回复。我想知道 Statefuljob 和 @DisallowConcurrentExecution 注释以及将 threadPool.threadCount 设置为 1 之间有什么区别。

重新安排的代码如下...

    Calendar cal = Calendar.getInstance();
    Calendar nextCal = Calendar.getInstance();
    cal.setTimeZone(TimeZone.getTimeZone(obj.getTimeZone()));
    nextCal.setTimeZone(TimeZone.getTimeZone(obj.getTimeZone()));
    Date startTime = null;
    SimpleTrigger trigger = null;

    JobDataMap dataMap = new JobDataMap();
     if (repeatTimeInMillis == null) {
        cal.set(Calendar.HOUR_OF_DAY, obj.getStartTime());
        nextCal.set(Calendar.HOUR_OF_DAY, obj.getStartTime());
        cal.set(Calendar.MINUTE, 0);
        nextCal.set(Calendar.MINUTE, 0);
        cal.set(Calendar.SECOND, 0);
        nextCal.set(Calendar.SECOND, 0);
        if (obj.getScheduleType() == ScheduleType.MONTHLY) { // Monthly
    log.info("in monthly schedule");                
            nextCal.add(Calendar.MONTH, 2);
            nextCal.set(Calendar.DAY_OF_MONTH, obj.getDate());
            cal.add(Calendar.MONTH, 1);
            cal.set(Calendar.DAY_OF_MONTH, obj.getDate());
        } else if (obj.getScheduleType() == ScheduleType.WEEKLY) { // Weekly
    log.info("in weekly schedule");                
            nextCal.add(Calendar.WEEK_OF_YEAR, 2);
            nextCal.set(Calendar.DAY_OF_WEEK, obj.getDay());
            cal.add(Calendar.WEEK_OF_YEAR, 1);
            cal.set(Calendar.DAY_OF_WEEK, obj.getDay());
        } else if (obj.getScheduleType() == ScheduleType.DAILY) { // Daily
    log.info("in daily schedule");                
    nextCal.add(Calendar.DAY_OF_YEAR, 2);
            cal.add(Calendar.DAY_OF_YEAR, 1);
        }

        long time = obj.getTimeWindow() * 60 * 60 * 1000;
        time = Math.round(time * Math.random());
        cal.setTimeInMillis(cal.getTimeInMillis() + time);
        startTime = cal.getTime();
        nextCal.setTimeInMillis(nextCal.getTimeInMillis() + time);
        repeatTimeInMillis = nextCal.getTimeInMillis() - cal.getTimeInMillis();

        log.info("Rescheduling job at " + startTime);
        trigger = newTrigger().usingJobData(dataMap)
                .withIdentity(obj.getScheduleJobName(), obj.getScheduleJobGroup()).startAt(startTime)
                .withSchedule(simpleSchedule().withIntervalInMilliseconds(repeatTimeInMillis).repeatForever())
                .build();
    } else {
        log.info("Rescheduling job next " + repeatTimeInMillis + " milliseconds.");
        cal.setTimeInMillis(cal.getTimeInMillis() + repeatTimeInMillis);
        startTime = cal.getTime();
        trigger = newTrigger().usingJobData(dataMap)
                .withIdentity(obj.getScheduleJobName(), obj.getScheduleJobGroup()).startAt(startTime)
                .withSchedule(simpleSchedule().withIntervalInMilliseconds(repeatTimeInMillis).withRepeatCount(1)).build();
    }
Run Code Online (Sandbox Code Playgroud)

Kie*_*xon 3

StatefulJob 接口和 @DisallowConcurrentExecution 注释执行相同的操作。

来自DisallowConcurrentExecution javadoc

将 Job 类标记为不能同时执行多个实例的类......

这可以用来代替实现 Quartz 2.0 之前使用的 StatefulJob 标记接口

将 threadPool.threadCount 属性设置为 1 意味着最多可以执行1 个任何类型的作业

使用任何这些解决方案都将停止并发执行的作业,并导致任何触发器被放入队列中,以便在前一个触发器实例完成时执行