[JAVA] Informationen zur Fehlertoleranz von MicroProfile

Beim Aufbau eines Dienstes mit einem Mikrodienst ist es sehr wichtig, bei der Implementierung die Fehlertoleranz zu berücksichtigen. Wiederholungsrichtlinien, Schotte, Leistungsschalter usw. sind sehr wichtige Konzepte, die auch im Entwurfsmuster des Mikroservices definiert sind.

Die Fehlertoleranz von MicroProfile bietet die Funktionalität, die Sie zum Erstellen dieser fehlertoleranten Dienste benötigen. Die Implementierung ist basierend auf CDI-Annotationen einfach zu entwickeln und funktioniert mit CDI-Interceptors (Klassen müssen als CDI-Beans implementiert werden). Dies trennt den redundanten Code für Geschäftslogik und Fault Torerance für eine einfache Implementierung.

Die Fehlertoleranzrichtlinie von MicroProfile kann außerhalb der externen Einstellungen verwaltet werden, und die Richtlinienverwaltung kann auch mit MicroProfile Config durchgeführt werden.

Hauptmerkmale in der Fehlertoleranzspezifikation

Merkmale der Fehlertoleranz Zu verwendende Anmerkungen und eine kurze Beschreibung
1.Auszeit: @Verwenden Sie die Timeout-Annotation. Definiert die maximale Zeit, die für die Verarbeitung benötigt wird
2.wiederholen: @Verwenden Sie die Annotation "Wiederholen". Stellen Sie den Wiederholungsvorgang ein, wenn die Verarbeitung fehlschlägt
3.Zurückfallen: @Verwenden Sie die Fallback-Annotation. Bietet eine alternative Methode (Aufruf einer anderen Methode), wenn die Verarbeitung fehlschlägt
4.Schott(Partition): @Verwenden Sie die Schottanmerkung. Begrenzen Sie die Anzahl der gleichzeitigen Läufe. Wenn die Last hoch ist, konzentriert sich die Last auf einen einzelnen Prozess und die Reaktion wird reduziert, wodurch Kettenfehler für das gesamte System verhindert werden.
5.Leistungsschalter: @Verwenden Sie die CircuitBreaker-Annotation. Lässt den Prozessaufruf sofort automatisch fehlschlagen, wenn der Prozess wiederholt fehlschlägt
6.asynchron: @Verwenden Sie asynchrone Annotation. Machen Sie den Prozess asynchron

Wenn Sie eine der oben genannten Richtlinien anwenden möchten (mehrere Spezifikationen sind möglich), können Sie diese grundsätzlich festlegen, indem Sie der zu implementierenden Klasse oder Methode einfach eine Anmerkung hinzufügen.


1. Timeout-Richtlinie (@Timeout)

Durch Festlegen eines Zeitlimits können Sie verhindern, dass auf den Abschluss der Verarbeitung gewartet wird. Wenn Sie kein Zeitlimit festlegen, wenn ein Netzwerkfehler vorliegt oder wenn das Verbindungsziel überlastet ist und Sie keine sofortige Antwort zurückgeben können, sind die Verbindungspool-Worker-Threads des Anrufers erschöpft, was möglicherweise auch den Anrufer belastet. nicht. Legen Sie daher beim Implementieren mehrerer Mikrodienste oder beim Aufrufen eines externen Dienstes ein Zeitlimit für die Zusammenarbeit zwischen den einzelnen Diensten fest.

@Timeout(400) //Verbindungszeitlimitwert 400 ms(0.4 sec)
public Connection getConnectionForServiceA() {
   Connection conn = connectionService();
   return conn;
}

Die Annotation @Timeout kann auf Klassen- oder Methodenebene hinzugefügt werden. Wenn der Timeout-Wert erreicht ist, wird eine TimeoutException ausgelöst.

2. Wiederholen Sie die Richtlinie (@Retry)

Wenn Sie einen geringfügigen Netzwerkfehler haben oder keine Antwort von Ihrem Ziel erhalten, können Sie die Annotation @Retry verwenden, um den Prozessaufruf erneut zu versuchen.

In der Wiederholungsrichtlinie können Sie Folgendes konfigurieren:

Parameter Erläuterung
maxRetries: Maximale Anzahl von Wiederholungsversuchen
delay: Wiederholungsintervall
delayUnit: Verzögerungseinheit
maxDuration: Maximale Wiederholungsdauer
durationUnit: Dauereinheit
jitter: Zufällige Änderung der Wiederholungsverzögerung(Zeitabweichung des Taktsignals (oder Zyklus))
jitterDelayUnit: Jittereinheit
retryOn: Fehler beim erneuten Versuch(Exception, Error)Angeben
abortOn: Fehler beim Abbrechen(Exception, Error)Angeben

