Ein Leitfaden für Spring Cloud Netflix - Hystrix

1. Übersicht

In diesem Tutorial behandeln wir Spring Cloud Netflix Hystrix - die Fehlertoleranzbibliothek. Wir werden die Bibliothek verwenden und das Unternehmensmuster "Leistungsschalter" implementieren, das eine Strategie gegen Fehler beschreibt, die auf verschiedenen Ebenen in einer Anwendung kaskadieren.

Das Prinzip ist analog zur Elektronik: Hystrix beobachtet Methoden, um Anrufe bei verwandten Diensten fehlzuschlagen. Wenn ein solcher Fehler auftritt, wird der Stromkreis geöffnet und der Aufruf an eine Fallback-Methode weitergeleitet.

Die Bibliothek toleriert Fehler bis zu einem Schwellenwert. Darüber hinaus bleibt der Stromkreis offen. Dies bedeutet, dass alle nachfolgenden Aufrufe an die Fallback-Methode weitergeleitet werden, um zukünftige Fehler zu vermeiden. Dadurch wird ein Zeitpuffer erstellt, in dem der zugehörige Dienst seinen fehlerhaften Status wiederherstellen kann.

2. REST-Produzent

Um ein Szenario zu erstellen, das das Leistungsschaltermuster demonstriert, benötigen wir zuerst einen Service. Wir werden es "REST Producer" nennen, da es Daten für den Hystrix-fähigen "REST Consumer" bereitstellt, die wir im nächsten Schritt erstellen werden.

Erstellen wir ein neues Maven-Projekt unter Verwendung der Spring-Boot-Starter-Web- Abhängigkeit:

 org.springframework.boot spring-boot-starter-web 2.2.6.RELEASE  

Das Projekt selbst ist bewusst einfach gehalten. Es besteht aus einer Controller-Schnittstelle mit einer mit @RequestMapping kommentierten GET-Methode, die einfach einen String zurückgibt , einem @RestController , der diese Schnittstelle implementiert, und einer @SpringBootApplication .

Wir beginnen mit der Schnittstelle:

public interface GreetingController { @GetMapping("/greeting/{username}") String greeting(@PathVariable("username") String username); }

Und die Umsetzung:

@RestController public class GreetingControllerImpl implements GreetingController { @Override public String greeting(@PathVariable("username") String username) { return String.format("Hello %s!\n", username); } }

Als nächstes schreiben wir die Hauptanwendungsklasse auf:

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

Um diesen Abschnitt zu vervollständigen, müssen Sie nur noch einen Anwendungsport konfigurieren, den wir abhören werden. Der Standardport 8080 wird nicht verwendet, da der Port für die im nächsten Schritt beschriebene Anwendung reserviert bleiben sollte.

Darüber hinaus definieren wir einen Anwendungsnamen, um unseren Produzenten anhand der Client-Anwendung nachschlagen zu können, die wir später vorstellen werden.

Lassen Sie uns dann einen Port angeben 9090 und einen Namen der Rest-Produzenten in unserer application.properties - Datei:

server.port=9090 spring.application.name=rest-producer

Jetzt können wir unseren Produzenten mit cURL testen:

$> curl //localhost:9090/greeting/Cid Hello Cid!

3. REST Consumer With Hystrix

Für unser Demonstrationsszenario implementieren wir eine Webanwendung, die den REST-Service aus dem vorherigen Schritt mithilfe von RestTemplate und Hystrix verwendet . Der Einfachheit halber nennen wir es den "REST-Verbraucher".

Infolgedessen erstellen wir ein neues Maven-Projekt mit Spring-Cloud-Starter- Hystrix , Spring-Boot-Starter-Web und Spring-Boot-Starter-Thymeleaf als Abhängigkeiten:

 org.springframework.cloud spring-cloud-starter-hystrix 1.4.7.RELEASE   org.springframework.boot spring-boot-starter-web 2.2.6.RELEASE   org.springframework.boot spring-boot-starter-thymeleaf 2.2.6.RELEASE 

Damit der Leistungsschalter funktioniert, durchsucht Hystix mit Annotationen versehene @ Component- oder @ Service- Klassen nach mit Annotationen versehenen @ HystixCommand- Methoden, implementiert einen Proxy dafür und überwacht seine Aufrufe.

Wir werden zuerst eine @ Service- Klasse erstellen , die in einen @ Controller eingespeist wird . Da wir eine Webanwendung mit Thymeleaf erstellen, benötigen wir auch eine HTML-Vorlage als Ansicht.

Dies wird unser injizierbarer @ Service sein , der einen @ HystrixCommand mit einer zugehörigen Fallback-Methode implementiert . Dieser Fallback muss dieselbe Signatur wie das Original verwenden:

