Java >> Java tutorial >  >> Tag >> Spring

Spring Retry vs Resilience4j Retry

I dette indlæg vil jeg vise sammenligningen af ​​de to genforsøg – Spring Retry vs Resilience4j Retry. Normalt kan du kombinere genforsøg med en strømafbryder ved implementering for at gøre din applikation mere robust. Jeg har allerede dækket afbryderdemoen. Jeg har også opdateret min bog Simplifying Spring Security med Okta Demo, hvis du er interesseret i at lære mere om Spring Security.

Forårsforsøg vs Resilience4j Prøv igen

Spring Retry giver applikationer mulighed for automatisk at prøve en mislykket handling igen. I de fleste tilfælde, hvis din tjeneste ringer til en anden tjeneste, og en anden tjeneste af en eller anden grund ikke svarer, kan du bruge Spring Retry til at prøve den samme handling igen. Dette giver en anden måde at gøre din tjeneste mere tilgængelig på.

Prøv igen gør din applikation mere robust og mindre udsat for fejl. Du kan enten konfigurere Spring Retry på en metode, som du tror kan mislykkes, eller du kan konfigurere en RetryTemplate . Den nemme konfiguration gør Spring Retry til et nemmere valg, når du skriver kode.

På den anden side tilbyder Resilience4j Retry-modulet en lige så nemmere konfiguration – enten gennem kode eller gennem egenskaber.

I dette indlæg vil jeg vise, hvordan du bruger modulerne Spring Retry og Resilience4j Retry, når du kalder metoder eller tjenester.

Hvornår skal du bruge Prøv igen?

Normalt bør du overveje at prøve igen i visse scenarier.

  1. HTTP-kald til et REST-slutpunkt
  2. Sende eller hente beskeder fra SQS
  3. Fjernprocedureopkald eller en webtjeneste
  4. Hentning eller lagring af data fra databaser

I sådanne tilfælde kan vi enten smide en fejl, hvis vi ikke kan udføre operationen med succes. Men da tilgængeligheden af ​​applikationer bliver vigtigere, er disse fejl for det meste trivielle, og de fleste tjenester kommer online igen inden for et par millisekunder til sekunder.

Derfor er det fornuftigt at prøve igen. Du skal være forsigtig med, at den operation, du anvender, prøv igen med, skal være idempotent. Antag, at din ansøgning sendte en anmodning, og måltjenesten modtog anmodningen, men ind i mellem skete der noget, og din måltjeneste kunne ikke svare i tide. Så, med genforsøg, bør måltjenesten ikke behandle forsøget igen som en separat eller ny anmodning. Dette gør dit system mere modstandsdygtigt.

Forår Prøv igen

I dette afsnit vil jeg vise forskellige måder at bruge Spring Retry på. Til at starte med vil vi have en simpel Spring Boot REST-applikation til at hente en liste over virksomheder fra databasen. Som sædvanlig vil jeg ikke vise, hvordan man bygger en Spring Boot-applikation.

Gradafhængigheder

For at bruge Spring Retry skal vi bruge to afhængigheder i vores konfiguration.

        implementation 'org.springframework.retry:spring-retry:1.3.1'
	implementation 'org.springframework:spring-aspects:5.3.5'

Aktiver Retry Annotation

En gang har vi spring-retry afhængighed, vil vi være i stand til at annotere vores hovedklasse med annotation @EnableRetry som følger:


package com.betterjavacode.retrydemo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.retry.annotation.EnableRetry;
import org.springframework.retry.backoff.FixedBackOffPolicy;
import org.springframework.retry.policy.SimpleRetryPolicy;
import org.springframework.retry.support.RetryTemplate;


@SpringBootApplication
@EnableRetry
@EnableJpaRepositories(basePackages = "com.betterjavacode.retrydemo.daos")
public class RetrydemoApplication {

	public static void main(String[] args) {
		SpringApplication.run(RetrydemoApplication.class, args);
	}

	@Bean
	public RetryTemplate retryTemplate()
	{
		RetryTemplate retryTemplate = new RetryTemplate();

		FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy();
		backOffPolicy.setBackOffPeriod(100);

		SimpleRetryPolicy simpleRetryPolicy = new SimpleRetryPolicy();
		simpleRetryPolicy.setMaxAttempts(2);

		retryTemplate.setRetryPolicy(simpleRetryPolicy);
		retryTemplate.setBackOffPolicy(backOffPolicy);
		return retryTemplate;
	}


}

