Java >> Java Tutorial >  >> Tag >> Spring

So verwenden Sie den Leistungsschalter in der Spring Boot-Anwendung

In diesem Beitrag werde ich zeigen, wie wir das Circuit Breaker-Muster in einer Spring Boot-Anwendung verwenden können. Wenn ich Circuit-Breaker-Muster sage, ist es ein architektonisches Muster. Netflix hatte eine Hysterix-Bibliothek zum Umgang mit Leistungsschaltern veröffentlicht. Als Teil dieses Beitrags werde ich zeigen, wie wir mit resilence4j  ein Trennschaltermuster verwenden können Bibliothek in einer Spring Boot-Anwendung.

Außerdem habe ich kürzlich mein Buch „Simplifying Spring Security“ veröffentlicht. Wenn Sie mehr über Spring Security erfahren möchten, können Sie es hier kaufen.

Bild von Pixabay – Von Jürgen Diermaier

Was ist ein Leistungsschalter?

Der Begriff Leistungsschalter stammt aus der Elektrotechnik. In den meisten Stromnetzen sind Leistungsschalter Schalter, die das Netz vor Schäden durch Stromüberlastung oder Kurzschlüsse schützen.

In ähnlicher Weise stoppt in der Software ein Trennschalter den Anruf bei einem Remote-Dienst, wenn wir wissen, dass der Anruf bei diesem Remote-Dienst entweder fehlschlägt oder eine Zeitüberschreitung aufweist. Dies hat den Vorteil, Ressourcen zu sparen und proaktiv bei der Fehlerbehebung der Remoteprozeduraufrufe zu sein.

Der Trennschalter trifft die Entscheidung, den Anruf zu stoppen, basierend auf der vorherigen Historie der Anrufe. Es gibt jedoch alternative Möglichkeiten, wie es die Anrufe verarbeiten kann. Normalerweise werden frühere Anrufe nachverfolgt. Angenommen, 4 von 5 Aufrufen sind fehlgeschlagen oder abgelaufen, dann schlägt der nächste Aufruf fehl. Dies hilft, die Fehler mit dem aufrufenden Dienst proaktiver zu behandeln, und der aufrufende Dienst kann die Antwort auf andere Weise verarbeiten, sodass Benutzer die Anwendung anders erleben als eine Fehlerseite.

Eine andere Möglichkeit, wie ein Schutzschalter handeln kann, besteht darin, dass Anrufe an den Remote-Service für eine bestimmte Zeitdauer fehlschlagen. Ein Trennschalter wird geöffnet und lässt den nächsten Anruf nicht zu, bis der Remote-Service den Fehler behoben hat.

Resilience4J-Bibliothek

Wir haben unseren Code, den wir Remote Service nennen. Das Sicherungsmodul aus resilience4j Die Bibliothek enthält einen Lambda-Ausdruck für einen Aufruf des Remote-Dienstes ODER einen supplier zum Abrufen von Werten aus dem Remote-Service-Aufruf. Ich werde dies als Teil des Beispiels zeigen. Der Leistungsschalter dekoriert diesen Remote-Service-Aufruf so, dass er Antworten verfolgen und Zustände umschalten kann.

Verschiedene Konfigurationen der Resilience4j-Bibliothek

Um das Leistungsschalterkonzept zu verstehen, werden wir uns verschiedene Konfigurationen ansehen, die diese Bibliothek bietet.

slidingWindowType() – Diese Konfiguration hilft grundsätzlich bei der Entscheidungsfindung, wie der Leistungsschalter funktionieren wird. Es gibt zwei Typen COUNT_BASED und TIME_BASED . COUNT_BASED Das Schiebefenster des Leistungsschalters berücksichtigt die Anzahl der Anrufe beim Fernservice, während TIME_BASED Das Schiebefenster des Leistungsschalters berücksichtigt die Anrufe bei der Fernwartung in einer bestimmten Zeitdauer.

