Кварц повторить при неудаче - PullRequest
21 голосов
/ 10 декабря 2010

Допустим, у меня есть триггер, настроенный таким образом:

<bean id="updateInsBBTrigger"         
    class="org.springframework.scheduling.quartz.CronTriggerBean">
    <property name="jobDetail" ref="updateInsBBJobDetail"/>
    <!--  run every morning at 5 AM  -->
    <property name="cronExpression" value="0 0 5 * * ?"/>
</bean>

Триггер должен соединиться с другим приложением, и если есть какая-либо проблема (например, сбой соединения), он должен повторить задачу допять раз каждые 10 минут или до успеха.Есть ли способ настроить триггер так, чтобы он работал?

Ответы [ 3 ]

15 голосов
/ 10 декабря 2010

Источник : Автоматически повторить неудачные задания в кварце

Если вы хотите, чтобы задание выполнялось снова и снова до тех пор, пока оно не будет выполнено успешно, все, что вам нужно сделать, - это сгенерировать исключение JobExecutionException с флагом, чтобы планировщик запустил его снова при сбое. Следующий код показывает, как:

class MyJob implements Job {

    public MyJob() {
    }

    public void execute(JobExecutionContext context) throws JobExecutionException {

        try{
            //connect to other application etc
        }
        catch(Exception e){

            Thread.sleep(600000); //sleep for 10 mins

            JobExecutionException e2 = new JobExecutionException(e);
            //fire it again
            e2.setRefireImmediately(true);
            throw e2;
        }
    }
}

Это становится немного сложнее, если вы хотите повторить определенное количество раз. Вы должны использовать StatefulJob и держать retryCounter в его JobDataMap, который вы увеличиваете в случае сбоя задания. Если счетчик превышает максимальное количество повторных попыток, вы можете отключить задание, если хотите.

class MyJob implements StatefulJob {

    public MyJob() {
    }

    public void execute(JobExecutionContext context) throws JobExecutionException {
        JobDataMap dataMap = context.getJobDetail().getJobDataMap();
        int count = dataMap.getIntValue("count");

        // allow 5 retries
        if(count >= 5){
            JobExecutionException e = new JobExecutionException("Retries exceeded");
            //make sure it doesn't run again
            e.setUnscheduleAllTriggers(true);
            throw e;
        }


        try{
            //connect to other application etc

            //reset counter back to 0
            dataMap.putAsString("count", 0);
        }
        catch(Exception e){
            count++;
            dataMap.putAsString("count", count);
            JobExecutionException e2 = new JobExecutionException(e);

            Thread.sleep(600000); //sleep for 10 mins

            //fire it again
            e2.setRefireImmediately(true);
            throw e2;
        }
    }
}
7 голосов
/ 06 декабря 2016

Я бы порекомендовал реализацию, подобную этой, для восстановления задания после сбоя:

final JobDataMap jobDataMap = jobCtx.getJobDetail().getJobDataMap();
// the keys doesn't exist on first retry
final int retries = jobDataMap.containsKey(COUNT_MAP_KEY) ? jobDataMap.getIntValue(COUNT_MAP_KEY) : 0;

// to stop after awhile
if (retries < MAX_RETRIES) {
  log.warn("Retry job " + jobCtx.getJobDetail());

  // increment the number of retries
  jobDataMap.put(COUNT_MAP_KEY, retries + 1);

  final JobDetail job = jobCtx
      .getJobDetail()
      .getJobBuilder()
       // to track the number of retries
      .withIdentity(jobCtx.getJobDetail().getKey().getName() + " - " + retries, "FailingJobsGroup")
      .usingJobData(jobDataMap)
      .build();

  final OperableTrigger trigger = (OperableTrigger) TriggerBuilder
      .newTrigger()
      .forJob(job)
       // trying to reduce back pressure, you can use another algorithm
      .startAt(new Date(jobCtx.getFireTime().getTime() + (retries*100))) 
      .build();

  try {
    // schedule another job to avoid blocking threads
    jobCtx.getScheduler().scheduleJob(job, trigger);
  } catch (SchedulerException e) {
    log.error("Error creating job");
    throw new JobExecutionException(e);
  }
}

Почему?

  1. Он не будет блокировать кварцевых рабочих
  2. Это позволит избежать обратного давления. С setRefireImmediately задание будет немедленно выполнено, и это может привести к проблемам обратного давления
7 голосов
/ 10 декабря 2010

Я бы предложил для большей гибкости и конфигурируемости лучше хранить в вашей БД два смещения: repeatOffset , которое сообщит вам через сколько времени следует повторить задание и trialPeriodOffset , в котором будет храниться информация о временном окне, в котором задание выполнено разрешено перенести Затем вы можете получить эти два параметра, например (я предполагаю, что вы используете Spring):

String repeatOffset = yourDBUtilsDao.getConfigParameter(..);
String trialPeriodOffset = yourDBUtilsDao.getConfigParameter(..);

Тогда вместо задания запоминания счетчика нужно будет запомнить initalAttempt:

Long initialAttempt = null;
initialAttempt = (Long) existingJobDetail.getJobDataMap().get("firstAttempt");

и выполните что-то вроде следующей проверки:

long allowedThreshold = initialAttempt + Long.parseLong(trialPeriodOffset);
        if (System.currentTimeMillis() > allowedThreshold) {
            //We've tried enough, time to give up
            log.warn("The job is not going to be rescheduled since it has reached its trial period threshold");
            sched.deleteJob(jobName, jobGroup);
            return YourResultEnumHere.HAS_REACHED_THE_RESCHEDULING_LIMIT;
        }

Было бы неплохо создать перечисление для результата попытки, которая возвращается в основной рабочий процесс вашего приложение, как указано выше.

Затем построите время перепланирования:

Date startTime = null;
startTime = new Date(System.currentTimeMillis() + Long.parseLong(repeatOffset));

String triggerName = "Trigger_" + jobName;
String triggerGroup = "Trigger_" + jobGroup;

Trigger retrievedTrigger = sched.getTrigger(triggerName, triggerGroup);
if (!(retrievedTrigger instanceof SimpleTrigger)) {
            log.error("While rescheduling the Quartz Job retrieved was not of SimpleTrigger type as expected");
            return YourResultEnumHere.ERROR;
}

        ((SimpleTrigger) retrievedTrigger).setStartTime(startTime);
        sched.rescheduleJob(triggerName, triggerGroup, retrievedTrigger);
        return YourResultEnumHere.RESCHEDULED;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...