Jeg vil forklare resten af ​​koden efterhånden, men bemærk her annotationen @EnableRetry . Dette vil aktivere genforsøg i vores applikation.

REST-controller

Vi vil vise Spring Retry på to forskellige måder.

  1. Ved brug af @Retryable anmærkning
  2. Brug af RetryTemplate

Vores REST-controller vil hente os en liste over virksomheder, en virksomhed efter id eller en liste over virksomheder efter navn. Det vil se ud som nedenfor:


package com.betterjavacode.retrydemo.controllers;

import com.betterjavacode.retrydemo.dtos.CompanyDto;
import com.betterjavacode.retrydemo.service.CompanyService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/v1/betterjavacode/companies")
public class CompanyController
{
    @Autowired
    CompanyService companyService;

    @GetMapping
    public ResponseEntity<List> getAllCompanies()
    {
        List companyDtos = companyService.getAllCompanies();

        if(companyDtos.isEmpty())
        {
            return new ResponseEntity<>(HttpStatus.NO_CONTENT);
        }

        return new ResponseEntity<>(companyDtos, HttpStatus.OK);
    }

    @GetMapping("/{id}")
    public ResponseEntity getCompanyById(@PathVariable("id") long id)
    {
        CompanyDto companyDto = companyService.getCompany(id);
        if(companyDto == null)
        {
            return new ResponseEntity<>(HttpStatus.NO_CONTENT);
        }
        return new ResponseEntity<>(companyDto, HttpStatus.OK);
    }

    @GetMapping("/")
    public ResponseEntity<List> searchCompanies(@RequestParam("name") String companyName)
    {
        List companyDtos = companyService.searchCompanyByName(companyName);
        if(companyDtos.isEmpty())
        {
            return new ResponseEntity<>(HttpStatus.NO_CONTENT);
        }

        return new ResponseEntity<>(companyDtos, HttpStatus.OK);
    }
}

I vores controller bruger vi en @Service objekt kaldet CompanyService . Dette serviceobjekt giver os en måde at implementere vores metoder til at hente virksomhedsdata på.

Tjeneste med forskellige Prøv igen-konfigurationer

Så vi vil se, hvordan vi kan bruge annotation @Retryable :


    @Retryable(value = SQLException.class, maxAttempts = 2, backoff = @Backoff(delay = 100))
    public List getAllCompanies()
    {
        List companies =  companyRepository.findAll();
        List companyDtos = new ArrayList<>();
        for(Company company : companies)
        {
            CompanyDto companyDto = new CompanyDto(company.getName(), company.getType(),
                    company.getCity(), company.getState(), company.getDescription());
            companyDtos.add(companyDto);
        }

        return companyDtos;
    }

I ovenstående kode henter vi en liste over virksomheder. Hvis denne metode ikke kan hente resultatet med nogen undtagelse relateret til SQLException , vil vi prøve at hente igen. Vi vil prøve dette igen to gange som konfigureret med maxAttempts . Mellem hvert forsøg vil der være en forsinkelse på 100 millisekunder. Hvis vi nu kører vores applikation og kalder denne metode, vil vi se, hvordan dette genforsøg fungerer.

For at simulere fejlen vil jeg stoppe SQL Service fra Windows Services. Jeg vil vise et vellykket svar og et genforsøgt svar nedenfor:

Som du kan se på ovenstående skærmbillede, var der to forsøg på at prøve igen. I hvert genforsøg forsøgte den at oprette forbindelse til MySQL-serveren tre gange.

Hvad er Spring Boot Retry-skabelon?

På samme måde kan vi også bruge genforsøgsskabelon, som Spring-Retry tilbyder. I den følgende kode viser jeg en metode, som jeg har tilføjet i CompanyService for at få virksomhedsdata til et id.


    public CompanyDto getCompany(long id)
    {
        CompanyDto companyDto = retryTemplate.execute(rt -> {
           Company company = companyRepository.findById(id).get();
           CompanyDto localCompanyDto = new CompanyDto(company.getName(), company.getType(),
                   company.getCity(),
                   company.getState(), company.getDescription());
           return localCompanyDto;
        });

        return companyDto;
    }

