Bei der Arbeit mit verteilten Systemen geschieht es immer wieder, dass manche Services nicht erreichbar sind. Dies kann an einem ungeplanten Wartungsfenster, einer unterbrochenen Verbindung oder einer Fehlfunktion liegen. In solchen Fällen wünscht man sich als Entwickler eine einfache Möglichkeit die fehlgeschlagene Aktion zu wiederholen. Im Spring Boot Umfeld wird diese Möglichkeit durch Spring Retry bereitgestellt.
Bevor Spring Retry genutzt werden kann, müssen die entsprechenden Anhängigkeiten ins Projekt aufgenommen werden.
<dependency> <groupId>org.springframework.retry</groupId> <artifactId>spring-retry</artifactId> <version>1.3.1</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.3.9</version> </dependency>
Um eine fehlgeschlagene Aktion zu wiederholen gibt es mit Spring Retry einen deklarativen und einer imperativen Ansatz. Der hier vorgestellte deklarative Ansatz nutzt Annotationen an den Methoden, die bei einem Fehlschlag erneut ausgeführt werden sollen.
@Service public class ArchiveService { @Retryable public boolean checkRemoteAncestorArchive(String archive, Person person) { // check person } }
Die Methode checkRemoteAncestorArchive
im ArchiveService
prüft eine Person in der Ahnenverwaltung gegen ein anderes Ahnenarchiv. Dies geschieht immer dann, wenn eine Person mit einem externen GINA Verweis versehen wurde.
Die Annotation @Retryable
markiert die Methode für den Retry-Mechanismus. Damit der Mechanismus arbeitet muss er über die Annotation @EnableRetry
an einer Konfigurationsklasse aktiviert werden. wird nun in dieser Methode eine Exception
geworfen, dann wird diese Methode erneut aufgerufen. Dies geschieht standardmäßig drei mal mit einem Abstand von einer Sekunde.
Üblicherweise soll die Methode aber nicht bei jeder Art von Fehler wiederholt werden. Es sollte sich nur um temporäre Störungen in der Infrastruktur handeln. Verarbeitungsfehler durch falsche Daten werden sich durch erneute Versuche nicht ausmerzen lassen.
„Only fools repeat the same things over and over, expecting to obtain different results.“
George Bernard Shaw
Um Fehlerquellen zu unterscheiden können passende Exception
Klassen als Parameter der Annotation hinzugefügt werden.
@Retryable(ArchiveNotReachableException.class) public boolean checkRemoteAncestorArchive(String archive, Person person) { // check person }
In diesem Fall wird die Methode nur erneut aufgerufen, wenn sie aufgrund einer ArchiveNotReachableException
fehlschlug. In allen anderen Fällen wird kein weiterer Aufruf der Methode eingeleitet.
@Retryable(include=ArchiveNotReachableException.class, exclude=UnauthorizedOnArchiveException.class, maxAttempts = 5, backoff = @Backoff(delay = 2000, multiplier=2)) public boolean checkRemoteAncestorArchive(String archive, Person person) { // check person }
Das Verhalten des Retry-Mechanismus kann durch die Annotation aber noch weiter parametrisiert werden. In dem hier dargestellten Beispiel wird der Mechanismus nicht angewendet, wenn es sich bei der Exception
um eine UnauthorizedOnArchiveException
handelt. In diesem Fall kann ein Fehlschlag nur durch die Anpassung der Authentisierung korrigiert werden. Durch den Parameter maxAttempts
wird der erneute Aufruf der Methode auf 5 Versuche erhöht. Der Parameter backoff
sorgt für eine geänderte Wartezeit zwischen den Versuchen. Während standardmäßig jeweils eine Sekunde gewartet wird, sind es hier 2, 4, 8, 16 und 32 Sekunden.
In jedem der bisher dargestellten Beispiele endet die Reihe erfolgloser Versuche schlussendlich mit einem Fehler durch eine Exception
. Der Retry-Mechanismus liefert jedoch auch die Möglichkeit für einen sauberen Abschluss einer fehlschlagenden Aktion durch die @Recover
Annotation.
@Recover public boolean markOutstandingArchiveCheck(ArchiveNotReachableException exception, String archive, Person person) { // mark outstanding check }
Die @Recover
Annotation markiert eine Methode, die nach dem letzten und erfolglosen Versuch aufgerufen werden soll. Dabei muss diese Methode den identischen Rückgabewert wie die Originalmethode besitzen. Außerdem kann sie deren Parameter in korrekter Reihenfolge als eigene Parameter definieren. Zusätzlich sollte sie als ersten Parameter den Typ der Exception
besitzen. All diese Parameter werden genutzt um die Recover-Methode der richtigen Retry-Methode zuzuordnen. Der Rückgabewert der Recover-Methode ist darüber hinaus auch der Rückgabewert der Retry-Methode im Fehlerfall.
Im hier dargestellten Beispiel wird bei einer erfolglosen Überprüfung die entsprechende Person als noch nicht überprüft markiert. Solche Personeneinträge können dann zu einem späteren Zeitpunkt erneut zur Prüfung gegeben werden.
Damit ist der deklarative Ansatz von Spring Retry auch schon vorgestellt. Ein einfacher und eleganter Weg um temporäre Probleme einer Anwendung durch Wiederholungen zu korrigieren.