De fiecare dată când se execută un request, există șansa ca acesta să eșueze - fie din cauza unei conexiuni instabile, fie din cauza unui server temporar indisponibil. În astfel de situații, o strategie comună este să reîncercăm operațiunea, oferind aplicației noastre șansa de a depăși erorile temporare. Dar cum gestionăm acest proces fără să scriem cod redundant sau să pierdem din claritatea aplicației? Aici intervine Spring Retry, o bibliotecă puternică și flexibilă, integrată perfect în ecosistemul Spring Boot, care automatizează și simplifică reîncercarea operațiunilor eșuate.
Mai întâi avem nevoie de următoarele dependențe în fișierul nostru pom.xml:
<dependency>
<groupId>
org.springframework.retry
</groupId>
<artifactId>
spring-retry
</artifactId>
<version>2.0.3</version>
</dependency>
<dependency>
<groupId>
org.springframework
</groupId>
<artifactId>
spring-aspects
</artifactId>
<version>6.1.5</version>
</dependency>
De asemenea, vom adăuga @EnableRetry
în clasa de configurări ce conține deja @Configuration
, pentru a crea un proxy din clasele care au metode de tip retryable.
@EnableRetry
public class YourApplication {
public static void main(
String[] args) {
SpringApplication
.run(YourApplication.class, args);
}
}
De obicei, folosim \@Retryable atunci când facem un apel la un serviciu care poate fi indisponibil la un anumit moment și dorim să încercăm din nou să ne conectăm la acesta după o perioadă.
Exemplu:
@Retryable(
retryFor = {
ConnectException.class,
ResourceAccessException.class
},
maxAttempts = 5,
backoff = @Backoff(value = 1000,
multiplier = 2))
public void callAPI(String arg)
throws ConnectException,
ResourceAccessException {
//cod pentru retry...
}
Putem configura următorii parametri:
retryFor: lista de excepții pentru care se va încerca din nou apelul.
maxAttempts: de câte ori ar trebui să fie apelată metoda (valoarea default este 3).
backOff: cât de mult ar trebui să așteptăm între fiecare încercare, valoarea trebuie specificată în milisecunde.
Odată ce s-a executat numărul maxim de încercări, putem să ne revenim din eroarea întâlnită prin crearea unei metode de recuperare, marcată cu @Recover
care va fi apelată la final. Rețineți că, dacă o încercare este reușită, nu se va mai apela următoarea încercare și nu se va apela metoda de recuperare.
Exemplu:
@Recover
public void recover(){
//cod pentru recuperare
}
Eșecuri legate de rețea
Apeluri de servicii: când apelăm API-uri și microservicii, pot apărea probleme temporare de rețea
Folosirea brokerul de mesaje
Blocări ale resurselor
Deadlock-uri: când mai multe procese doresc să acceseze aceeași resursă și se creează un deadlock (blocaj), e indicată o perioadă de așteptare înainte de a apela din nou serviciul
Acces la resurse externe
Servere de configurare: aplicația obține configurări de la un server de configurare extern (Spring Cloud Config Server), iar operațiunea retry poate ajuta dacă serverul este temporar indisponibil
Indisponibilitatea serviciilor apelate
Arhitectură de microservicii: într-o arhitectură de microservicii, unde serviciile pot fi temporar suprasolicitate sau în mentenanță
Procesare asincronă
Background jobs: aplicația procesează joburi de fundal, folosind Spring Batch sau un instrument similar
Eșecuri ale dependințelor externe
Integrarea cu un serviciu de e-mail: aplicația trimite e-mailuri printr-un server SMTP extern
Eșecuri în descoperirea serviciilor
Indisponibilitatea temporară a cache-ului
RetryTemplate oferă o abordare programatică pentru implementarea operațiunii retry. Este mai flexibil și poate fi utilizat oriunde în cod, inclusiv în clase care nu sunt gestionate de Spring. Necesită să creezi manual instanța de RetryTemplate și să configurezi comportamentul său (maxAttempt, backoff, excepții etc.).
Exemplu:
RetryTemplate retryTemplate = RetryTemplate.builder()
.maxAttempts(5) // Maxim 5 încercări
.fixedBackoff(1000) // Pauză de 1 secundă între
// încercări
.retryOn(RuntimeException.class)
.build();
String result = retryTemplate.execute(context -> {
System.out.println("Execut operația... Încercare: "
+ context.getRetryCount());
if (context.getRetryCount() < 4) {
throw new RuntimeException("Eroare temporară");
}
return "Operație finalizată cu succes!";
});
System.out.println(result);
În exemplul de mai sus avem un număr maxim de încercări(maxAttempts(5)
), ceea ce înseamnă că operația va fi încercată de maximum 5 ori: 1 încercare inițială + 4 operațiuni retry. Dacă operația continuă să eșueze după 5 încercări, se aruncă o excepție. Prin configurarea (fixedBackoff(1000)
), se introduce o pauză de 1 secundă între încercări. Operațiunea retry pe RuntimeException (retryOn(RuntimeException.class)) va face următoarele: operațiunea retry se va declanșa doar dacă este aruncată o excepție de tip RuntimeException, iar orice altă excepție (sau finalizarea cu succes) va încheia procesul.
Dacă și a cincea încercare aruncă un RuntimeException
, RetryTemplate
nu mai face alte operațiuni retry și va arunca excepția către apelant. Rezultatul în acest caz ar fi o eroare și stack trace-ul excepției.
Caracteristică | @Retry |
RetryTemplate |
---|---|---|
Tip de implementare | Declarativă | Programatică |
Activare | Necesită \@EnableRetry și un bean | Poate fi utilizat oriunde în cod |
Spring | ||
Ușurință în utilizare | Simplu de folosit pentru metode de | Mai configurabil, dar necesită mai mult |
retry | cod | |
Flexibilitate | Limitat la metodele gestionate de Spring | Poate fi folosit în clase non-Spring |
Fallback ( @Recover ) |
Suport direct cu o metodă specifică. | Necesită gestionare manuală a |
fallback-ului | ||
Scenarii tipice | Apeluri service/metode gestionate de | Operații complexe sau apeluri |
Spring | non-Spring |
Pe scurt, \@Retry este mai simplu, dar mai puțin flexibil, iar RetryTemplate este mai versatil, dar necesită cod suplimentar.
Retry este în general util când operațiunea este probabil să reușească dacă este încercată din nou după o scurtă întârziere. Totuși, retry-ul excesiv poate duce la epuizarea resurselor și la probleme de performanță. Configurarea corectă a limitelor de retry, a întârzierilor și a gestionării excepțiilor este crucială pentru a asigura că mecanismul de retry nu duce la impact negativ asupra sistemului.
de Ovidiu Mățan
de Ovidiu Mățan
de Vlad Ionescu