@Service public class GreetingService { @HystrixCommand(fallbackMethod = "defaultGreeting") public String getGreeting(String username) { return new RestTemplate() .getForObject("//localhost:9090/greeting/{username}", String.class, username); } private String defaultGreeting(String username) { return "Hello User!"; } }

RestConsumerApplication wird unsere Hauptanwendungsklasse sein. Die Annotation @EnableCircuitBreaker durchsucht den Klassenpfad nach kompatiblen Leistungsschalterimplementierungen.

Um Hystrix explizit zu verwenden, müssen wir diese Klasse mit @EnableHystrix kommentieren :

@SpringBootApplication @EnableCircuitBreaker public class RestConsumerApplication { public static void main(String[] args) { SpringApplication.run(RestConsumerApplication.class, args); } }

Wir richten den Controller mit unserem GreetingService ein :

@Controller public class GreetingController { @Autowired private GreetingService greetingService; @GetMapping("/get-greeting/{username}") public String getGreeting(Model model, @PathVariable("username") String username) { model.addAttribute("greeting", greetingService.getGreeting(username)); return "greeting-view"; } }

Und hier ist die HTML-Vorlage:

   Greetings from Hystrix   

Um sicherzustellen, dass die Anwendung einen definierten Port überwacht, fügen wir Folgendes in eine application.properties- Datei ein:

server.port=8080

Um einen Hystix-Leistungsschalter in Aktion zu sehen, starten wir unseren Consumer und richten unseren Browser auf // localhost: 8080 / get-greeting / Cid . Unter normalen Umständen wird Folgendes angezeigt:

Hello Cid!

Um einen Fehler unseres Produzenten zu simulieren, stoppen wir ihn einfach. Nach Abschluss der Aktualisierung des Browsers sollte eine generische Meldung angezeigt werden, die von der Fallback-Methode in unserem @Service zurückgegeben wird :

Hello User!

4. REST Verbraucher mit Hystrix und Feign

Jetzt ändern wir das Projekt aus dem vorherigen Schritt, um Spring Netflix Feign anstelle von Spring RestTemplate als deklarativen REST-Client zu verwenden .

Der Vorteil ist, dass wir später unsere Feign Client-Oberfläche einfach umgestalten können, um Spring Netflix Eureka für die Serviceerkennung zu verwenden.

Um das neue Projekt zu starten, erstellen wir eine Kopie unseres Verbrauchers und fügen unseren Produzenten und Spring-Cloud-Starter-Feign als Abhängigkeiten hinzu:

 com.baeldung.spring.cloud spring-cloud-hystrix-rest-producer 1.0.0-SNAPSHOT   org.springframework.cloud spring-cloud-starter-feign 1.1.5.RELEASE 

Now, we’re able to use our GreetingController to extend a Feign Client. We'll implement Hystrix fallback as a static inner class annotated with @Component.

Alternatively, we could define a @Bean annotated method returning an instance of this fallback class.

The name property of the @FeignClient is mandatory. It is used, to look-up the application either by service discovery via a Eureka Client or by URL, if this property is given:

@FeignClient( name = "rest-producer" url = "//localhost:9090", fallback = GreetingClient.GreetingClientFallback.class ) public interface GreetingClient extends GreetingController { @Component public static class GreetingClientFallback implements GreetingController { @Override public String greeting(@PathVariable("username") String username) { return "Hello User!"; } } }

For more on using Spring Netflix Eureka for service discovery have a look at this article.

In the RestConsumerFeignApplication, we’ll put an additional annotation to enable Feign integration, in fact, @EnableFeignClients, to the main application class:

@SpringBootApplication @EnableCircuitBreaker @EnableFeignClients public class RestConsumerFeignApplication { public static void main(String[] args) { SpringApplication.run(RestConsumerFeignApplication.class, args); } }

We’re going to modify the controller to use an auto-wired Feign Client, rather than the previously injected @Service, to retrieve our greeting:

@Controller public class GreetingController { @Autowired private GreetingClient greetingClient; @GetMapping("/get-greeting/{username}") public String getGreeting(Model model, @PathVariable("username") String username) { model.addAttribute("greeting", greetingClient.greeting(username)); return "greeting-view"; } }

To distinguish this example from the previous, we'll alter the application listening port in the application.properties:

server.port=8082

Finally, we'll test this Feign-enabled consumer like the one from the previous section. The expected result should be the same.

5. Cache Fallback With Hystrix

Now, we are going to add Hystrix to our Spring Cloud project. In this cloud project, we have a rating service that talks to the database and gets ratings of books.

Let's assume that our database is a resource under demand, and its response latency might vary in time or might not be available in times. We'll handle this scenario with the Hystrix Circuit Breaker falling back to a cache for the data.

5.1. Setup and Configuration