Die Annotation @Retry kann auf Klassen- oder Methodenebene hinzugefügt werden. Wenn sie einer Klasse hinzugefügt wird, wird sie auf alle in der Klasse vorhandenen Methoden angewendet. Beim Hinzufügen zu einer Methode wird nur die angegebene Methode als Ziel ausgewählt. Wenn Sie der Klasse und auch der Methode eine Anmerkung hinzufügen, werden die in der Methode angegebenen Einstellungen wirksam.

  1. Wenn der Prozess normal endet, wird das Ergebnis normal zurückgegeben.
  2. Wenn die ausgelöste Ausnahme mit abortOn angegeben wird, wird die ausgelöste Ausnahme erneut übertragen.
  3. Wenn Sie die ausgelöste Ausnahme mit retryOn angeben, wird der Methodenaufruf wiederholt.
  4. Senden Sie andernfalls die ausgelöste Ausnahme erneut

Es kann auch mit anderen Fehlertoleranzanmerkungen verwendet werden.

    /**
     * serviceA()Wenn in einem Methodenaufruf eine Ausnahme ausgelöst wird
     *Wenn die Ausnahme nicht IOException ist, versuchen Sie es erneut.
     */
    @Retry(retryOn = Exception.class, abortOn = IOException.class)
    public void invokeService() {
        callServiceA();
    }

    /**
     *Stellen Sie die maximale Anzahl von Wiederholungsversuchen auf 90 und die maximale Dauer von Wiederholungsversuchen auf 1000 Millisekunden ein
     *Wenn die maximale Anzahl von Wiederholungsversuchen erreicht ist, werden keine Wiederholungsversuche durchgeführt, selbst wenn die maximale Anzahl von Wiederholungsversuchen nicht erreicht wurde.
     */
    @Retry(maxRetries = 90, maxDuration= 1000)
    public void serviceB() {
        callServiceB();
    }

    /**
    *Taktfrequenzverschiebung(jitter)Angenommen, 400 ms-400 ms ~ 400 ms
    * 0 (delay - jitter) 〜 800ms (delay + jitter )Es wird erwartet, dass Wiederholungsversuche mit dem Unterschied von durchgeführt werden.
    *3200, unter der Annahme, dass die maximale Verzögerung auftritt/800=Bei 4 beträgt die Mindestanzahl von Versuchen 4 oder mehr.
    *Stellen Sie die maximale Anzahl von Versuchen so ein, dass 10 nicht überschritten werden
    */
    @Retry(delay = 400, maxDuration= 3200, jitter= 400, maxRetries = 10)
    public Connection serviceA() {
        return getConnectionForServiceA();
    }

3. Fallback-Richtlinie (@Fallback)

Die Annotation @Fallback kann auf Methodenebene angegeben werden. Wenn in der mit Anmerkungen versehenen Methode eine Ausnahme auftritt und diese endet, wird die in der Fallback-Methode angegebene Methode aufgerufen.

Die Annotation @Fallback kann allein oder mit anderen Annotationen zur Fehlertoleranz verwendet werden. In Verbindung mit anderen Anmerkungen wird der Fallback aufgerufen, nachdem alle anderen Fehlertoleranzoperationen ausgeführt wurden.

Wenn beispielsweise @Retry definiert ist, wird die Fallback-Verarbeitung ausgeführt, wenn die maximale Anzahl von Wiederholungsversuchen überschritten wird. Wenn @CircuitBreaker zusammen definiert ist, wird es sofort aufgerufen, wenn der Methodenaufruf fehlschlägt. Und wenn der Stromkreis offen ist, wird die Fallback-Methode aufgerufen.

3.1 Implementierungsbeispiel für die Fallback-Verarbeitung durch Implementierung von FallbackHandler

Definieren Sie eine Klasse (ServiceInvocationAFallbackHandler) für FallbackHandler, die die FallbackHandler-Schnittstelle implementiert. Implementieren Sie dann die alternative Verarbeitung innerhalb der Handle-Methode.

Hier wird MicroProfile Config verwendet, um die in der Eigenschaft von app.serviceinvokeA.FallbackReplyMessage oder der Umgebungsvariablen definierte Zeichenfolge zu beantworten.

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

