Mécanisme de nouvelle tentative de goyave

Annuaire d'articles

Réessayer la goyave

Lors de l'appel de services externes ou d'interfaces associées, les services externes ne sont généralement pas fiables pour l'appel, en particulier dans les microservices actuels. Dans le cas d'un environnement réseau médiocre, la gigue du réseau peut très facilement provoquer des conditions anormales telles que des délais d'attente de requête. Dans ce cas , un une nouvelle tentative en cas d'échec est requise pour rappeler l'API pour la tolérance aux pannes. La stratégie de nouvelle tentative est largement utilisée dans la gouvernance des services, et il est généralement vérifié si le service est actif grâce à une détection régulière.

Guava RetryingIl s'agit d'un package d'extension de la bibliothèque Google Guava. Il peut créer une machine de nouvelle tentative configurable pour tout appel de fonction. C'est un composant de nouvelle tentative flexible et pratique qui comprend une variété de stratégies de nouvelle tentative . Le plus important est qu'il est très facile à Définir un mécanisme de nouvelle tentative pour résoudre divers facteurs instables dans le système tout en surveillant les résultats et le comportement de chaque nouvelle tentative
Guava-retrying


Adresse gitHub : https://github.com/rholder/guava-retrying

1. Principales classes connexes

1.1 Classe Attemp

Attemp** Il s'agit d'une tentative de tâche (appel) et du résultat d'une requête **, qui enregistre principalement le nombre de tentatives de la requête en cours , si elle contient des exceptions et la valeur de retour de la requête .

Généralement utilisé avec un écouteur pour gérer les détails du processus de nouvelle tentative

Méthode d'essai décrire
long getAttemptNumber() Le nombre actuel de tentatives (le nombre de tentatives) commence à partir de 1
long getDelaySinceFirstAttempt() Délai depuis la première tentative, c'est-à-dire la différence de temps depuis la première tentative, en millisecondes
boolean hasException() Juger s'il y a une exception (vous pouvez réessayer en fonction de l'exception/valeur du résultat spécial)
boolean hasResult() Déterminer s'il faut renvoyer le résultat des données (réessayer d'atteindre la valeur de résultat spéciale)
Throwable getExceptionCause() throws IllegalStateException Données de nouvelle tentative anormales, obtenir des informations sur les exceptions
V getResult() throws IllegalStateException Obtenir des informations sur le résultat de la nouvelle tentative
V get() throws ExecutionException Similaire au getResult() retour des résultats de la nouvelle tentative, mais gère les exceptions différemment

1.2 Classe Retryer

Retryer Est la classe principale , utilisée pour implémenter la stratégie de nouvelle tentative , un groupe de classes est RetryerBuilder construit (créateur d'usine) et RetryerBuilder est responsable de l'ajout de la stratégie de nouvelle tentative définie à Retryer, et enfin implémente la stratégie de nouvelle tentative en exécutant Retryerla méthode principalecall


Le processus approximatif est le suivant :

  1. Déterminer si la limite de durée de la tâche est dépassée
  2. Exécuter l'écouteur de nouvelle tentative
  3. Déterminer si la condition de nouvelle tentative est remplie
  4. stratégie d'arrêt de nouvelle tentative
  5. stratégie d'attente de nouvelle tentative
  6. stratégie de blocage

callLe code source de la méthode est le suivant :

	public V call(Callable<V> callable) throws ExecutionException, RetryException {
     
     
        long startTime = System.nanoTime();
        for (int attemptNumber = 1; ; attemptNumber++) {
     
     
            Attempt<V> attempt;
            try {
     
     
                // 任务执行的时间限制
                V result = attemptTimeLimiter.call(callable);
                attempt = new ResultAttempt<V>(result, attemptNumber, TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime));
            } catch (Throwable t) {
     
     
                attempt = new ExceptionAttempt<V>(t, attemptNumber, TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime));
            }

            for (RetryListener listener : listeners) {
     
     
                listener.onRetry(attempt);
            }
            // 判断是否满足重试条件,来决定是否继续等待并进行重试
            if (!rejectionPredicate.apply(attempt)) {
     
     
                return attempt.get();
            }
            //重试停止 策略
            if (stopStrategy.shouldStop(attempt)) {
     
     
                throw new RetryException(attemptNumber, attempt);
            } else {
     
     
                // 重试等待 策略
                long sleepTime = waitStrategy.computeSleepTime(attempt);
                try {
     
     
                    // 根据重试等待计算的时间,执行阻塞策略
                    blockStrategy.block(sleepTime);
                } catch (InterruptedException e) {
     
     
                    // 线程中断,抛出异常
                    Thread.currentThread().interrupt();
                    throw new RetryException(attemptNumber, attempt);
                }
            }
        }
    }

