2010-12-10 36 views
16

Disons que j'ai un déclencheur configuré de cette façon:nouvelle tentative Quartz lorsque l'échec

<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> 

Le déclencheur doivent se connecter avec une autre application et s'il y a un problème (comme un échec de connexion) il se doit pour recommencer la tâche jusqu'à cinq fois toutes les 10 minutes ou jusqu'à la réussite. Il y a un moyen de configurer le déclencheur pour fonctionner comme ça?

Répondre

16

Source: Automatically Retry Failed Jobs in Quartz

Si vous voulez avoir un emploi qui ne cesse d'essayer encore et encore jusqu'à ce qu'il réussisse, tout ce que vous devez faire est de jeter un JobExecutionException avec un drapeau pour dire le planificateur pour le feu encore quand il échoue. Le code suivant montre comment:

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

Cela devient un peu plus compliqué si vous voulez réessayer un certain nombre de fois. Vous devez utiliser un StatefulJob et détenir un retryCounter dans son JobDataMap, que vous incrémentez si le travail échoue. Si le compteur dépasse le nombre maximal de tentatives, vous pouvez désactiver le travail si vous le souhaitez.

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; 
     } 
    } 
} 
+0

Merci. C'est ce que je cherchais. – Averroes

+43

-1, je ne recommande pas cette approche - elle bloque l'un des fils de travail Quartz pendant 10 minutes.La bonne façon de procéder serait de faciliter la fonctionnalité Quartz existante - dire d'une manière ou d'une autre de relancer le même travail après 10 minutes - après tout, c'est ce pour quoi il est fait. Si nous devons exécuter du code et dormir, il ne sert à rien d'utiliser Quartz en premier lieu. –

+1

compléter cela en Quartz 2.0 (pour .net au moins). Le StatefulJob est remplacé par 'PersistJobDataAfterExecutionAttribute' http://quartznet.sourceforge.net/apidoc/2.0/html/html/babe3560-218c-38de-031a-7fe1fdd569d2.htm – ossek

7

Je suggère pour plus de flexibilité et de configurabilité pour mieux stocker dans votre DB deux décalages: la repeatOffset qui vous dira après combien de temps le travail doit être rejugé et la trialPeriodOffset qui gardera la information de la fenêtre temporelle que le travail est autorisé à être replanifié. Ensuite, vous pouvez récupérer ces deux paramètres comme (je suppose que vous utilisez Spring):

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

Alors, au lieu du travail de se rappeler le compteur, il devra se rappeler le initalAttempt:

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

et effectuer quelque chose comme le contrôle suivant:

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

ce serait une bonne idée de créer un ENUM pour le résultat de la tentative qui est retourné au flux de travail de base de votre application comme ci-dessus.

construit alors le temps rééchelonnements:

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; 
2

Je recommande une mise en œuvre comme celui-ci pour récupérer le travail après un échec:

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

Pourquoi?

  1. Il ne bloquera pas quartz travailleurs
  2. Cela évitera une contre-pression. Avec setRefireImmédiatement, le travail sera déclenché immédiatement et cela pourrait entraîner des problèmes de contre-pression