Wenn die unten stehende BusinessLogicServiceBean # invokeServiceA () -Methode aufgerufen wird, wird hier intern eine RuntimeException ausgelöst, der Prozess wird jedoch dreimal wiederholt. Nachdem alle Wiederholungsversuche fehlgeschlagen sind, wird ServiceInvocationAFallbackHandler # handle () aufgerufen. Wird sein.

@RequestScoped
public class BusinessLogicServiceBean {

    //Geben Sie die Implementierungsklasse von FallbackHandler an@Fallback-Annotation hinzufügen
    //Maximale Anzahl von Wiederholungsversuchen(dreimal)Wenn es überschreitet, behandeln Sie FallbackHandler()Die Methode wird aufgerufen
    @Retry(maxRetries = 3)
    @Fallback(ServiceInvocationAFallbackHandler.class)
    public String invokeServiceA() {
        throw new RuntimeException("Connection failed");
        return null;
    }   
}

3.2 Implementierungsbeispiel für die Fallback-Verarbeitung mit der angegebenen Fallback-Methode

Schreiben Sie den Namen des alternativen Aufrufs direkt in die Annotation @Fallback.

Hier wird die fallbackForServiceB () -Methode als alternative Methode definiert.

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

4. Bulkhead-Richtlinie (@Bulkhead)

Das Schottmuster wird verwendet, um zu verhindern, dass sich einige Systemfehler im gesamten System ausbreiten und das gesamte System ausfällt. Die MicroProfile-Implementierung begrenzt die Anzahl gleichzeitiger Anforderungen, die auf eine Instanz zugreifen.

Das Schottmuster ist wirksam, wenn es auf Komponenten angewendet wird, die in großer Anzahl aufgerufen werden können, oder auf Dienste, die unter hoher Last eine schlechte Reaktion verursachen.

Die Annotation @Bulkhead kann auf Klassen- oder Methodenebene hinzugefügt werden. Wenn sie einer Klasse hinzugefügt wird, wird sie auf alle in der Klasse vorhandenen Methoden angewendet. Beim Hinzufügen zu einer Methode wird nur die angegebene Methode als Ziel ausgewählt. Wenn Sie der Klasse und auch der Methode eine Anmerkung hinzufügen, werden die in der Methode angegebenen Einstellungen wirksam.

Das Schott kann auf zwei Arten eingestellt werden.

  1. Thread-Pool-Isolation: (bei Verwendung mit @ Asynchronous Annotation) Legt die maximale Anzahl gleichzeitiger Anforderungen für die Größe der Warteschlange im Thread-Pool fest.
  2. Trennung von Semaphos: (wenn nicht mit @ Asynchronous Annotation verwendet) Es ist nur zulässig, die Anzahl der gleichzeitigen Anforderungen festzulegen.

4.1 Beispiel für die Trennung nach Thread-Pool

Bei Verwendung mit der Annotation @Asynchronous wird die Thread-Pool-Isolation angewendet. Im folgenden Beispiel sind bis zu 5 gleichzeitige Anforderungen zulässig und 8 Anforderungen werden in der Warteschlange gehalten.

//Bis zu 5 gleichzeitige Anforderungen zulässig, bis zu 8 Anforderungen in der Warteschlange zulässig
@Asynchronous
@Bulkhead(value = 5, waitingTaskQueue = 8)
public Future<Connection> invokeServiceA() {
   Connection conn = null;
   counterForInvokingServiceA++;
   conn = connectionService();
   return CompletableFuture.completedFuture(conn);
}

4.2 Beispiel für die Trennung durch Semapho

Wenn Sie die Annotation @Asynchronous nicht verwenden, definieren Sie einfach die Anzahl der gleichzeitigen Anforderungen.

@Bulkhead(5) //Bis zu 5 gleichzeitige Anfragen sind zulässig
public Connection invokeServiceA() {
   Connection conn = null;
   counterForInvokingServiceA++;
   conn = connectionService();
   return conn;
}

5. Richtlinie für Leistungsschalter (@CircuitBreaker)

Der Leistungsschalter verhindert wiederholte Aufrufe des fehlgeschlagenen Dienstes, sodass der fehlgeschlagene Dienst oder API-Aufruf sofort fehlschlägt. Wenn ein Serviceabruf häufig fehlschlägt, wird der Leistungsschalter geöffnet und es wird kein Anruf an diesen Service versucht, bis eine bestimmte Zeit verstrichen ist.