RetryerBuilderIl s'agit d'un créateur d'usine qui peut personnaliser la source de nouvelle tentative et prendre en charge plusieurs sources de nouvelle tentative. Il peut définir Exception des objets d'exception et des objets d'assertion personnalisés via retryIfExceptionet retryIfResult , et prend en charge plusieurs et compatibles.

Propriétés de RetryerBuilderRetryerBuilder Properties décrire
retryIfExceptionOfType(Class<? extends Throwable> exceptionClass) Ne réessayez que lorsque certaines exceptions se produisent, telles queNullPointerException :retryIfExceptionOfType(Exception.class);
Predicateatteint par
retryIfException(Predicates.or(Predicates.instanceOf(NullPointerException.class)
Prédicats.instanceOf(NullPointerException.class)))
retryIfException Lever une runtimeexception, checkedl'exception sera réessayée, mais la levée errorne sera pas réessayée
retryIfRuntimeException runtimeRéessayer lorsqu'une exception est levée , ni checkedexception errorni nouvelle tentative
retryIfExceptionOfType(Error.class) Jetez seulement une erreur pour réessayer
retryIfResultVous pouvez spécifier Callablela méthode à réessayer lorsqu'elle renvoie une valeur
retryIfResult(Predicates.equalTo(false)) revenir falseen arrière réessayer
retryIfResult(Predicates.containsPattern("_customInfo$")) _customInfoRéessayez uniquement après avoir terminé par

1.3 RetryListener

Lorsque l'écouteur exécute callla méthode, il appelle la méthode dans l'écouteur RetryListener onRetryet implémente le mécanisme logique de nouvelle tentative personnalisé en implémentant et en réécrivant cette classe.

	@Beta
	public interface RetryListener {
    
    
		// 监听方法
	    <V> void onRetry(Attempt<V> var1);
	}



2. Stratégie d'attente de nouvelle tentative WaitStrategies

Lorsque l'exécution échoue, utilisez pour WaitStrategies spécifier différentes stratégies d'attente pour réessayer la nième fois

withWaitStrategyDifférentes stratégies d'attente de nouvelle tentative peuvent être définies via la méthode, et les stratégies courantes sont les suivantes :

2.1 ExponentialWaitStrategy Stratégie d'attente exponentielle (WaitStrategies.exponentialWait)

Implémentation de l'algorithme de compensation exponentielle ( wikipedia Exponential Backoff ), calcule le temps d'attente en fonction du nombre de tentatives, le code source est le suivant :

	@Override
	public long computeSleepTime(Attempt failedAttempt) {
     
     
    	double exp = Math.pow(2, failedAttempt.getAttemptNumber());
    	long result = Math.round(multiplier * exp);
    	if (result > maximumWait) {
     
     
    	    result = maximumWait;
    	}
    	return result >= 0L ? result : 0L;
	}
// 默认倍数(乘) multiplier = 1,最大值是 Long.MAX_VALUE
RetryerBuilder.newBuilder().withWaitStrategy(WaitStrategies.exponentialWait());
// 指定乘数multiplier 和 最大值,第一次失败后,依次等待时长:2^1 * 100、2^2 * 100、2^3 * 100...直到最多5分钟。 
// 5分钟后,每隔5分钟重试一次
// 3个参数, multiplier: 乘数, maximumTime: 最大等待时长, maximumTimeUnit: 最大等待时长单位
RetryerBuilder.newBuilder().withWaitStrategy(WaitStrategies.exponentialWait(100, 5, TimeUnit.MINUTES));

