Service Resiliency With Spring Cloud Netflix Hystrix

While developing spring cloud applications, we may have multiple microservices communicating with each other. A remote service can fail to return the response at any time. We need to achieve a service resiliency system, which should be able to handle these service call failures, and the Spring cloud modules Netflix Hystrix library provides this support.

In a client resilient application, client service will recover from crashing even if the remote service is not responding. This pattern is also called the client resiliency pattern.

In this article, we will learn how to implement client side service resiliency patterns like load balancing, circuit breakers, fall back, and bulkhead, etc using the Netflix Hystrix library.

Table of Contents

Client side load balancing

In the client-side load balancing technique, the load balancer sits between the service and the client. It handles the service invocation by using the list of available registered services.

We have learned about client-side load balancing using the Ribbon load balancer-backed RestTemplate in the previous article.

Circuit breaker

The circuit breaker pattern monitors the call to remote service, detects any slowness in receiving the response, and then breaks the connection if the remote service is failing to respond within the time slot. It is a fail-fast approach.

Client side circuit breaker with Netflix Hystrix

Spring Cloud Netflix Hystrix library provides circuit breaker support with fallback method support.

Create a Eureka server

Create a Eureka server. We use the eureka server as a service discovery registry. Other services can register to this service registry as a eureka client so that they can be available for service discovery.

Check out this article to know how to set up the eureka server.

Create a remote Eureka client service

Once the eureka server is set up, we can register other microservices into this eureka service registry.

Let us create a simple service with the name spring-boot-eureka-client and then add spring-cloud-starter-netflix-eureka-client, spring-boot-starter-web dependencies.

Below is the pom.xml file after creating the application.

<?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 http://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.1.6.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.asb.example</groupId>
    <artifactId>spring-boot-eureka-client</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-boot-eureka-client</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Greenwich.SR2</spring-cloud.version>
        <maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </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>
</project>

Add the following properties to the application.properties file under src/main/resources/ directory.

#set the application name, which is used for service registry
spring.application.name=my-client
#server port
server.port=8080

Update the spring boot bootstrap class with the below code.

@SpringBootApplication
@EnableEurekaClient
@RestController
public class SpringBootEurekaClientApplication {

    @GetMapping("/call-me")
    public String method() throws InterruptedException {
        
        Thread.sleep(5000);
        return "You are calling me through service discovery!!";
    }

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

In the above code, we have created an endpoint called “/call-me“.

Also, notice the Thread.sleep() method, which adds a slight delay(delay of 5 seconds) before returning the response to the caller.

Create a service with Hystrix circuit breaker

Let us create another service to invoke the endpoint “/call-me” of the eureka client service, which we created earlier.

Create a service with the name spring-boot-hystrix-example and then add the spring-cloud-starter-netflix-hystrix, spring-cloud-starter-netflix-eureka-client, and the spring-boot-starter-web dependencies.

Below is the complete content of the pom.xml file.

<?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 http://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.1.7.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.asb.example</groupId>
    <artifactId>spring-boot-hystrix-example</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-boot-hystrix-example</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Greenwich.SR2</spring-cloud.version>
        <maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </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>
</project>

We have added eureka client dependency to enable Netflix ribbon load balancer. We are also going to use the ribbon-baked RestTemplate to invoke the external endpoint /call-me.

Enable Hystrix circuit breaker

The next step is to add the @EnableCircuitBreaker annotation to our spring boot bootstrap class.

The @EnableCircuitBreaker annotation enables the circuit breaker functionality to use in any of our application methods.

@SpringBootApplication
@EnableCircuitBreaker
public class SpringBootHystrixExampleApplication {
    //main method
}

To enable the circuit breaker in any of the methods in our application, we can use the @HystrixCommand annotation.

The @HystrixCommand annotation wraps the method with the Hystrix circuit breaker.

Finally, the below is the complete code of our application’s bootstrap class.

@SpringBootApplication
@EnableCircuitBreaker
@RestController
public class SpringBootHystrixExampleApplication {
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
    @Autowired
    private RestTemplate restTemplate;
    @GetMapping("/call-hystrix-client")
    @HystrixCommand
    public String method() {
        String response = restTemplate.getForObject("http://my-client/call-me/", String.class);
        return "Response : " + response;
    }
    public static void main(String[] args) {
        SpringApplication.run(SpringBootHystrixExampleApplication.class, args);
    }
}

Now run the applications in the below order.

