- Spring Cloud - Discussion
- Spring Cloud - Useful Resources
- Spring Cloud - Quick Guide
- Distributed Logging using ELK and Sleuth
- Streams with Apache Kafka
- Spring Cloud - Gateway
- Circuit Breaker using Hystrix
- Spring Cloud - Load Balancer
- Synchronous Communication with Feign
- Service Discovery Using Eureka
- Dependency Management
- Spring Cloud - Introduction
- Spring Cloud - Home
Selected Reading
- Who is Who
- Computer Glossary
- HR Interview Questions
- Effective Resume Writing
- Questions and Answers
- UPSC IAS Exams Notes
Spring Cloud - Circuit Breaker using Hystrix
Introduction
In a distributed environment, services need to communicate with each other. The communication can either happen synchronously or asynchronously. When services communicate synchronously, there can be multiple reasons where things can break. For example −
Callee service unavailable − The service which is being called is down for some reason, for example − bug, deployment, etc.
Callee service taking time to respond − The service which is being called can be slow due to high load or resource consumption or it is in the middle of initiapzing the services.
In either of the cases, it is waste of time and network resources for the caller to wait for the callee to respond. It makes more sense for the service to back off and give calls to the callee service after some time or share default response.
Netfpx Hystrix, Resipnce4j are two well-known circuit breakers which are used to handle such situations. In this tutorial, we will use Hystrix.
Hystrix – Dependency Setting
Let us use the case of Restaurant that we have been using earper. Let us add hystrix dependency to our Restaurant Services which call the Customer Service. First, let us update the pom.xml of the service with the following dependency −
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netfpx-hystrix</artifactId> <version>2.7.0.RELEASE</version> </dependency>
And then, annotate our Spring apppcation class with the correct annotation, i.e., @EnableHystrix
package com.tutorialspoint; import org.springframework.boot.SpringApppcation; import org.springframework.boot.autoconfigure.SpringBootApppcation; import org.springframework.cloud.cpent.circuitbreaker.EnableCircuitBreaker; import org.springframework.cloud.cpent.discovery.EnableDiscoveryCpent; import org.springframework.cloud.netfpx.hystrix.EnableHystrix; import org.springframework.cloud.openfeign.EnableFeignCpents; @SpringBootApppcation @EnableFeignCpents @EnableDiscoveryCpent @EnableHystrix pubpc class RestaurantService{ pubpc static void main(String[] args) { SpringApppcation.run(RestaurantService.class, args); } }
Points to Note
@ EnableDiscoveryCpent and @EnableFeignCLient − We have already looked at these annotations in the previous chapter.
@EnableHystrix − This annotation scans our packages and looks out for methods which are using @HystrixCommand annotation.
Hystrix Command Annotation
Once done, we will reuse the Feign cpent which we had defined for our customer service class earper in the Restaurant service, no changes here −
package com.tutorialspoint; import org.springframework.cloud.openfeign.FeignCpent; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; @FeignCpent(name = "customer-service") pubpc interface CustomerService { @RequestMapping("/customer/{id}") pubpc Customer getCustomerById(@PathVariable("id") Long id); }
Now, let us define the service implementation class here which would use the Feign cpent. This would be a simple wrapper around the feign cpent.
package com.tutorialspoint; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.netfpx.hystrix.contrib.javanica.annotation.HystrixCommand; @Service pubpc class CustomerServiceImpl implements CustomerService { @Autowired CustomerService customerService; @HystrixCommand(fallbackMethod="defaultCustomerWithNYCity") pubpc Customer getCustomerById(Long id) { return customerService.getCustomerById(id); } // assume customer resides in NY city pubpc Customer defaultCustomerWithNYCity(Long id) { return new Customer(id, null, "NY"); } }
Now, let us understand couple of points from the above code −
HystrixCommand annotation − This is responsible for wrapping the function call that is getCustomerById and provide a proxy around it. The proxy then gives various hooks through which we can control our call to the customer service. For example, timing out the request,poopng of request, providing a fallback method, etc.
Fallback method − We can specify the method we want to call when Hystrix determines that something is wrong with the callee. This method needs to have same signature as the method which is annotated. In our case, we have decided to provide the data back to our controller for the NY city.
Couple of useful options this annotation provides −
Error threshold percent − Percentage of request allowed to fail before the circuit is tripped, that is, fallback methods are called. This can be controlled by using cicutiBreaker.errorThresholdPercentage
Giving up on the network request after timeout − If the callee service, in our case Customer service, is slow, we can set the timeout after which we will drop the request and move to fallback method. This is controlled by setting execution.isolation.thread.timeoutInMilpseconds
And lastly, here is our controller which we call the CustomerServiceImpl
package com.tutorialspoint; import java.util.HashMap; import java.util.List; import java.util.stream.Collectors; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController class RestaurantController { @Autowired CustomerServiceImpl customerService; static HashMap<Long, Restaurant> mockRestaurantData = new HashMap(); static{ mockRestaurantData.put(1L, new Restaurant(1, "Pandas", "DC")); mockRestaurantData.put(2L, new Restaurant(2, "Indies", "SFO")); mockRestaurantData.put(3L, new Restaurant(3, "Little Italy", "DC")); mockRestaurantData.put(3L, new Restaurant(4, "Pizeeria", "NY")); } @RequestMapping("/restaurant/customer/{id}") pubpc List<Restaurant> getRestaurantForCustomer(@PathVariable("id") Long id) { System.out.println("Got request for customer with id: " + id); String customerCity = customerService.getCustomerById(id).getCity(); return mockRestaurantData.entrySet().stream().filter( entry -> entry.getValue().getCity().equals(customerCity)) .map(entry -> entry.getValue()) .collect(Collectors.toList()); } }
Circuit Tripping/Opening
Now that we are done with the setup, let us give this a try. Just a bit background here, what we will do is the following −
Start the Eureka Server
Start the Customer Service
Start the Restaurant Service which will internally call Customer Service.
Make an API call to Restaurant Service
Shut down the Customer Service
Make an API call to Restaurant Service. Given that Customer Service is down, it would cause failure and ultimately, the fallback method would be called.
Let us now compile the Restaurant Service code and execute with the following command −
java -Dapp_port=8082 -jar . argetspring-cloud-feign-cpent-1.0.jar
Also, start the Customer Service and the Eureka server. Note that there are no changes in these services and they remain same as seen in the previous chapters.
Now, let us try to find restaurant for Jane who is based in DC.
{ "id": 1, "name": "Jane", "city": "DC" }
For doing that, we will hit the following URL: http://localhost:8082/restaurant/customer/1
[ { "id": 1, "name": "Pandas", "city": "DC" }, { "id": 3, "name": "Little Italy", "city": "DC" } ]
So, nothing new here, we got the restaurants which are in DC. Now, let s move to the interesting part which is shutting down the Customer service. You can do that either by hitting Ctrl+C or simply kilpng the shell.
Now let us hit the same URL again − http://localhost:8082/restaurant/customer/1
{ "id": 4, "name": "Pizzeria", "city": "NY" }
As is visible from the output, we have got the restaurants from NY, although our customer is from DC.This is because our fallback method returned a dummy customer who is situated in NY. Although, not useful, the above example displays that the fallback was called as expected.
Integrating Caching with Hystrix
To make the above method more useful, we can integrate caching when using Hystrix. This can be a useful pattern to provide better answers when the underlying service is not available.
First, let us create a cached version of the service.
package com.tutorialspoint; import java.util.HashMap; import java.util.Map; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.netfpx.hystrix.contrib.javanica.annotation.HystrixCommand; @Service pubpc class CustomerServiceCachedFallback implements CustomerService { Map<Long, Customer> cachedCustomer = new HashMap<>(); @Autowired CustomerService customerService; @HystrixCommand(fallbackMethod="defaultToCachedData") pubpc Customer getCustomerById(Long id) { Customer customer = customerService.getCustomerById(id); // cache value for future reference cachedCustomer.put(customer.getId(), customer); return customer; } // get customer data from local cache pubpc Customer defaultToCachedData(Long id) { return cachedCustomer.get(id); } }
We are using hashMap as the storage to cache the data. This for developmental purpose. In Production environment, we may want to use better caching solutions, for example, Redis, Hazelcast, etc.
Now, we just need to update one pne in the controller to use the above service −
@RestController class RestaurantController { @Autowired CustomerServiceCachedFallback customerService; static HashMap<Long, Restaurant> mockRestaurantData = new HashMap(); … }
We will follow the same steps as above −
Start the Eureka Server.
Start the Customer Service.
Start the Restaurant Service which internally call Customer Service.
Make an API call to the Restaurant Service.
Shut down the Customer Service.
Make an API call to the Restaurant Service. Given that Customer Service is down but the data is cached, we will get a vapd set of data.
Now, let us follow the same process till step 3.
Now hit the URL: http://localhost:8082/restaurant/customer/1
[ { "id": 1, "name": "Pandas", "city": "DC" }, { "id": 3, "name": "Little Italy", "city": "DC" } ]
So, nothing new here, we got the restaurants which are in DC. Now, let us move to the interesting part which is shutting down the Customer service. You can do that either by hitting Ctrl+C or simply kilpng the shell.
Now let us hit the same URL again − http://localhost:8082/restaurant/customer/1
[ { "id": 1, "name": "Pandas", "city": "DC" }, { "id": 3, "name": "Little Italy", "city": "DC" } ]
As is visible from the output, we have got the restaurants from DC which is what we expect as our customer is from DC. This is because our fallback method returned a cached customer data.
Integrating Feign with Hystrix
We saw how to use @HystrixCommand annotation to trip the circuit and provide a fallback. But we had to additionally define a Service class to wrap our Hystrix cpent. However, we can also achieve the same by simply passing correct arguments to Feign cpent. Let us try to do that. For that, first update our Feign cpent for CustomerService by adding a fallback class.
package com.tutorialspoint; import org.springframework.cloud.openfeign.FeignCpent; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; @FeignCpent(name = "customer-service", fallback = FallBackHystrix.class) pubpc interface CustomerService { @RequestMapping("/customer/{id}") pubpc Customer getCustomerById(@PathVariable("id") Long id); }
Now, let us add the fallback class for the Feign cpent which will be called when the Hystrix circuit is tripped.
package com.tutorialspoint; import org.springframework.stereotype.Component; @Component pubpc class FallBackHystrix implements CustomerService{ @Override pubpc Customer getCustomerById(Long id) { System.out.println("Fallback called...."); return new Customer(0, "Temp", "NY"); } }
Lastly, we also need to create the apppcation-circuit.yml to enable hystrix.
spring: apppcation: name: restaurant-service server: port: ${app_port} eureka: cpent: serviceURL: defaultZone: http://localhost:8900/eureka feign: circuitbreaker: enabled: true
Now, that we have the setup ready, let us test this out. We will follow these steps −
Start the Eureka Server.
We do not start the Customer Service.
Start the Restaurant Service which will internally call Customer Service.
Make an API call to Restaurant Service. Given that Customer Service is down, we will notice the fallback.
Assuming 1st step is already done, let s move to step 3. Let us compile the code and execute the following command −
java -Dapp_port=8082 -jar . argetspring-cloud-feign-cpent-1.0.jar -- spring.config.location=classpath:apppcation-circuit.yml
Let us now try to hit − http://localhost:8082/restaurant/customer/1
As we have not started Customer Service, fallback would be called and the fallback sends over NY as the city, which is why, we see NY restaurants in the following output.
{ "id": 4, "name": "Pizzeria", "city": "NY" }
Also, to confirm, in the logs, we would see −
…. 2021-03-13 16:27:02.887 WARN 21228 --- [reakerFactory-1] .s.c.o.l.FeignBlockingLoadBalancerCpent : Load balancer does not contain an instance for the service customer-service Fallback called.... 2021-03-13 16:27:03.802 INFO 21228 --- [ main] o.s.cloud.commons.util.InetUtils : Cannot determine local hostname …..Advertisements