2.2 FibonacciWaitStrategy Stratégie d'attente de Fibonacci (WaitStrategies.fibonacciWait)

Après échec, attendre selon la suite de Fibonacci

// 默认乘数multiplier是1,最大值是Long.MAX_VALUE
RetryerBuilder.newBuilder().withWaitStrategy(WaitStrategies.fibonacciWait());
// 指定乘数multiplier 和 最大值,第一次失败后,依次等待时长,1*100、1*100、2*100、3*100、5*100...直到最多5分钟,5分钟后每隔5分钟重试一次
// 3个参数, multiplier: 乘数, maximumTime: 最大等待时长, maximumTimeUnit: 最大等待时长单位
RetryerBuilder.newBuilder().withWaitStrategy(WaitStrategies.fibonacciWait(100, 5, TimeUnit.MINUTES));

2.3 FixedWaitStrategy Stratégie d'attente à durée fixe (WaitStrategies.fixedWait)

Après un échec, il attendra un laps de temps fixe pour réessayer

// 每 100 ms 重试一次
RetryerBuilder.newBuilder().withWaitStrategy(WaitStrategies.fixedWait(100, TimeUnit.MILLISECONDS));

2.4 RandomWaitStrategy Stratégie d'attente aléatoire (WaitStrategies.randomWait)

En définissant l'intervalle de temps d'attente aléatoire, ou la durée maximale d'attente aléatoire, prenez-en un nombre aléatoire et réessayez à un moment aléatoire

// 最大随机时长10s
RetryerBuilder.newBuilder().withWaitStrategy(WaitStrategies.randomWait(10, TimeUnit.SECONDS));

// 随机区间配置,[2, 10] 2-10s随机等待,四个参数分别为: 
// minimumTime: 最小值,minimumTimeUnit: 最小值单位; maximumTime: 最大值, maximumTimeUnit: 最大值单位
RetryerBuilder.newBuilder().withWaitStrategy(WaitStrategies.randomWait(2, TimeUnit.SECONDS, 10, TimeUnit.SECONDS));

2.5 IncrementingWaitStrategy Stratégie d'attente incrémentielle (WaitStrategies.incrementingWait)

Selon la valeur initiale et la valeur incrémentielle, le temps d'attente augmente séquentiellement

// 递增配置,初始2s,后面每次在前面的基础上加3s,等待时长: 2、5、8、11、14、17、20
// 四个参数 >>> initialSleepTime: 初始等待时长,initialSleepTimeUnit: 初始等待时长单位, increment: 递增时长值, incrementTimeUnit: 递增时长单位
RetryerBuilder.newBuilder().withWaitStrategy(WaitStrategies.incrementingWait(2, TimeUnit.SECONDS, 3, TimeUnit.SECONDS))

2.6 ExceptionWaitStrategy stratégie d'attente d'exception (WaitStrategies.exceptionWait)

Spécifiez le temps d'attente pour une nouvelle tentative en fonction des informations sur l'exception de configuration. Si l'exception ne correspond pas, le temps d'attente est de 0.

// 当出现空指针异常时,等待1s,出现数组越界异常时等待2s (可以配置多个)
// 参数: exceptionClass: 异常类,Function<T, Long> function: 处理函数,出现对应异常,返回等待时长

RetryerBuilder<Object> builder = RetryerBuilder.newBuilder();
builder.withWaitStrategy(WaitStrategies.exceptionWait(NullPointerException.class, e -> 1000L));        builder.withWaitStrategy(WaitStrategies.exceptionWait(ArrayIndexOutOfBoundsException.class, e -> 2000L));

2.7 CompositeWaitStrategy stratégie d'attente composite 【***】(WaitStrategies.join)

Lorsqu'une ou plusieurs politiques d'attente sont satisfaites en même temps, le temps d'attente est la somme de toutes les politiques d'attente. Par exemple:

// 固定时长策略 + 异常等待策略,对于空指针异常,等待3s,其它情况等待2s
// join 组合多个策略
RetryerBuilder.newBuilder().withWaitStrategy(
                WaitStrategies.join(WaitStrategies.exceptionWait(NullPointerException.class, e -> 1000L),
                        WaitStrategies.fixedWait(2, TimeUnit.SECONDS)));



3. StopStrategies réessayer la stratégie d'arrêt

Arrêtez de réessayer après avoir spécifié le nombre de tentatives . Il est préférable de les configurer toutes, sinon il peut y avoir un nombre illimité de tentatives . withStopStrategyVous pouvez définir la stratégie d'arrêt des nouvelles tentatives à l'aide de la méthode

3.1 Nouvelle tentative illimitée NeverStopStrategy (StopStrategies.neverStop)

// 无限次数重试,谨慎使用
RetryerBuilder.newBuilder().withStopStrategy(StopStrategies.neverStop());

3.2 StopAfterAttemptStrategy Réessayez le nombre de fois spécifié pour arrêter (StopStrategies.stopAfterAttempt)

// 重试五次结束
RetryerBuilder.newBuilder().withStopStrategy(StopStrategies.stopAfterAttempt(5));

3.3 StopAfterDelayStrategy se termine après une nouvelle tentative pendant une période de temps spécifiée (StopStrategies.stopAfterDelay)

// 10s重试,超过10s结束
RetryerBuilder.newBuilder().withStopStrategy(StopStrategies.stopAfterDelay(10, TimeUnit.SECONDS));



4 Limite de temps d'exécution de la tâche AttemptTimeLimiters (withAttemptTimeLimiter)

Indique la limite de temps d'exécution d'une seule tâche [si un délai d'exécution de tâche unique se produit, l'exécution de la tâche de nouvelle tentative en cours sera terminée], et la limite de withAttemptTimeLimitertemps d'exécution de la tâche est définie par la méthode

4.1 FixedAttemptTimeLimit spécifie la limite de temps d'exécution (AttemptTimeLimiters.fixedTimeLimit)

Spécifiez la limite de temps d'exécution de la tâche. Afin de contrôler la gestion des threads, il est préférable de spécifier le pool de threads correspondant

// 重试方法超过2s中断
RetryerBuilder.newBuilder().withAttemptTimeLimiter(AttemptTimeLimiters.fixedTimeLimit(2, TimeUnit.SECONDS));
RetryerBuilder.newBuilder().withAttemptTimeLimiter(AttemptTimeLimiters.fixedTimeLimit(2, TimeUnit.SECONDS), Executors.newCachedThreadPool());

4.2 NoAttemptTimeLimit Pas de limite de temps (AttemptTimeLimiters.noTimeLimit)

RetryerBuilder.newBuilder().withAttemptTimeLimiter(AttemptTimeLimiters.noTimeLimit())



5. Stratégie de blocage BlockStrategies

Dans le processus de nouvelle tentative et d'attente, l' exécution est bloquée en fonction du temps calculé par la stratégie d'attente. Par
défaut, une seule stratégie de blocage est fournie : ThreadSleepStrategy, et la méthode de mise en œuvre est réalisée via Thread.sleep() la méthode de veille [Avantages de la méthode de veille : peut répondre aux demandes d'interruption externes]

La stratégie de blocage par défaut est le thread sleep, et vous pouvez personnaliser la stratégie de blocage. Ici, un spin lock est utilisé pour l'implémenter sans bloquer le thread.

public class GuavaBlockStrategy implements BlockStrategy {
     
     

    @Override
    public void block(long sleepTime) throws InterruptedException {
     
     

        long start = System.currentTimeMillis();
        long end = start;

        while (end - start <= sleepTime) {
     
     
            end = System.currentTimeMillis();
        }

        LogUtil.info("block end", start, end, sleepTime);
    }
}

utiliser:

// 自定义阻塞策略:自旋锁实现
RetryerBuilder.newBuilder().withBlockStrategy(new SpinBlockStrategy());

6. Utilisation du forfait

Référence d'utilisation réelle du projet : utilisation du package de nouvelle tentative de goyave

Je suppose que tu aimes

Origine blog.csdn.net/qq_40542534/article/details/128212025
conseillé
Classement