Spring Cloud : Hystrix

Hystrix is a library from netfix that  supports the circuit breaker pattern to prevent cascading failures. Whenever you have a large number of interacting services, there is always a possibility that one of them could be in a fail state for any reason. This could affect the services that are calling to this failed state service and could cause a ripple effect.

The principle is analogous to electronics. A household circuit breaker watches the circuit for any failures. When failure occurs (to much current flow), it “opens” the circuit (disconnecting the circuit) and thus isolating the failed area. Once the problem is resolved, you can manually “close” the circuit by flipping the switch. This prevents a cascade failure i.e your house burning down.

In the same way, Hystrix is watching methods for failing calls to related services. If there is such a failing method, it will open the circuit, thus isolating that service. The default behaviour is 20 failures in 10 secs. (I may be wrong, please check the Netflix hystrix config on github to check this) Now unlike the house circuit breaker hystrix will forward the call to a fallback method. You could think of this functionality as a catch block. Fallbacks can be chained like if this fails, use another and so on.  Hystrix then automatically closes itself after an interval. Default is 5 sec. These values ofcourse could be changed.

Lets see this in action. Suppose we have 2 service – offers-service and recommendation-service that are regster to Eureka.

hystrix6

Now suppose we have a microservice or a web-app that needs to call this service. Lets create a new application that will call these two. We will be using Feign but a Restemplate will also work with hystrix since its a method annotation. pom.xml – we just need one dependency for hytrix. Others are the usual ones


<?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>1.4.2.RELEASE</version>
<relativePath />
</parent>

<groupId>com.mynotes.spring.cloud</groupId>
<artifactId>feign-hystrix-client</artifactId>
<version>1.0.0-SNAPSHOT</version>
<name>feign-hystrix-client</name>
<description>Demo project for Spring Cloud cloud feign-hystrix-client</description>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Camden.SR3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
<dependency>
<groupId>com.mynotes.spring.cloud</groupId>
<artifactId>offer-service</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.mynotes.spring.cloud</groupId>
<artifactId>recommendation-service</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>

Enable Hystrix to the starter class FeignHystrixClientApplication.java


package com.mynotes.spring.cloud.feign;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.feign.EnableFeignClients;

@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
@EnableCircuitBreaker
public class FeignHystrixClientApplication {

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

}

OffersServiceClient and RecommendationServiceClient are just simple feign client interface.


package com.mynotes.spring.cloud.feign;

import java.util.List;

import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import com.mynotes.spring.cloud.service.Product;

@FeignClient("recommendation-service")
public interface RecommendationServiceClient {

@RequestMapping(value = "/getRecommendations", method = RequestMethod.GET)
public List<Product> getRecommendations();

}


package com.mynotes.spring.cloud.feign;

import java.util.List;

import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import com.mynotes.spring.cloud.service.Offers;

@FeignClient("offers-service")
public interface OffersServiceClient {

@RequestMapping(value = "/getCurrentOffers", method = RequestMethod.GET)
public List<Offers> getCurrentOffers();

}

Now lets write the Integration client where we will actually use hystrix


package com.mynotes.spring.cloud.feign;

import java.util.Arrays;
import java.util.Collection;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.mynotes.spring.cloud.service.Offers;
import com.mynotes.spring.cloud.service.Product;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;

@Component
public class IntegrationClient {

@Autowired
private OffersServiceClient offersClient;

@Autowired
private RecommendationServiceClient recommnedationClient;


public Collection<Product> getRecommendationFallback() {
System.out.println("=======getRecommendationFallback=========");
return Arrays.asList();
}

@HystrixCommand(fallbackMethod = "getRecommendationFallback")
public Collection<Product> getRecommendations() {
return this.recommnedationClient.getRecommendations();
}

public Collection<Offers> getOffersFallback() {
System.out.println("===========getOffersFallback===========");
return Arrays.asList();
}

@HystrixCommand(fallbackMethod = "getOffersFallback")
public Collection<Offers> getOffers() {
return this.offersClient.getCurrentOffers();
}

}

As you can see from above, getRecommendations() is the actual Rest call, It is annotated with @HystrixCommand which give a fallbackMethod to this meaning if for some reason recommendation service is down the fallback method will be called. There are many option in this Hystrix command like using @HystrixProperty we can overide some of the defaults that we talked about earlier (That also can be done from the properties files).
The Hystrix commands also gives us other options like how we want to invoke:

  • Synchronously – default behaviour. Its what we do most of the time. We execute the command (call) and when its done we get the control back (blocks thread). This is exactly the same when you call one java method from another method.
  • Asynchronously – This is like spring @Async operation. Call in a seperate thread (queue), returning a Future. Deal with the Future object when you want. This is good when you have multiple task that doesnt depend on each other.
  • Reactively – This is asynchronus as well and even better as we dont need to query any Future object if its done. We subscribe and get a Observable which tell us when its done.

Right now lets stick with the the Synchronous one. So now we have a MainController.java that calls the integration client.


package com.mynotes.spring.cloud.feign;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class MainController {

@Autowired
private IntegrationClient integrationClient;

@GetMapping(value="/offersAndRecommendation" ,produces ={MediaType.APPLICATION_JSON_VALUE})
public Map<String,Collection> offersAndRecommendation() {
Map<String,Collection> result=new HashMap<String,Collection>();
result.put("offers", integrationClient.getOffers());
result.put("recommendations", integrationClient.getRecommendations());
return result;

}

}

Now up the service. Lets see the result of the /offersAndRecommendation  endpoint when both the services are up using postman.

hystrix4

As we can see both the service result are being returned. Now lets kill the offer-service.

hystrix7

Postman result

hystrix3

If you check the logs , the fallback method for offers was called. As we know Feign can cache results too, we can do that and return that in our fallback senario or call a different service alltogether. It up to your requirement.

Hystrix provides a built-in dashboard to monitor your hystrix cirtuitbreakers. Lets create a simple spring boot  project with following pom.xml. We just need 1 dependency.


<?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>1.4.2.RELEASE</version>
<relativePath />
</parent>

<groupId>com.mynotes.spring.cloud</groupId>
<artifactId>hystrix-dashboard-service</artifactId>
<version>1.0.0-SNAPSHOT</version>
<name>hystrix-dashboard-service</name>
<description>Demo project for Spring Cloud cloud hystrix-dashboard-service</description>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Camden.SR3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
</dependency>
</dependencies>
</project>

Startup class HystrixDashboardApplication.java


package com.mynotes.spring.cloud.feign;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@SpringBootApplication
@Controller
@EnableHystrixDashboard

public class HystrixDashboardApplication {

@RequestMapping("/")
public String home() {
return "forward:/hystrix/index.html";
}

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

}

We would be running this on port 9903. Since we would like to monitor the hystrix.stream of our service running on 8080, ener the url like below

hystrix5

Following is the dashboard when things were normal. I was hitting multiple time with postman

hystrix1

When the offer-service was down and we hitted multiple times, The circuit was open.

hystrix2

The when the offer-service was up again , after a few seconds hystrix automatically close the connection.

As you noticed, monitoring large number of Hystrix dashboards by putting in a new url isnt practicle. Turbine provides a consolidated view of all hystrix dashboards. This is also fairly easy, you add the dependency and connect it to eureka from where it will gather information about variaous circuitbreakers and services.

Advertisements
%d bloggers like this: