Complete microservices architecture with real examples - Step 5 - Circuit breaker with hystrix

Hi, guys! Welcome to Agnasarp! Today we came here with a very useful concept in the microservices architecture. In the home grid, there is a switch called "breaker" or "circuit breaker" which is automatically switched off while the electricity is flowing off. It will secure our home and everything not letting a bad thing happened. Likewise, we need to have a mechanism to secure our whole microservices echo system without hanging any service because definitely there may be service calls from one microservice to others. Therefore the same concept of circuit braking can be applied to this echo system. Here we have picked up Hystrix from several technologies. We have to setup Hystrix dashboard to see graphically what is going on behind the scene and configure the API gateway to support Hystrix.

Hystrix


Step 5 - Hystrix dashboard

Let's take a look at the project initialization with https://start.spring.io/

Spring Initialzr - Hystrix Dashboard

  • Project build tool: Maven
  • Language: Java
  • Spring boot: 2.3.10 (SNAPSHOT)
  • Project Metadata
    • Group: com.agnasarp
    • Artifact: agnasarp-hystrix-dashboard
    • Name: agnasarp-hystrix-dashboard
    • Description: Agnasarp Hystrix Dashboard
    • Package name: com.agnasarp.hystrixdashboard
    • Packaging: Jar
    • Java  version: 8
  • Dependencies:
    • Eureka Discovery Client: A REST based service for locating services for the purpose of load balancing and failover of middle-tier servers.
    • Hystrix Dashboard [Maintenance]: Circuit breaker dashboard with Spring Cloud Netflix Hystrix. In maintenance mode with no direct replacement.
Here, we have to pick the right Spring Boot version because Hystrix may not support for few higher versions of Spring Boot. Then, we will import the project into IntelliJ Idea.

Project structure

Project structure of hystrix dashboard in IntelliJ Idea

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
		 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.3.10.BUILD-SNAPSHOT</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.agnasarp</groupId>
	<artifactId>agnasarp-hystrix-dashboard</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>agnasarp-hystrix-dashboard</name>
	<description>Agnasarp Hystrix Dashboard</description>
	<properties>
		<java.version>1.8</java.version>
		<spring-cloud.version>Hoxton.BUILD-SNAPSHOT</spring-cloud.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
			<exclusions>
				<exclusion>
					<groupId>org.junit.vintage</groupId>
					<artifactId>junit-vintage-engine</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
	</dependencies>
	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>org.springframework.cloud</groupId>
				<artifactId>spring-cloud-dependencies</artifactId>
				<version>${spring-cloud.version}</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
		</dependencies>
	</dependencyManagement>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
	<repositories>
		<repository>
			<id>spring-milestones</id>
			<name>Spring Milestones</name>
			<url>https://repo.spring.io/milestone</url>
		</repository>
		<repository>
			<id>spring-snapshots</id>
			<name>Spring Snapshots</name>
			<url>https://repo.spring.io/snapshot</url>
			<snapshots>
				<enabled>true</enabled>
			</snapshots>
		</repository>
	</repositories>
	<pluginRepositories>
		<pluginRepository>
			<id>spring-milestones</id>
			<name>Spring Milestones</name>
			<url>https://repo.spring.io/milestone</url>
		</pluginRepository>
		<pluginRepository>
			<id>spring-snapshots</id>
			<name>Spring Snapshots</name>
			<url>https://repo.spring.io/snapshot</url>
			<snapshots>
				<enabled>true</enabled>
			</snapshots>
		</pluginRepository>
	</pluginRepositories>

</project>

application.yml

server:
  port: 8480

spring:
  application:
    name: HYSTRIX-DASHBOARD

hystrix:
  dashboard:
    proxy-stream-allow-list: "*"

eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://localhost:8761/eureka/
    instance:
      hostname: localhost

AgnasarpHystrixDashboardApplication.java

package com.agnasarp.hystrixdashboard;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;

@SpringBootApplication
@EnableEurekaClient
@EnableHystrixDashboard
public class AgnasarpHystrixDashboardApplication {

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

}