Denne retryTemplate bean er konfigureret med simpleRetryPolicy med 2 forsøg og 100 millisekunders forsinkelse mellem hvert forsøg. Ikke desto mindre, hvis jeg prøver at udføre denne metode på samme måde, som jeg gjorde for @Retryable , vil vi se nedenstående output:

Som nævnt ovenfor er alt, hvad jeg gør, at stoppe min MySQL-tjeneste fra Windows-tjenester, og det gør det muligt for min metode at blive eksekveret for at prøve igen.

Er Prøv igen skabelontråd sikkert?

Prøv igen Template-klassen er trådsikker. Det tillader samtidig adgang. Til gengæld kan man udføre flere operationer.

Resilience4j Prøv igen

Mens du bruger resilience4j-retry bibliotek, kan du registrere en tilpasset global RetryConfig med en RetryRegistry Bygger. Brug dette register til at opbygge et Forsøg igen. I vores demo for at hente virksomhedsdata har vi tilføjet en ny metode til at hente virksomheder efter navn.

Denne metode vil se ud som nedenfor:


    public List searchCompanyByName(String name)
    {
        LOGGER.info("Search for company = {}", name);

        RetryConfig retryConfig =
                RetryConfig.custom().maxAttempts(4).waitDuration(Duration.of(2, SECONDS)).build();         

        RetryRegistry retryRegistry = RetryRegistry.of(retryConfig);

        Retry retryConfiguration = retryRegistry.retry("companySearchService", retryConfig);

        Supplier<List> companiesSupplier = () -> companyRepository.findAllByName(name);

        Supplier<List> retryingCompaniesSearch =
                Retry.decorateSupplier(retryConfiguration, companiesSupplier);

        List companyDtos = new ArrayList<>();
        List companies = retryingCompaniesSearch.get();
        LOGGER.info("Retrying..");
        for(Company company : companies)
        {
            CompanyDto companyDto = new CompanyDto(company.getName(), company.getType(),
                    company.getCity(), company.getState(), company.getDescription());
            companyDtos.add(companyDto);
        }

        return companyDtos;
    }

I ovenstående metode opretter vi først RetryConfig . Vi opretter en RetryRegistry og tilføj RetryConfig i dette register. Så når vi opretter vores opfordring til at hente en liste over virksomheder. Vi dekorerer dette opkald med retryConfiguration .

Tilpasninger med Resilience4j-Retry

RetryConfig tilbyder forskellig tilpasning:

  1. maxAttempts – 3 er standardantallet af forsøg for genforsøg.
  2. waitDuration – en fast ventetid mellem hvert genforsøg.
  3. intervalFunction – en funktion til at ændre venteintervallet efter en fejl.
  4. retryOnResultPredicate – konfigurerer et prædikat, der evaluerer, om et resultat skal prøves igen.
  5. retryExceptions – Konfigurerer en liste over kastbare klasser, der bruges til at prøve igen
  6. ignoreExceptions – Konfigurerer en liste over kastbare klasser, der ignoreres
  7. failAfterMaxRetries – En boolsk værdi for at aktivere eller deaktivere kast af MaxRetriesExceededException, når Retry har nået de konfigurerede maxAttempts

Demo

Lad os nu se på, hvad der sker, når vi udfører denne metode med resilience4j-retry . Følgende skærmbillede viser det vellykkede svar, når SQL-tjenesten stadig kører.

Hvis jeg stopper SQL-tjenesten, vil vi se genforsøg 4 gange, da vi har konfigureret det til 4.

Kode

Koden til denne demo kan findes i mit github-lager.

Konklusion

I dette indlæg viste jeg sammenligningen mellem Spring Retry vs Resilience4j Retry. Hvornår du skal bruge et af disse biblioteker afhænger af dit scenarie. Normalt går Resilience4j Retry godt, hvis du også planlægger et resilience4j afbrydermodul. Spring Retry kan også være praktisk med forskellige konfigurationer ved at bruge RetryTemplate .

Hvis du kunne lide dette indlæg, bedes du abonnere på min blog her.


Java tag