Let us add the spring-cloud-starter-hystrix dependency to our rating module:

 org.springframework.cloud spring-cloud-starter-hystrix 

When ratings are inserted/updated/deleted in the database, we'll replicate the same to the Redis cache with a Repository. To learn more about Redis, check this article.

Let's update the RatingService to wrap the database querying methods in a Hystrix command with @HystrixCommand and configure it with a fallback to reading from Redis:

@HystrixCommand( commandKey = "ratingsByIdFromDB", fallbackMethod = "findCachedRatingById", ignoreExceptions = { RatingNotFoundException.class }) public Rating findRatingById(Long ratingId) { return Optional.ofNullable(ratingRepository.findOne(ratingId)) .orElseThrow(() -> new RatingNotFoundException("Rating not found. ID: " + ratingId)); } public Rating findCachedRatingById(Long ratingId) { return cacheRepository.findCachedRatingById(ratingId); }

Note that the fallback method should have the same signature of a wrapped method and must reside in the same class. Now when the findRatingById fails or gets delayed more than a given threshold, Hystrix fallbacks to findCachedRatingById.

As the Hystrix capabilities are transparently injected as AOP advice, we have to adjust the order in which the advice is stacked, in case if we have other advice like Spring's transactional advice. Here we have adjusted the Spring's transaction AOP advice to have lower precedence than Hystrix AOP advice:

@EnableHystrix @EnableTransactionManagement( order=Ordered.LOWEST_PRECEDENCE, mode=AdviceMode.ASPECTJ) public class RatingServiceApplication { @Bean @Primary @Order(value=Ordered.HIGHEST_PRECEDENCE) public HystrixCommandAspect hystrixAspect() { return new HystrixCommandAspect(); } // other beans, configurations }

Here, we have adjusted the Spring's transaction AOP advice to have lower precedence than Hystrix AOP advice.

5.2. Testing Hystrix Fallback

Now that we have configured the circuit, we can test it by bringing down the H2 database our repository interacts with. But first, let's run the H2 instance as an external process instead of running it as an embedded database.

Let's copy the H2 library (h2-1.4.193.jar) to a known directory and start the H2 server:

>java -cp h2-1.4.193.jar org.h2.tools.Server -tcp TCP server running at tcp://192.168.99.1:9092 (only local connections)

Let's now update our module's data source URL in rating-service.properties to point to this H2 server:

spring.datasource.url = jdbc:h2:tcp://localhost/~/ratings

We can start our services as given in our previous article from the Spring Cloud series, and test ratings of each book by bringing down the external H2 instance we are running.

We could see that when the H2 database is not reachable, Hystrix automatically falls back to Redis to read the ratings for each book. The source code demonstrating this use case can be found here.

6. Using Scopes

Normally a @HytrixCommand annotated method is executed in a thread pool context. But sometimes it needs to be running in a local scope, for example, a @SessionScope or a @RequestScope. This can be done via giving arguments to the command annotation:

@HystrixCommand(fallbackMethod = "getSomeDefault", commandProperties = { @HystrixProperty(name = "execution.isolation.strategy", value = "SEMAPHORE") })

7. The Hystrix Dashboard

A nice optional feature of Hystrix is the ability to monitor its status on a dashboard.

To enable it, we’ll put spring-cloud-starter-hystrix-dashboard and spring-boot-starter-actuator in the pom.xml of our consumer:

 org.springframework.cloud spring-cloud-starter-hystrix-dashboard 1.4.7.RELEASE   org.springframework.boot spring-boot-starter-actuator 2.2.6.RELEASE 

The former needs to be enabled via annotating a @Configuration with @EnableHystrixDashboard and the latter automatically enables the required metrics within our web application.

After we’ve done restarting the application, we’ll point a browser at //localhost:8080/hystrix, input the metrics URL of a Hystrix stream and begin monitoring.

Finally, we should see something like this:

Monitoring a Hystrix stream is something fine, but if we have to watch multiple Hystrix-enabled applications, it will become inconvenient. For this purpose, Spring Cloud provides a tool called Turbine, which can aggregate streams to present in one Hystrix dashboard.

Die Konfiguration der Turbine würde den Rahmen dieses Aufsatzes sprengen, aber die Möglichkeit sollte hier erwähnt werden. So ist es auch möglich, diese Streams per Messaging mithilfe des Turbine-Streams zu sammeln.

8. Fazit

Wie wir bisher gesehen haben, können wir das Leistungsschaltermuster jetzt mit Spring Netflix Hystrix zusammen mit Spring RestTemplate oder Spring Netflix Feign implementieren.

Dies bedeutet, dass wir Dienste mit eingeschlossenem Fallback unter Verwendung von Standarddaten nutzen und die Verwendung dieser Daten überwachen können.

Wie immer finden wir die Quellen auf GitHub.