web-dev-qa-db-fra.com

Modèle de conception pour "réessayer" logique qui a échoué?

J'écris une logique de reconnexion afin de tenter périodiquement d'établir une connexion avec un terminal distant qui s'est effondré. Essentiellement, le code ressemble à ceci:

public void establishConnection() {
    try {
        this.connection = newConnection();
    } catch (IOException e) {
        // connection failed, try again.
        try { Thread.sleep(1000); } catch (InterruptedException e) {};

        establishConnection();
    }
}

J'ai souvent résolu ce problème général avec un code similaire à celui ci-dessus, mais je suis largement insatisfait du résultat. Existe-t-il un modèle de conception conçu pour traiter ce problème?

48
Naftuli Kay

Une bibliothèque qui vaut la peine d’être extraite est Sarge , qui effectue automatiquement de nouvelles tentatives selon un plan défini.

3
Jonathan

Prise sans vergogne: J'ai implémenté certaines classes pour permettre de nouvelles tentatives. La bibliothèque n'est pas encore disponible, mais vous pouvez la bifurquer sur github. Et un fork existe.

Il permet de construire un sécheur avec diverses stratégies flexibles. Par exemple:

Retryer retryer = 
    RetryerBuilder.newBuilder()
                  .withWaitStrategy(WaitStrategies.fixedWait(1, TimeUnit.SECOND))
                  .withStopStrategy(StopStrategies.stopAfterAttempt(3))
                  .retryIfExceptionOfType(IOException.class)
                  .build();

Et vous pouvez ensuite exécuter un appelable (ou plusieurs) avec le Retryer:

retryer.call(new Callable<Void>() {
    public Void call() throws IOException {
        connection = newConnection();
        return null;
    }
}
30
JB Nizet

Vous pouvez essayer le motif Idempotent Retry .

enter image description here

22
GalacticJello

J'utilise des annotations AOP et Java. Il existe un mécanisme prêt à l'emploi dans jcabi-aspects (je suis un développeur):

@RetryOnFailure(attempts = 3, delay = 1, unit = TimeUnit.SECONDS)
public void establishConnection() {
  this.connection = newConnection();
}

ps. Vous pouvez également essayer RetryScalar à partir de Cactoos .

9
yegor256

Utilisation de Failsafe (auteur ici):

RetryPolicy retryPolicy = new RetryPolicy()
  .retryOn(IOException.class)
  .withMaxRetries(5)
  .withDelay(1, TimeUnit.SECONDS);

Failsafe.with(retryPolicy).run(() -> newConnection());

Aucune annotation, aucune magie, ne doit pas nécessairement être une application Spring, etc. Simplement simple 

4
Jonathan

J'aime beaucoup ce code Java 8 de ce blog et vous n'avez pas besoin de bibliothèque supplémentaire sur votre chemin de classe.

Il vous suffit de passer une fonction à la classe de nouvelle tentative.

@Slf4j
public class RetryCommand<T> {

    private int maxRetries;

    RetryCommand(int maxRetries)
    {
        this.maxRetries = maxRetries;
    }

    // Takes a function and executes it, if fails, passes the function to the retry command
    public T run(Supplier<T> function) {
        try {
            return function.get();
        } catch (Exception e) {
            log.error("FAILED - Command failed, will be retried " + maxRetries + " times.");
            return retry(function);
        }
    }

    private T retry(Supplier<T> function) throws RuntimeException {

        int retryCounter = 0;
        while (retryCounter < maxRetries) {
            try {
                return function.get();
            } catch (Exception ex) {
                retryCounter++;
                log.error("FAILED - Command failed on retry " + retryCounter + " of " + maxRetries, ex);
                if (retryCounter >= maxRetries) {
                    log.error("Max retries exceeded.");
                    break;
                }
            }
        }
        throw new RuntimeException("Command failed on all of " + maxRetries + " retries");
    }
}

Et pour l'utiliser:

new RetryCommand<>(5).run(() -> client.getThatThing(id));
1
Dherik

Vous pouvez essayer spring-retry , il a une interface claire et facile à utiliser.

Exemple:

 @Retryable(maxAttempts = 4, backoff = @Backoff(delay = 500))
 public void establishConnection() {
    this.connection = newConnection();
 } 

En cas d'exception, il réessayera (appelera) jusqu'à 4 fois la méthode EstablConnection () avec une politique de sauvegarde de 500 ms 

1
db80

Vous pouvez également créer une fonction wrapper qui effectue simplement une boucle sur l'opération souhaitée et lorsque l'opération est effectuée, sortez simplement de la boucle.

public static void main(String[] args) {
    retryMySpecialOperation(7);
}

private static void retryMySpecialOperation(int retries) {
    for (int i = 1; i <= retries; i++) {
        try {
            specialOperation();
            break;
        }
        catch (Exception e) {
            System.out.println(String.format("Failed operation. Retry %d", i));
        }
    }
}

private static void specialOperation() throws Exception {
    if ((int) (Math.random()*100) % 2 == 0) {
        throw new Exception("Operation failed");
    }
    System.out.println("Operation successful");
}
0
klusht

Si vous utilisez Java 8, cela peut aider.

import Java.util.function.Supplier;

public class Retrier {
public static <T> Object retry(Supplier<T> function, int retryCount) throws Exception {
     while (0<retryCount) {
        try {
            return function.get();
        } catch (Exception e) {
            retryCount--;
            if(retryCount == 0) {
                throw e;
            }
        }
    }
    return null;
}

public static void main(String[] args) {
    try {
        retry(()-> {
            System.out.println(5/0);
            return null;
        }, 5);
    } catch (Exception e) {
        System.out.println("Exception : " + e.getMessage());
    }
}
}

Merci,

Praveen R.

0
Ravipati Praveen

il n'y a rien de spécial dans la nouvelle tentative - prenez cette classe comme exemple http://www.docjar.com/html/api/org/springframework/jms/listener/DefaultMessageListenerContainer.Java.html peut voir même les développeurs Spring écrire encore du code pour réessayer - ligne 791 ... il n'y a pas de motif aussi spécial que je sache. 

Ce que je peux vous conseiller pour gérer les ressources, c’est de prendre la bibliothèque de piscines Apache commons - cochez cette http://commons.Apache.org/pool/apidocs/org/Apache/commons/pool/impl/GenericObjectPool.html et visiter http://commons.Apache.org/pool

0
Andrey Borisov