failureRateThreshold() – Dies konfiguriert den Fehlerraten-Schwellenwert in Prozent. Wenn x Prozent der Anrufe fehlschlagen, wird der Leistungsschalter geöffnet.

slidingWindowSize() – Diese Einstellung hilft bei der Entscheidung, wie viele Anrufe beim Schließen eines Leistungsschalters berücksichtigt werden sollen.

slowCallRateThreshold() – Konfiguriert den Schwellenwert für langsame Anrufe in Prozent. Wenn x Prozent der Anrufe langsam sind, wird der Leistungsschalter geöffnet.

slowCallDurationThreshold – Schwellenwert für die Zeitdauer, ab dem Anrufe als langsam gelten.

minimumNumberOfCalls() – Eine Mindestanzahl von Anrufen, die erforderlich sind, bevor der Leistungsschalter die Fehlerrate berechnen kann.

ignoreException() – Mit dieser Einstellung können Sie eine Ausnahme konfigurieren, die ein Trennschalter ignorieren kann und die nicht für den Erfolg oder Misserfolg eines Anrufs des Fernservices zählt.

waitDurationInOpenState() – Dauer, die der Leistungsschalter im geöffneten Zustand bleiben soll, bevor er in einen halb geöffneten Zustand übergeht. Der Standardwert ist 60 Sekunden.

Zählbasierter Leistungsschalter

Bei Verwendung von resilience4j Bibliothek kann man immer die Standardkonfigurationen verwenden, die der Leistungsschalter anbietet. Standardkonfigurationen basieren auf dem COUNT-BASED Sliding Window-Typ.

Wie erstellen wir also einen Leistungsschalter für den Schiebefenstertyp COUNT-BASED?


      CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom()
                .slidingWindowType(CircuitBreakerConfig.SlidingWindowType.COUNT_BASED)
                .slidingWindowSize(10)
                .slowCallRateThreshold(65.0f)
                .slowCallDurationThreshold(Duration.ofSeconds(3))
                .build();

        CircuitBreakerRegistry circuitBreakerRegistry =
                CircuitBreakerRegistry.of(circuitBreakerConfig);

        CircuitBreaker cb = circuitBreakerRegistry.circuitBreaker("BooksSearchServiceBasedOnCount");

Im obigen Beispiel erstellen wir eine Leistungsschalterkonfiguration, die ein Schiebefenster vom Typ COUNT_BASED enthält . Dieser Leistungsschalter zeichnet das Ergebnis von 10 Anrufen auf, um den Leistungsschalter auf closed umzuschalten Zustand. Wenn 65 % der Anrufe langsam sind und die Verlangsamung länger als 3 Sekunden dauert, wird der Leistungsschalter geöffnet.

CircuitBreakerRegistry ist eine Fabrik zur Herstellung eines Leistungsschalters.

Zeitbasierter Trennschalter

Jetzt auf Time-Based Leistungsschalter.


       CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom()
                .slidingWindowType(CircuitBreakerConfig.SlidingWindowType.TIME_BASED)
                .minimumNumberOfCalls(3)
                .slidingWindowSize(10)
                .failureRateThreshold(70.0f)
                .build();

        CircuitBreakerRegistry circuitBreakerRegistry =
                CircuitBreakerRegistry.of(circuitBreakerConfig);

        CircuitBreaker cb = circuitBreakerRegistry.circuitBreaker("BookSearchServiceBasedOnTime");

Im obigen Beispiel erstellen wir eine Leistungsschalterkonfiguration, die ein Schiebefenster vom Typ TIME_BASED enthält . Der Leistungsschalter zeichnet den Ausfall von Anrufen nach mindestens 3 Anrufen auf. Wenn 70 Prozent der Anrufe fehlschlagen, wird der Leistungsschalter geöffnet.

Beispiel eines Leistungsschalters in einer Spring Boot-Anwendung

Wir haben die erforderlichen Konzepte zum Leistungsschalter behandelt. Jetzt werde ich zeigen, dass wir einen Leistungsschalter in einer Spring Boot-Anwendung verwenden können.