  • Run the Eureka server.
  • Then, run the Eureka client application(spring-boot-eureka-client).
  • Finally, run the application spring-boot-hystrix-example.

Finally, we should be able to get the response as shown below.

Netflix hystrix circuit breaker example

This is a happy flow. We got the response as the remote service responded within the default timeout value.

Change the /call-me endpoint’s response delay to a higher value by modifying the code to Thread.sleep(11000);

Now hit the endpoint with the postman. We will receive the error as the remote service is not responding within the default circuit breaker timeout period.

hystrix circuit breaker failed
Customizing the circuit breaker

We can customize the circuit breaker timeout period by using the @HystrixProperty annotation.

Also, the below example shows how to set the circuit breaker timeout to 16 seconds.

@GetMapping("/call-hystrix-client-with-delay")
@HystrixCommand(commandProperties = {
@HystrixProperty(name = execution.isolation.thread.timeoutInMilliseconds", value = "16000") })
public String hystrixWithDelay() {
    String response = restTemplate.getForObject("http://my-client/call-me/", String.class);
    return "Response : " + response;
}

Client side fall back with Netflix Hystrix

The circuit breaker breaks the connection if the called service is not responding. We can use a fallback method to handle the circuit breaker exception and send a default response back to the service client.

To enable fallback functionality, we also need to create a fallback method in our controller class. Then we have to set the property fallbackMethod with our fallback method name as a value.

@GetMapping("/call-hystrix-client-with-fallback")
@HystrixCommand(commandProperties = {
    @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "16000") },
fallbackMethod = "fallBackMethod")
public String hystrixWithFallback() {
    String response = restTemplate.getForObject("http://my-client/call-me/", String.class);
    return "Response : " + response;
}
//fallback method:
public String fallBackMethod() {
    return "Looks like my-client service is not reponding!!";
}

Now check the service again. We should be able to get the fallback response.

Hystrix fallback example

We can also set default circuit breaker properties at a class level using the @DefaultProperties annotation.

@DefaultProperties(commandProperties = { @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "10000") })

Bulkhead with Netflix Hystrix

A microservice may call many external services. By default, these service calls use the current Java container’s thread for the program execution.

Also, this may cause the entire application to crash if a high volume of the load is present on the system, and service calls are using too many resources.

The bulkhead pattern also segregates the remote service calls into a separate thread pool and solves this problem.

Hystrix uses a thread pool for remote service calls. By default, this thread pool contains a size of 10 threads. This thread pool may not be sufficient for high-volume applications.

Hystrix also supports a mechanism, where each service call can have a segregated thread pool.

The below example shows the customized thread pool setup for our external service call.

@GetMapping("/call-hystrix-client-with-fallback")
@HystrixCommand(commandProperties = {
    @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "10000") }, fallbackMethod = "fallBackMethod", 
threadPoolKey = "myServicePool", 
threadPoolProperties = {
    @HystrixProperty(name = "coreSize", value = "40"),
    @HystrixProperty(name = "maxQueueSize", value = "10") })
public String hystrixWithFallback() {
    String response = restTemplate.getForObject("http://my-client/call-me/", String.class);
    return "Response : " + response;
}
  • threadPoolProperties: Allows us to define our custom thread pool behavior.
  • threadPoolKey: Allows us to define unique name for the thread pool.
  • coreSize: Allows us to define maximum threads available in the thread pool.
  • maxQueueSize: Allows us to set size of the thread queue.

Conclusion

In this article, we learned about different client-side service resiliency patterns like load balancing, circuit breaker, fallback, and bulkhead patterns.

We also learned how to use the spring cloud Netflix Hystrix library to achieve service resiliency patterns.

Sample example code is available on Github.

Service Resiliency With Spring Cloud Netflix Hystrix
Scroll to top

Discover more from ASB Notebook

Subscribe now to keep reading and get access to the full archive.

Continue reading