Now we have to do few modifications in API Gateway microservice as well. Those changes are highlighted below.

API Gateway modifications

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.10.BUILD-SNAPSHOT</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.agnasarp</groupId>
    <artifactId>agnasarp-cloud-gateway</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>agnasarp-cloud-gateway</name>
    <description>Agnasarp Cloud Gateway</description>
    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Hoxton.BUILD-SNAPSHOT</spring-cloud.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
    <repositories>
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
        </repository>
        <repository>
            <id>spring-snapshots</id>
            <name>Spring Snapshots</name>
            <url>https://repo.spring.io/snapshot</url>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </repository>
    </repositories>
    <pluginRepositories>
        <pluginRepository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
        </pluginRepository>
        <pluginRepository>
            <id>spring-snapshots</id>
            <name>Spring Snapshots</name>
            <url>https://repo.spring.io/snapshot</url>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </pluginRepository>
    </pluginRepositories>

</project>

application.yml

server:
  port:
    9191

spring:
  application:
    name: API-GATEWAY
  cloud:
    gateway:
      routes:
        - id: DEPARTMENT-SERVICE
          uri: lb://DEPARTMENT-SERVICE
          predicates:
            - Path=/departments/**
          filters:
            - name: CircuitBreaker
              args:
                name: DEPARTMENT-SERVICE
                fallbackuri: forward:/departmentServiceFallback
        - id: USER-SERVICE
          uri: lb://USER-SERVICE
          predicates:
            - Path=/users/**
          filters:
            - name: CircuitBreaker
              args:
                name: USER-SERVICE
                fallbackuri: forward:/userServiceFallback

hystrix:
  command:
    fallbackcmd:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 4000

management:
  endpoints:
    web:
      exposure:
        include: hystrix.stream

eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://localhost:8761/eureka/
    instance:
      hostname: localhost

AgnasarpCloudGatewayApplication.java

package com.agnasarp.cloudgateway;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;

@SpringBootApplication
@EnableEurekaClient
@EnableHystrix
public class AgnasarpCloudGatewayApplication {

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

}

And a new fallback controller has been added to forward the request when the 3rd party API is not available or unresponsive within the timeout defined in the application.yml file and the fallback method in the controller will give a meaningful message as below.

FallbackController.java

package com.agnasarp.cloudgateway.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class FallbackController {
    @GetMapping("/departmentServiceFallback")
    public String departmentServiceFallback(){
        return "DEPARTMENT-SERVICE is not available or unresponsive. Please try again later.";
    }

    @GetMapping("/userServiceFallback")
    public String userServiceFallback(){
        return "USER-SERVICE is not available or unresponsive. Please try again later.";
    }
}

If Service Registry, Department Service, User Service, API Gateway Service, and Hystrix Dashboard Service is up and running we can test whether our circuit breaker is working well or not. We can go to the Hystrix Dashboard with the below link.

http://localhost:8480/hystrix

Hystrix Dashboard

We can see the status of microservices configured in API Gateway via the below hystrix stream endpoint.

http://localhost:9191/actuator/hystrix.stream

Hystrix Stream

When all User Service and Department Service are working fine we can get successful response as below.

User Service - Success Scenario

Hystrix Dashboard will show the circuit as closed as below.

Hystrix Dashboard - Circuit Closed

Now we have down the Department Service which is being called by the User Service and try to call the User Service, so we got the below response hardcoded in the fallback method userServiceFallback.

User Service - Fail Scenario

Then the Hystrix Dashboard shows the status of our services graphically when the circuit opens.

Hystrix Dashboard - Circuit Opened

That is all for today. In summary, we have created a Hystrix Dashboard and configured API Gateway to support Hystrix to implement the circuit breaker pattern in our echo system. You can also try with these and all source code can be downloaded from GitHub using the below links. Hope you have grabbed something from this post and we will meet again with another important concept in microservices. Until then, bye bye!


0/Post a Comment/Comments

Previous Post Next Post