Auf der einen Seite haben wir eine REST-Anwendung BooksApplication das im Wesentlichen Details von Bibliotheksbüchern speichert. Auf der anderen Seite haben wir eine Anwendung Circuitbreakerdemo die die REST-Anwendung mit RestTemplate aufruft . Wir werden unseren REST-Aufruf durch den Leistungsschalter dekorieren.

BooksApplication speichert Informationen über Bücher in einer MySQL-Datenbanktabelle librarybooks . Der REST-Controller für diese Anwendung hat GET und POST Methoden.


package com.betterjavacode.books.controllers;

import com.betterjavacode.books.daos.BookDao;
import com.betterjavacode.books.models.Book;
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.ArrayList;
import java.util.List;
import java.util.Optional;

@CrossOrigin("https://localhost:8443")
@RestController
@RequestMapping("/v1/library")
public class BookController
{
    @Autowired
    BookDao bookDao;

    @GetMapping("/books")
    public ResponseEntity<List> getAllBooks(@RequestParam(required = false) String bookTitle)
    {
        try
        {
            List listOfBooks = new ArrayList<>();
            if(bookTitle == null || bookTitle.isEmpty())
            {
                bookDao.findAll().forEach(listOfBooks::add);
            }
            else
            {
                bookDao.findByTitleContaining(bookTitle).forEach(listOfBooks::add);
            }

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

            return new ResponseEntity<>(listOfBooks, HttpStatus.OK);
        }
        catch (Exception e)
        {
            return new ResponseEntity<>(null, HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }

    @GetMapping("/books/{id}")
    public ResponseEntity getBookById(@PathVariable("id") long id)
    {
        try
        {
            Optional bookOptional = bookDao.findById(id);

            return new ResponseEntity<>(bookOptional.get(), HttpStatus.OK);
        }
        catch (Exception e)
        {
            return new ResponseEntity<>(null, HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }

    @PostMapping("/books")
    public ResponseEntity addABookToLibrary(@RequestBody Book book)
    {
        try
        {
            Book createdBook = bookDao.save(new Book(book.getTitle(), book.getAuthor(),
                    book.getIsbn()));
            return new ResponseEntity<>(createdBook, HttpStatus.CREATED);
        }
        catch (Exception e)
        {
            return new ResponseEntity<>(null, HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }

    @PutMapping("/books/{id}")
    public ResponseEntity updateABook(@PathVariable("id") long id, @RequestBody Book book)
    {
        Optional bookOptional = bookDao.findById(id);

        if(bookOptional.isPresent())
        {
            Book updatedBook = bookOptional.get();
            updatedBook.setTitle(book.getTitle());
            updatedBook.setAuthor(book.getAuthor());
            updatedBook.setIsbn(book.getIsbn());
            return new ResponseEntity<>(bookDao.save(updatedBook), HttpStatus.OK);
        }
        else
        {
            return new ResponseEntity<>(HttpStatus.NOT_FOUND);
        }
    }

    @DeleteMapping("/books/{id}")
    public ResponseEntity deleteABook(@PathVariable("id") long id)
    {
        try
        {
            bookDao.deleteById(id);
            return new ResponseEntity<>(HttpStatus.NO_CONTENT);
        }
        catch (Exception e)
        {
            return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }
}

Auf der anderen Seite unsere Anwendung Circuitbreakerdemo verfügt über einen Controller mit Thymeleaf-Vorlage, sodass ein Benutzer in einem Browser auf die Anwendung zugreifen kann.

Zu Demonstrationszwecken habe ich CircuitBreaker in einer separaten Bean definiert, die ich in meiner Serviceklasse verwenden werde.


    @Bean
    public CircuitBreaker countCircuitBreaker()
    {
        CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom()
                .slidingWindowType(CircuitBreakerConfig.SlidingWindowType.COUNT_BASED)
                .slidingWindowSize(10)
                .slowCallRateThreshold(65.0f)
                .slowCallDurationThreshold(Duration.ofSeconds(3))
                .build();

        CircuitBreakerRegistry circuitBreakerRegistry =
                CircuitBreakerRegistry.of(circuitBreakerConfig);

        CircuitBreaker cb = circuitBreakerRegistry.circuitBreaker("BooksSearchServiceBasedOnCount");

        return cb;
    }

    @Bean
    public CircuitBreaker timeCircuitBreaker()
    {
        CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom()
                .slidingWindowType(CircuitBreakerConfig.SlidingWindowType.TIME_BASED)
                .minimumNumberOfCalls(3)
                .slidingWindowSize(10)
                .failureRateThreshold(70.0f)
                .build();

        CircuitBreakerRegistry circuitBreakerRegistry =
                CircuitBreakerRegistry.of(circuitBreakerConfig);

        CircuitBreaker cb = circuitBreakerRegistry.circuitBreaker("BookSearchServiceBasedOnTime");
        return cb;
    }

Ich habe zwei Beans definiert, eine für den anzahlbasierten Leistungsschalter und eine für den zeitbasierten.

Die BookStoreService enthält eine aufrufende BooksApplication und zeigt verfügbare Bücher an. Dieser Dienst sieht wie folgt aus:


@Controller
public class BookStoreService
{

    private static final Logger LOGGER = LoggerFactory.getLogger(BookStoreService.class);

    @Autowired
    public BookManager bookManager;

    @Autowired
    private CircuitBreaker countCircuitBreaker;

    @RequestMapping(value = "/home", method= RequestMethod.GET)
    public String home(HttpServletRequest request, Model model)
    {
        return "home";
    }

    @RequestMapping(value = "/books", method=RequestMethod.GET)
    public String books(HttpServletRequest request, Model model)
    {
        Supplier<List> booksSupplier =
                countCircuitBreaker.decorateSupplier(() -> bookManager.getAllBooksFromLibrary());

        LOGGER.info("Going to start calling the REST service with Circuit Breaker");
        List books = null;
        for(int i = 0; i < 15; i++)
        {
            try
            {
                LOGGER.info("Retrieving books from returned supplier");
                books = booksSupplier.get();
            }
            catch(Exception e)
            {
                LOGGER.error("Could not retrieve books from supplier", e);
            }
        }
        model.addAttribute("books", books);

        return "books";
    }
}

Wenn der Benutzer also auf die Bücherseite klickt, rufen wir Bücher von unserem BooksApplication-REST-Dienst ab.

Ich habe die Bean für countCircuitBreaker automatisch verdrahtet . Zu Demozwecken – ich werde den REST-Dienst 15 Mal hintereinander aufrufen, um alle Bücher zu erhalten. Auf diese Weise kann ich eine Unterbrechung auf meiner REST-Dienstseite simulieren.

Unser Leistungsschalter schmückt einen Lieferanten, der einen REST-Aufruf zum Remote-Service durchführt, und der Lieferant speichert das Ergebnis unseres Remote-Service-Aufrufs.

In dieser Demo rufen wir unseren REST-Dienst sequentiell auf, aber Remote-Dienstaufrufe können auch parallel erfolgen. Der Leistungsschalter verfolgt weiterhin die Ergebnisse, unabhängig von sequentiellen oder parallelen Aufrufen.

Demo

Schauen wir uns jetzt in einer Live-Demo an, wie der Leistungsschalter funktionieren wird. Mein REST-Dienst läuft auf Port 8443 und mein Circuitbreakerdemo Anwendung läuft auf Port 8743.

Zunächst starte ich beide Anwendungen und greife auf die Startseite von Circuitbreakerdemo zu Anwendung. Die Startseite enthält den Link zum Anzeigen aller Bücher aus dem Geschäft.

Um nun einige Fehler zu simulieren, habe ich den folgenden Code in meinen RestTemplate-Aufruf eingefügt, der grundsätzlich 3 Sekunden lang schläft, bevor er das Ergebnis des REST-Aufrufs zurückgibt.


    public List getAllBooksFromLibrary ()
    {
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.setContentType(MediaType.APPLICATION_JSON);

        ResponseEntity<List> responseEntity;
        long startTime = System.currentTimeMillis();
        LOGGER.info("Start time = {}", startTime);
        try
        {
            responseEntity= restTemplate.exchange(buildUrl(),
                    HttpMethod.GET, null, new ParameterizedTypeReference<List>()
                    {});
            if(responseEntity != null && responseEntity.hasBody())
            {
                Thread.sleep(3000);
                LOGGER.info("Total time to retrieve results = {}",
                        System.currentTimeMillis() - startTime);
                return responseEntity.getBody();
            }
        }
        catch (URISyntaxException | InterruptedException e)
        {
            LOGGER.error("URI has a wrong syntax", e);
        }

        LOGGER.info("No result found, returning an empty list");
        return new ArrayList<>();
    }

Kurz gesagt, meine Circuit-Breaker-Schleife ruft den Dienst oft genug an, um den Schwellenwert von 65 Prozent langsamer Anrufe mit einer Dauer von mehr als 3 Sekunden zu überschreiten. Einmal klicke ich auf den Link für here , erhalte ich das Ergebnis, aber mein Leitungsschutzschalter ist offen und lässt keine zukünftigen Anrufe zu, bis er entweder in half-open ist Staat oder closed Zustand.

Sie werden feststellen, dass wir eine Ausnahme CallNotPermittedException erhalten haben wenn der Sicherungsautomat im OPEN war Zustand. Außerdem wurde der Leistungsschalter geöffnet, als die 10 Anrufe durchgeführt wurden. Das liegt daran, dass unsere gleitende Fenstergröße 10 ist.

Auf andere Weise kann ich den Fehler simulieren, indem ich meinen REST-Dienst oder Datenbankdienst herunterfahre. Auf diese Weise können REST-Aufrufe länger als erforderlich dauern.

Lassen Sie uns nun den COUNT_BASED wechseln Trennschalter an TIME_BASED Leistungsschalter. In TIME_BASED Circuit Breaker, schalten wir unseren REST-Dienst nach einer Sekunde aus und klicken dann auf here Link von der Homepage. Wenn 70 % der Anrufe in den letzten 10 Sekunden fehlschlagen, öffnet sich unser Schutzschalter.

Da der REST-Dienst geschlossen ist, sehen wir die folgenden Fehler in Circuitbreakdemo Anwendung

Wir sehen die Anzahl der Fehler, bevor der Leistungsschalter in OPEN steht Zustand.

Eine Konfiguration können wir immer hinzufügen, wie lange wir den Leistungsschalter im geöffneten Zustand halten wollen. Für die Demo habe ich hinzugefügt, dass der Leistungsschalter 10 Sekunden lang in einem offenen Zustand bleibt.

Wie geht man mit OFFENEN Leistungsschaltern um?

Es stellt sich die Frage, wie gehen Sie mit OFFENEN Leistungsschaltern um? Zum Glück resilience4j bietet eine Fallback-Konfiguration mit Decorators Dienstprogramm. In den meisten Fällen können Sie dies immer so konfigurieren, dass das Ergebnis früherer erfolgreicher Ergebnisse abgerufen wird, sodass Benutzer weiterhin mit der Anwendung arbeiten können.

Schlussfolgerung

In diesem Beitrag habe ich behandelt, wie man einen Leistungsschalter in einer Spring Boot-Anwendung verwendet. Der Code für diese Demo ist hier verfügbar.

In dieser Demo habe ich nicht behandelt, wie diese Leistungsschalterereignisse als resilience4j überwacht werden Die Bibliothek ermöglicht das Speichern dieser Ereignisse mit Metriken, die man mit einem Überwachungssystem überwachen kann.

Wenn Ihnen dieser Beitrag gefallen hat, können Sie meinen Blog hier abonnieren.

Referenzen

  1. Resilience4J-Bibliothek – Resilience4J
  2. Leistungsschalter mit Resilience4j – Leistungsschalter

Java-Tag