Die Annotation @CircuitBreaker kann auf Klassen- oder Methodenebene hinzugefügt werden. Wenn sie einer Klasse hinzugefügt wird, wird sie auf alle in der Klasse vorhandenen Methoden angewendet. Beim Hinzufügen zu einer Methode wird nur die angegebene Methode als Ziel ausgewählt. Wenn Sie der Klasse und auch der Methode eine Anmerkung hinzufügen, werden die in der Methode angegebenen Einstellungen wirksam.

Drei Zustände des Leistungsschalters

Geschlossen: (normale Zeit)

Normalerweise ist der Leistungsschalter geschlossen. Der Leistungsschalter verfolgt, ob jeder Anruf erfolgreich ist oder fehlschlägt, und verfolgt die neuesten Ergebnisse. Wenn die Ausfallrate das Fehlerverhältnis überschreitet, wird der Leistungsschalter geöffnet.

Öffnen: (wenn ein Fehler auftritt)

Wenn der Leistungsschalter geöffnet ist, schlagen Aufrufe von Diensten, die auf dem Leistungsschalter ausgeführt werden, sofort mit einer CircuitBreakerOpenException fehl. Nach einer Weile (konfigurierbar) geht der Leistungsschalter in den halboffenen Zustand über.

Halb offen: (Bestätigung der Notfallwiederherstellung)

Im halboffenen Zustand beginnen Serviceanrufversuche (konfigurierbare Nummer). Wenn einer der Anrufe fehlschlägt, kehrt der Leistungsschalter wieder in den geöffneten Zustand zurück. Wenn alle Versuche erfolgreich sind, geht der Leistungsschalter in den geschlossenen Zustand über.

Beispiel für die Implementierung eines Leistungsschalters 1

@CircuitBreaker(successThreshold = 10, requestVolumeThreshold = 4, failureRatio=0.5, delay = 1000)
public Connection serviceA() {
   Connection conn = null;
   counterForInvokingServiceA++;
   conn = connectionService();
   return conn;
}
Parameter Erläuterung
requestVolumeThreshold: Rollendes Fenster zur Verwendung, wenn der Leistungsschalter "geschlossen" ist(Anzahl der Nenner zur Berechnung der Fehlerquote)Größe
failureRatio: Fehlerquote im Rollfenster zum "Öffnen" des Leistungsschalters
successThreshold: Anzahl der Versuche, sich zu schließen, wenn der Leistungsschalter "halb offen" ist
delay and delayUnit: Zeit, den Leistungsschalter "offen" zu halten

Oben "öffnet" sich die Schaltung, wenn zwei (4 x 0,5) Fehler während vier aufeinanderfolgender Aufrufe auftreten. Dies ist die Anzahl der durch requestVolumeThreshold angegebenen rollenden Fenster. Die Schaltung bleibt 1.000 Millisekunden lang "offen", bevor sie auf "halb offen" wechselt. Nach 10 erfolgreichen Anrufen in "Half Open" wird der Stromkreis wieder "geschlossen".

Anfrage 1-Erfolg
Anfrage 2-Fehler
Anfrage 3-Erfolg
Anfrage 4-Erfolg
Anfrage 5-Fehler
Anfrage 6-CircuitBreakerOpenException

Bei der obigen Anforderung schlagen zwei der letzten vier Anforderungen fehl und das Fehlerverhältnis erreicht 0,5, sodass "Anforderung 5" den Schaltkreis öffnet und eine CircuitBreakerOpenException auslöst.

Ausnahmedefinition hinzugefügt, um Erfolg / Misserfolg zu berücksichtigen

Die Parameter failOn und skipOn werden verwendet, um zu bestimmen, welche Ausnahmen als fehlgeschlagen gelten, da sie bestimmen, ob der Leistungsschalter "offen" sein soll.

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

Wenn die für failOn angegebene Ausnahme auftritt, wird dies als Fehler betrachtet. Wenn die für skipOn angegebene Ausnahme auftritt, wird sie als erfolgreich angesehen.

6. Asynchrone (@ Asynchrone) Richtlinie

