Lors de la création d'un service avec un micro-service, il est très important de tenir compte de la tolérance aux pannes lors de sa mise en œuvre. Les stratégies de nouvelle tentative, les cloisons, les disjoncteurs, etc. sont des concepts très importants qui sont également définis dans le modèle de conception de microservice.
La tolérance aux pannes de MicroProfile fournit les fonctionnalités dont vous avez besoin pour créer ces services tolérants aux pannes. L'implémentation est facile à développer sur la base des annotations CDI et fonctionne avec les intercepteurs CDI (les classes doivent être implémentées comme des beans CDI). Cela sépare le code redondant pour la logique métier et la correction des pannes et facilite sa mise en œuvre.
La politique de tolérance aux pannes de MicroProfile peut être gérée en dehors des paramètres externes, et la gestion des politiques peut également être effectuée à l'aide de MicroProfile Config.
Fonctionnalités fournies par Fault Tolerance | Annotations à utiliser et brève description |
---|---|
1.temps libre: | @Utilisez l'annotation Timeout. Définit le temps maximum requis pour le traitement |
2.recommencez: | @Utilisez l'annotation Réessayer. Définir l'opération de nouvelle tentative en cas d'échec du traitement |
3.Se retirer: | @Utilisez l'annotation de secours. Fournit une méthode alternative (appelant une autre méthode) lorsque le traitement échoue |
4.Cloison(Cloison): | @Utilisez l'annotation Bulkhead. Limitez le nombre d'exécutions simultanées. Par conséquent, lorsque la charge est élevée, la charge est concentrée sur un seul processus et la réponse est réduite, ce qui évite les pannes de chaîne sur l'ensemble du système. |
5.Disjoncteur: | @Utilisez l'annotation CircuitBreaker. Fait échouer automatiquement l'appel de processus immédiatement si le processus échoue à plusieurs reprises |
6.asynchrone: | @Utilisez l'annotation asynchrone. Rendre le processus asynchrone |
Fondamentalement, si vous souhaitez appliquer l'une des politiques ci-dessus (plusieurs spécifications sont possibles), vous pouvez la définir en ajoutant simplement une annotation à la classe ou à la méthode à implémenter.
En définissant un délai d'attente, vous pouvez éviter d'attendre la fin du traitement. Si vous ne définissez pas de délai d'expiration, en cas de défaillance du réseau ou si la destination de connexion est surchargée et que vous ne pouvez pas renvoyer une réponse immédiatement, les threads de travail du pool de connexions de l'appelant seront épuisés, ce qui peut également mettre une charge sur l'appelant. ne pas. Par conséquent, lors de l'implémentation de plusieurs microservices ou de l'appel d'un service externe, définissez un délai d'expiration pour la coopération entre chaque service.
@Timeout(400) //Valeur du délai de connexion 400 ms(0.4 sec)
public Connection getConnectionForServiceA() {
Connection conn = connectionService();
return conn;
}
L'annotation @Timeout peut être ajoutée au niveau de la classe ou de la méthode. Si la valeur du délai d'expiration est atteinte, une exception TimeoutException sera levée.
Si vous rencontrez une panne réseau mineure ou si vous n'obtenez pas de réponse de la destination, vous pouvez utiliser l'annotation @Retry pour réessayer l'appel de processus.
Vous pouvez configurer les éléments suivants dans la stratégie de nouvelle tentative:
Paramètres | La description |
---|---|
maxRetries: | Nombre maximum de tentatives |
delay: | Intervalle de réessai |
delayUnit: | unité de retard |
maxDuration: | Durée maximale de la nouvelle tentative |
durationUnit: | unité de durée |
jitter: | Changement aléatoire du délai de nouvelle tentative(Écart de synchronisation (ou cycle) du signal d'horloge) |
jitterDelayUnit: | unité de gigue |
retryOn: | Échec de la nouvelle tentative(Exception, Error)Spécifier |
abortOn: | Défaut d'annuler(Exception, Error)Spécifier |
L'annotation @Retry peut être ajoutée au niveau de la classe ou de la méthode, et lorsqu'elle est ajoutée à une classe, elle sera appliquée à toutes les méthodes existantes dans la classe. Lorsqu'elle est ajoutée à une méthode, seule la méthode spécifiée est ciblée. Si vous ajoutez une annotation dans la classe et également dans la méthode, les paramètres spécifiés dans la méthode prendront effet.
Il peut également être utilisé avec d'autres annotations de tolérance aux pannes.
/**
* serviceA()Lorsqu'une exception est levée dans un appel de méthode
*Si l'exception n'est pas IOException, réessayez.
*/
@Retry(retryOn = Exception.class, abortOn = IOException.class)
public void invokeService() {
callServiceA();
}
/**
*Définissez le nombre maximal de tentatives sur 90 et la durée maximale des tentatives sur 1000 millisecondes
*Lorsque le nombre maximum de tentatives est atteint, aucune tentative n'est effectuée même si le nombre maximum de tentatives n'a pas été atteint.
*/
@Retry(maxRetries = 90, maxDuration= 1000)
public void serviceB() {
callServiceB();
}
/**
*Décalage de fréquence d'horloge(jitter)En supposant 400 ms-400 ms à 400 ms
* 0 (delay - jitter) 〜 800ms (delay + jitter )On s'attend à ce que les tentatives soient effectuées avec la différence de.
*3200, en supposant que le délai maximum se produit/800=À 4, le nombre minimum d'essais est de 4 ou plus,
*Définissez le nombre maximum de tentatives pour ne pas dépasser 10
*/
@Retry(delay = 400, maxDuration= 3200, jitter= 400, maxRetries = 10)
public Connection serviceA() {
return getConnectionForServiceA();
}
L'annotation @Fallback peut être spécifiée au niveau de la méthode. Si une méthode annotée entraîne une exception et se ferme, la méthode spécifiée dans la méthode de secours est appelée.
L'annotation @Fallback peut être utilisée seule ou avec d'autres annotations Fault Tolerance. Lorsqu'il est utilisé avec d'autres annotations, le repli est appelé une fois que toutes les autres opérations de tolérance aux pannes ont été effectuées.
Par exemple, si @Retry est défini, le traitement de secours sera effectué si le nombre maximal de tentatives est dépassé. De plus, si @CircuitBreaker est défini ensemble, il sera appelé immédiatement si l'appel de méthode échoue. Et chaque fois que le circuit est ouvert, la méthode de secours est appelée.
Définissez une classe (ServiceInvocationAFallbackHandler) pour FallbackHandler qui implémente l'interface FallbackHandler. Ensuite, implémentez le traitement alternatif dans la méthode handle.
Ici, il est implémenté pour répondre à la chaîne de caractères définie par la propriété de app.serviceinvokeA.FallbackReplyMessage ou la variable d'environnement à l'aide de MicroProfile Config.
@Dependent
public class ServiceInvocationAFallbackHandler implements FallbackHandler<String> {
@ConfigProperty(name="app.serviceinvokeA.FallbackReplyMessage", defaultValue = "Unconfigured Default Reply")
private String replyString;
@Override
public String handle(ExecutionContext ec) {
return replyString;
}
}
Lorsque la méthode BusinessLogicServiceBean # invokeServiceA () ci-dessous est appelée, une RuntimeException est déclenchée en interne ici, mais le processus est retenté trois fois. Une fois toutes les tentatives échouées, ServiceInvocationAFallbackHandler # handle () est appelé. Sera.
@RequestScoped
public class BusinessLogicServiceBean {
//Spécifiez la classe d'implémentation de FallbackHandler@Ajouter une annotation de secours
//Nombre maximum de tentatives(3 fois)S'il dépasse, handle de FallbackHandler()La méthode s'appelle
@Retry(maxRetries = 3)
@Fallback(ServiceInvocationAFallbackHandler.class)
public String invokeServiceA() {
throw new RuntimeException("Connection failed");
return null;
}
}
Décrivez le nom de la méthode à appeler comme alternative directement dans l'annotation @Fallback.
Ici, la méthode fallbackForServiceB () est définie comme une méthode alternative.
@RequestScoped
public class BusinessLogicServiceBean {
@Retry(maxRetries = 3)
@Fallback(fallbackMethod= "fallbackForServiceB")
public String invokeServiceB() {
counterForInvokingServiceB++;
return nameService();
}
@ConfigProperty(name="app.serviceinvokeB.FallbackReplyMessage", defaultValue = "Unconfigured Default Reply")
private String replyString;
private String fallbackForInvokeServiceB() {
return replyString;
}
Le modèle de cloison est utilisé pour empêcher certaines défaillances du système de se propager dans tout le système et de provoquer une panne de l'ensemble du système. L'implémentation MicroProfile limite le nombre de requêtes simultanées qui accèdent à une instance.
Le modèle Bulkhead est efficace lorsqu'il est appliqué aux composants qui peuvent être appelés en grand nombre ou aux services qui entraînent une mauvaise réponse sous une charge élevée.
L'annotation @Bulkhead peut être ajoutée au niveau de la classe ou au niveau de la méthode, et lorsqu'elle est ajoutée à une classe, elle sera appliquée à toutes les méthodes existantes dans la classe. Lorsqu'elle est ajoutée à une méthode, seule la méthode spécifiée est ciblée. Si vous ajoutez une annotation dans la classe et également dans la méthode, les paramètres spécifiés dans la méthode prendront effet.
La cloison peut être réglée des deux manières suivantes.
Lorsqu'il est utilisé avec l'annotation @Asynchronous, l'isolation du pool de threads est appliquée. Dans l'exemple ci-dessous, jusqu'à 5 demandes simultanées sont autorisées et 8 demandes sont conservées dans la file d'attente.
//Jusqu'à 5 demandes simultanées autorisées, jusqu'à 8 demandes autorisées dans la file d'attente
@Asynchronous
@Bulkhead(value = 5, waitingTaskQueue = 8)
public Future<Connection> invokeServiceA() {
Connection conn = null;
counterForInvokingServiceA++;
conn = connectionService();
return CompletableFuture.completedFuture(conn);
}
Si vous n'utilisez pas l'annotation @Asynchronous, définissez simplement le nombre de requêtes simultanées.
@Bulkhead(5) //Jusqu'à 5 demandes simultanées sont autorisées
public Connection invokeServiceA() {
Connection conn = null;
counterForInvokingServiceA++;
conn = connectionService();
return conn;
}
Le disjoncteur empêche les appels répétés au service en échec de sorte que le service ou l'appel API échoué échoue immédiatement. Si un appel de service échoue fréquemment, le disjoncteur s'ouvre et aucun appel à ce service n'est tenté avant un certain laps de temps.
L'annotation @CircuitBreaker peut être ajoutée au niveau de la classe ou au niveau de la méthode, et lorsqu'elle est ajoutée à une classe, elle sera appliquée à toutes les méthodes existantes dans la classe. Lorsqu'elle est ajoutée à une méthode, seule la méthode spécifiée est ciblée. Si vous ajoutez une annotation dans la classe et également dans la méthode, les paramètres spécifiés dans la méthode prendront effet.
Normalement, le disjoncteur est fermé. Le disjoncteur garde une trace de la réussite ou de l'échec de chaque appel et garde la trace des derniers résultats. Lorsque le taux de défaillance dépasse le taux de défaillance, le disjoncteur s'ouvre.
Si le disjoncteur est ouvert, les appels aux services exécutés sur le disjoncteur échoueront immédiatement avec une exception CircuitBreakerOpenException. Après un certain temps (configurable), le disjoncteur passera à l'état semi-ouvert.
Dans l'état semi-ouvert, les tentatives d'appel de service commencent (numéro configurable). Si l'un ou l'autre des appels échoue, le disjoncteur reviendra à l'état ouvert. Si toutes les tentatives réussissent, le disjoncteur passera à l'état fermé.
@CircuitBreaker(successThreshold = 10, requestVolumeThreshold = 4, failureRatio=0.5, delay = 1000)
public Connection serviceA() {
Connection conn = null;
counterForInvokingServiceA++;
conn = connectionService();
return conn;
}
Paramètres | La description |
---|---|
requestVolumeThreshold: | Fenêtre roulante à utiliser lorsque le disjoncteur est «fermé»(Nombre de dénominateurs pour le calcul du taux de défaut)Taille |
failureRatio: | Rapport de défaut dans la fenêtre déroulante pour "ouvrir" le disjoncteur |
successThreshold: | Nombre de tentatives pour passer à fermé lorsque le disjoncteur est «à moitié ouvert» |
delay et delayUnit: | Il est temps de garder le disjoncteur «ouvert» |
Dans ce qui précède, le circuit «s'ouvre» lorsque deux (4 x 0,5) échecs se produisent pendant quatre appels consécutifs, qui est le nombre de fenêtres glissantes spécifié par requestVolumeThreshold. Le circuit reste «ouvert» pendant 1 000 millisecondes avant de passer à «semi-ouvert». Après 10 appels réussis en "Semi-Ouvert", le circuit sera à nouveau "Fermé".
Demande 1-Succès
Requête 2-Échec
Demande 3-Succès
Requête 4-Succès
Requête 5-Échec
Demande 6-CircuitBreakerOpenException
Pour la demande ci-dessus, deux des quatre dernières demandes échoueront et le failureRatio atteindra 0,5, donc "Request 5" ouvrira le circuit et lèvera une CircuitBreakerOpenException.
Les paramètres failOn et skipOn sont utilisés pour déterminer quelles exceptions sont considérées comme échouant car ils déterminent si le disjoncteur doit être "ouvert".
@CircuitBreaker(successThreshold = 10, requestVolumeThreshold = 4, failureRatio=0.5, delay = 1000,failOn = {ExceptionA.class, ExceptionB.class}, skipOn = ExceptionBSub.class))
public Connection serviceA() {
Connection conn = null;
counterForInvokingServiceA++;
conn = connectionService();
return conn;
}
Si l'exception spécifiée pour failOn se produit, elle est considérée comme un échec. Si l'exception spécifiée pour skipOn se produit, elle est considérée comme réussie.
Les principales fonctionnalités de Fault Tolerance sont décrites dans Architecture , Voici les fonctions répertoriées en 1-5 ci-dessus. Le traitement asynchrone n'est donc pas directement lié à la tolérance aux pannes. Cependant, le traitement asynchrone est très important dans le traitement distribué, et en le combinant avec diverses fonctions de tolérance aux pannes, il a été intégré dans les spécifications pour fonctionner plus efficacement.
As mentioned above, the Fault Tolerance specification is to focus on the following aspects:
- Timeout: Define a duration for timeout
- Retry: Define a criteria on when to retry
- Fallback: provide an alternative solution for a failed execution.
- CircuitBreaker: offer a way of fail fast by automatically failing execution to prevent the system overloading and indefinite wait or timeout by the clients.
- Bulkhead: isolate failures in part of the system while the rest part of the system can still function.
L'annotation @Asynchronous peut être ajoutée au niveau de la classe ou de la méthode, et lorsqu'elle est ajoutée à une classe, elle sera appliquée à toutes les méthodes existantes dans la classe. Lorsqu'elle est ajoutée à une méthode, seule la méthode spécifiée est ciblée. Si vous ajoutez une annotation dans la classe et également dans la méthode, les paramètres spécifiés dans la méthode prendront effet.
Dès qu'une méthode avec l'annotation @Asynchronous est appelée, elle renvoie Future ou CompletionStage. Le reste du traitement du corps de méthode est exécuté dans un thread séparé. Le Future ou CompletionStage retourné n'aura pas la valeur correcte tant que le processus asynchrone ne sera pas terminé. Si une exception se produit pendant le traitement, Future ou CompletionStage se terminera avec cette exception. Si le processus se termine avec succès, Future ou CompletionStage renvoie une valeur de retour (elle-même Future ou CompletionStage).
@Asynchronous
public CompletionStage <Connection> serviceA(){
Connection conn = null;
counterForInvokingServiceA ++;
conn = connectionService();
return CompletableFuture.completedFuture(conn);
}
Dans l'exemple ci-dessus, l'appel à la méthode serviceA est asynchrone. L'appel à serviceA renvoie CompletionStage et le corps de la méthode est exécuté dans un thread séparé.
Mise en garde: Lors de l'appel de @Asynchronous à partir d'un RequestScope CDI, le RequestScope doit être actif lors de l'appel de méthode asynchrone. Les méthodes avec l'annotation @Asynchronous doivent renvoyer le Future ou CompletionStage du package java.util.concurrent. Sinon, vous obtiendrez une FaultToleranceDefinitionException.
Comme nous l'avons vu dans chaque section, les stratégies de tolérance aux pannes peuvent être appliquées à l'aide d'annotations dans la plupart des cas, à quelques exceptions près. Après avoir implémenté le code source, si vous souhaitez modifier la valeur implémentée par l'annotation, vous pouvez également utiliser MicroProfile Config pour écraser la valeur du paramètre.
Les paramètres de l'annotation peuvent être remplacés dans les propriétés de configuration à l'aide de la convention de dénomination suivante:
<classname>/<methodname>/<annotation>/<parameter>
Par exemple, si vous souhaitez remplacer en externe les paramètres spécifiés dans le délai d'expiration spécifié dans une méthode spécifique ou dans l'annotation Réessayer, écrivez comme suit dans MicroProfile Config.
com.yoshio3.FaultToleranceService.resilient.ResilienceController/checkTimeout/Timeout/value=2000
com.yoshio3.FaultToleranceService.resilient.ResilienceController/checkTimeout/Retry/maxDuration=3000
Si vous souhaitez l'appliquer à toute la classe, vous pouvez supprimer la partie du nom de la méthode et l'appliquer à toute la classe comme indiqué ci-dessous.
com.yoshio3.FaultToleranceService.resilient.ResilienceController/Timeout/value=2000
com.yoshio3.FaultToleranceService.resilient.ResilienceController/Retry/maxDuration=3000
Et si vous souhaitez appliquer les mêmes règles à tout le code de votre projet, vous pouvez simplement lister les annotations et les paramètres.
Timeout/value=2000
Retry/maxDuration=3000
Voici le code pour créer une application tolérante aux pannes qui exploite MicroProfile Fault Tolerance. Ensuite, j'aimerais créer une application qui utilise Fault Tolerance et lier plusieurs services tolérants aux pannes sur Azure.