Die Hauptmerkmale der Fehlertoleranz sind in [Architektur] beschrieben (https://github.com/eclipse/microprofile-fault-tolerance/blob/master/spec/src/main/asciidoc/asynchronous.asciidoc). Dies sind die in den obigen Abschnitten 1-5 aufgeführten Funktionen. Die asynchrone Verarbeitung steht also nicht in direktem Zusammenhang mit der Fehlertoleranz. Die asynchrone Verarbeitung ist jedoch bei der verteilten Verarbeitung sehr wichtig. Durch die Kombination mit verschiedenen Fehlertoleranzfunktionen wurde sie in die Spezifikationen aufgenommen, um effektiver arbeiten zu können.

Quelle: Architektur

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.

Die Annotation @Asynchronous kann auf Klassen- oder Methodenebene hinzugefügt werden. Wenn sie einer Klasse hinzugefügt wird, wird sie auf alle in der Klasse vorhandenen Methoden angewendet. Beim Hinzufügen zu einer Methode wird nur die angegebene Methode als Ziel ausgewählt. Wenn Sie der Klasse und auch der Methode eine Anmerkung hinzufügen, werden die in der Methode angegebenen Einstellungen wirksam.

Wenn eine Methode mit der Annotation @Asynchronous aufgerufen wird, gibt sie sofort Future oder CompletionStage zurück. Der Rest der Methodenkörperverarbeitung wird in einem separaten Thread ausgeführt. Die zurückgegebene Future oder CompletionStage hat erst dann den richtigen Wert, wenn der asynchrone Prozess abgeschlossen ist. Wenn während der Verarbeitung eine Ausnahme auftritt, endet Future oder CompletionStage mit dieser Ausnahme. Wenn der Prozess erfolgreich abgeschlossen wurde, gibt Future oder CompletionStage einen Rückgabewert zurück (selbst Future oder CompletionStage).

@Asynchronous
public CompletionStage <Connection> serviceA(){
   Connection conn = null;
   counterForInvokingServiceA ++;
   conn = connectionService();
   return CompletableFuture.completedFuture(conn);
}

Im obigen Beispiel ist der Aufruf der serviceA-Methode asynchron. Der Aufruf von serviceA gibt CompletionStage zurück und der Methodenkörper wird in einem separaten Thread ausgeführt.

Hinweis: Beim Aufruf von @Asynchronous von einem CDI RequestScope muss das RequestScope während des asynchronen Methodenaufrufs aktiv sein. Methoden mit der Annotation @Asynchronous müssen Future oder CompletionStage des Pakets java.util.concurrent zurückgeben. Andernfalls erhalten Sie eine FaultToleranceDefinitionException.

So überschreiben Sie den im Quellcode beschriebenen Einstellwert

Wie wir in jedem Abschnitt gesehen haben, können Fehlertoleranzrichtlinien in den meisten Fällen mit Anmerkungen angewendet werden, mit einigen Ausnahmen. Wenn Sie nach der Implementierung des Quellcodes den durch die Annotation implementierten Wert ändern möchten, können Sie den Einstellungswert auch mit MicroProfile Config überschreiben.

Die Parameter in der Anmerkung können in den Konfigurationseigenschaften mithilfe der folgenden Namenskonvention überschrieben werden:

<classname>/<methodname>/<annotation>/<parameter>

Wenn Sie beispielsweise die Parameter, die im in einer bestimmten Methode angegebenen Zeitlimit oder in der Annotation "Wiederholen" angegeben sind, extern überschreiben möchten, schreiben Sie wie folgt in MicroProfile Config.

com.yoshio3.FaultToleranceService.resilient.ResilienceController/checkTimeout/Timeout/value=2000
com.yoshio3.FaultToleranceService.resilient.ResilienceController/checkTimeout/Retry/maxDuration=3000

Wenn Sie es auf die gesamte Klasse anwenden möchten, können Sie den Teil des Methodennamens löschen und wie unten gezeigt auf die gesamte Klasse anwenden.

com.yoshio3.FaultToleranceService.resilient.ResilienceController/Timeout/value=2000
com.yoshio3.FaultToleranceService.resilient.ResilienceController/Retry/maxDuration=3000

Wenn Sie dieselben Regeln auf den gesamten Code in Ihrem Projekt anwenden möchten, können Sie einfach die Anmerkungen und Parametereinstellungen auflisten.

Timeout/value=2000
Retry/maxDuration=3000

schließlich

Hier ist der Code zum Erstellen einer fehlertoleranten Anwendung, die die Fehlertoleranz von MicroProfile nutzt. Als Nächstes möchte ich eine Anwendung erstellen, die Fehlertoleranz verwendet und mehrere fehlertolerante Dienste in Azure verknüpft.

Recommended Posts

Informationen zur Fehlertoleranz von MicroProfile
Informationen zur Eclipse MicroProfile-Konfiguration
Über =