In this blog we will learn, Netflix Circuit Breaker component implementation while invoking underlying microservices.
In this example, we have two microservices (student and skillstechnologies), where first student microservices is dependent on skillstechnologies for fetching the student course details. Here, we are adding servicedelegate class for consuming skillstechnologies rest api named /getStudentCourse/{name} through RestTemplate.
Now, suppose due to some reason skillstechnogies service (/getStudentCourse/{name}) throws an exception. In this case using Hystrix we are defining a fallback method, it will return some value on failure.
When, we call to a particular service exceed circuitBreaker.requestVolumeThreshold (default: 20 requests) and the failure percentage is greater than circuitBreaker.errorThresholdPercentage(default: >50%) in a rolling window defined by metrics.rollingStats.timeInMilliseconds (default: 10 seconds), the circuit opens and the call is not made. In cases of error and an open circuit, a fallback can be provided by the developer.
It is possible by using @HystrixProperty or through application.properties
@HystrixCommand(fallbackMethod = "callStudentServiceAndGetData_Fallback", commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000"),
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "60") })
Java 8
Maven 3.5.0
Spring Boot 2.0.5.RELEASE
Eclipse
Netflix Eureka as Service Registry Server
Netflix Ribbon as Client Side Load balancer
Netflix Hystrix as circuit breaker
2 Microservices (student & skillstechnologies)
spring-boot-zuul-gateway-proxy 1 node (localhost:8989)
spring-boot-zuul-gateway-proxy-eureka-server 1 node (localhost:9090)
spring-boot-zuul-gateway-proxy-student-service 1 node (localhost:8930)
spring-boot-zuul-gateway-proxy-skillstechnologies-service 1 node (localhost:8940)
Note: for step by step configurations click on my previous blog: Spring Cloud Netflix OSS + Netflix Zuul API Gateway + Netflix Eureka Discovery Server + 2 x Microservices + Example
Note: there is no any change in any properties file.
Following highlighted maven dependencies need to be added in pom.xml file of spring-boot-zuul-gateway-proxy-student-service microservice
<?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>
<groupId>com.example</groupId>
<artifactId>spring-boot-zuul-gateway-proxy-student-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>spring-boot-zuul-gateway-proxy-student-service</name>
<description>Project for Spring Boot Student Service</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.1.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.RELEASE</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</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-actuator</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.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-turbine</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
</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>
</project>
package com.the.basic.tech.info.studentinfoservice.delegate;
import java.util.Date;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
@Service
public class StudentServiceDelegate {
private static final Logger logger = LoggerFactory.getLogger(StudentServiceDelegate.class);
@Autowired
RestTemplate restTemplate;
@HystrixCommand(fallbackMethod = "callStudentServiceAndGetData_Fallback", commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000"),
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "60") })
public String callStudentServiceAndGetData(String name) {
logger.info("Getting StudentSubject details for {}", name);
String response = restTemplate
.exchange("http://localhost:8989/portal/skillTechService/skillstech/getStudentCourse/{name}",
HttpMethod.GET, null, new ParameterizedTypeReference<String>() {
}, name)
.getBody();
logger.info("Response Received as " + response + " - " + new Date());
return "NORMAL FLOW. Student Course: " + response;
}
@SuppressWarnings("unused")
private String callStudentServiceAndGetData_Fallback(String name) {
logger.info("Skills Tech Service is down!!! Hystrix fallback route enabled for this service.");
return "CIRCUIT BREAKER ENABLED. No Response From Skills Tech Service at this moment. Service will be back Shortly.";
}
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
package com.the.basic.tech.info.studentinfoservice;
import java.util.Date;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.the.basic.tech.info.studentinfoservice.delegate.StudentServiceDelegate;
@RestController
@SpringBootApplication
@EnableHystrixDashboard
@EnableCircuitBreaker
public class StudentServiceApp {
private static final Logger logger = LoggerFactory.getLogger(StudentServiceApp.class);
@RequestMapping(value = "/echoStudentName/{name}")
public String echoStudentName(@PathVariable(name = "name") String name) {
return "Hello " + name + " Responsed on : " + new Date();
}
@Autowired
StudentServiceDelegate studentServiceDelegate;
@RequestMapping(value = "/getStudentDetails/{name}")
public Student getStudentDetails(@PathVariable(name = "name") String name) {
logger.info("Going to call skillstechnologies /getStudentCourse/ service (Hystrix Enabled) to get data!");
String cls = studentServiceDelegate.callStudentServiceAndGetData(name);
logger.info("course/class value from skillstechnologies microservice {}", cls);
return new Student(name, "Northridge, CA", cls, "Block-E", "California State University");
}
public static void main(String[] args) {
SpringApplication.run(StudentServiceApp.class, args);
}
}
class Student {
String name;
String address;
String cls;
String university;
String block;
public Student(String name, String address, String cls, String block, String university) {
super();
this.name = name;
this.address = address;
this.cls = cls;
this.block = block;
this.university = university;
}
public String getName() {
return name;
}
public String getAddress() {
return address;
}
public String getCls() {
return cls;
}
public String getUniversity() {
return university;
}
public String getBlock() {
return block;
}
}
package com.the.basic.tech.info.studentinfoservice.delegate;
import java.util.Collections;
import org.springframework.boot.actuate.info.Info;
import org.springframework.boot.actuate.info.InfoContributor;
import org.springframework.stereotype.Component;
@Component
public class SpringEurekaClientSkillTechServiceInfoContributor implements InfoContributor {
@Override
public void contribute(Info.Builder builder) {
builder.withDetail("details",
Collections.singletonMap("description", "This is the Student microservice, which discovery server awares, and this service will Call SkillsTechnologies microservice."));
}
}
We need to add one more rest interface named /getStudentCourse/{name}/ for retruning the course name to the student microservice.
package com.the.basic.tech.info.skillstechnologies.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/skillstech")
public class SkillsTechnologiesController {
private static final Logger logger = LoggerFactory.getLogger(SkillsTechnologiesController.class);
@GetMapping("/getSkills")
public String getSkills() {
logger.info("Inside getSkitlls Method.");
// we can implement DB layer for fetching below details
return "Java, J2EE, Spring, Spring-Boot, Microservices, Docker, Kubernetes.";
}
@RequestMapping(value = "/getStudentCourse/{name}")
public String getStudentDetails(@PathVariable(name = "name") String name) {
return "MBA";
}
}
When both the microservices are up and running susccessfully.
http://localhost:8989/portal/student/getStudentDetails/The%20Basic%20Tech%20Info
Suppose skillstechnologies microservice is down. Just stop the microservice and see the result.
2021-06-21 00:18:19.166 INFO 18252 --- [ Thread-6] o.s.c.n.e.s.EurekaServiceRegistry : Unregistering application SKILLTECHNOLOGIESSERVICE with eureka with status DOWN
2021-06-21 00:18:19.168 WARN 18252 --- [ Thread-6] com.netflix.discovery.DiscoveryClient : Saw local status change event StatusChangeEvent [timestamp=1624214899168, current=DOWN, previous=UP]
2021-06-21 00:18:19.170 INFO 18252 --- [nfoReplicator-0] com.netflix.discovery.DiscoveryClient : DiscoveryClient_SKILLTECHNOLOGIESSERVICE/INDSNOI6N6RMH2.AMERICAS.GLOBAL.NTTDATA.COM:skillTechnologiesService:8940: registering service...
2021-06-21 00:18:19.179 INFO 18252 --- [nfoReplicator-0] com.netflix.discovery.DiscoveryClient : DiscoveryClient_SKILLTECHNOLOGIESSERVICE/LOCALHOST:skillTechnologiesService:8940 - registration status: 204
2021-06-21 00:18:19.187 INFO 18252 --- [ Thread-6] o.s.s.concurrent.ThreadPoolTaskExecutor : Shutting down ExecutorService 'applicationTaskExecutor'
2021-06-21 00:18:19.204 INFO 18252 --- [ Thread-6] com.netflix.discovery.DiscoveryClient : Shutting down DiscoveryClient ...
2021-06-21 00:18:22.208 INFO 18252 --- [ Thread-6] com.netflix.discovery.DiscoveryClient : Unregistering ...
2021-06-21 00:18:22.215 INFO 18252 --- [ Thread-6] com.netflix.discovery.DiscoveryClient : DiscoveryClient_SKILLTECHNOLOGIESSERVICE/LOCALHOST:skillTechnologiesService:8940 - deregister status: 200
2021-06-21 00:18:22.239 INFO 18252 --- [ Thread-6] com.netflix.discovery.DiscoveryClient : Completed shut down of DiscoveryClient
Terminate batch job (Y/N)? Y
D:\development\spring-boot-api-gateway-zuul\spring-boot-zuul-gateway-proxy-skillstechnologies-service>
http://localhost:8989/portal/student/getStudentDetails/The%20Basic%20Tech%20Info
Here is the fall back output in the browser.
2021-06-21 00:07:59.750 INFO 14940 --- [ restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8930 (http) with context path ''
2021-06-21 00:07:59.751 INFO 14940 --- [ restartedMain] .s.c.n.e.s.EurekaAutoServiceRegistration : Updating port to 8930
2021-06-21 00:07:59.765 INFO 14940 --- [ restartedMain] c.t.b.t.i.s.StudentServiceApp : Started StudentServiceApp in 27.7 seconds (JVM running for 29.519)
2021-06-21 00:08:57.988 INFO 14940 --- [nio-8930-exec-2] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
2021-06-21 00:08:57.989 INFO 14940 --- [nio-8930-exec-2] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2021-06-21 00:08:58.086 INFO 14940 --- [nio-8930-exec-2] o.s.web.servlet.DispatcherServlet : Completed initialization in 88 ms
2021-06-21 00:08:58.318 INFO 14940 --- [nio-8930-exec-2] c.t.b.t.i.s.StudentServiceApp : Going to call skillstechnologies /getStudentCourse/ service (Hystrix Enabled) to get data!
2021-06-21 00:08:59.159 INFO 14940 --- [rviceDelegate-1] c.t.b.t.i.s.d.StudentServiceDelegate : Getting StudentSubject details for The Basic Tech Info
2021-06-21 00:09:01.003 INFO 14940 --- [rviceDelegate-1] c.t.b.t.i.s.d.StudentServiceDelegate : Response Received as MBA - Mon Jun 21 00:09:01 IST 2021
2021-06-21 00:09:01.052 INFO 14940 --- [nio-8930-exec-2] c.t.b.t.i.s.StudentServiceApp : course/class value from skillstechnologies microservice NORMAL FLOW. Student Course: MBA
2021-06-21 00:09:06.182 INFO 14940 --- [nio-8930-exec-3] c.t.b.t.i.s.StudentServiceApp : Going to call skillstechnologies /getStudentCourse/ service (Hystrix Enabled) to get data!
2021-06-21 00:09:06.188 INFO 14940 --- [rviceDelegate-2] c.t.b.t.i.s.d.StudentServiceDelegate : Getting StudentSubject details for The Basic Tech Info
2021-06-21 00:09:06.234 INFO 14940 --- [rviceDelegate-2] c.t.b.t.i.s.d.StudentServiceDelegate : Response Received as MBA - Mon Jun 21 00:09:06 IST 2021
2021-06-21 00:09:06.237 INFO 14940 --- [nio-8930-exec-3] c.t.b.t.i.s.StudentServiceApp : course/class value from skillstechnologies microservice NORMAL FLOW. Student Course: MBA
2021-06-21 00:12:59.020 INFO 14940 --- [trap-executor-0] c.n.d.s.r.aws.ConfigClusterResolver : Resolving eureka endpoints via configuration
2021-06-21 00:17:59.023 INFO 14940 --- [trap-executor-0] c.n.d.s.r.aws.ConfigClusterResolver : Resolving eureka endpoints via configuration
2021-06-21 00:19:36.277 INFO 14940 --- [nio-8930-exec-5] c.t.b.t.i.s.StudentServiceApp : Going to call skillstechnologies /getStudentCourse/ service (Hystrix Enabled) to get data!
2021-06-21 00:19:36.312 INFO 14940 --- [rviceDelegate-3] c.t.b.t.i.s.d.StudentServiceDelegate : Getting StudentSubject details for The Basic Tech Info
2021-06-21 00:19:36.490 INFO 14940 --- [rviceDelegate-3] c.t.b.t.i.s.d.StudentServiceDelegate : Skills Tech Service is down!!! Hystrix fallback route enabled for this service.
2021-06-21 00:19:36.502 INFO 14940 --- [nio-8930-exec-5] c.t.b.t.i.s.StudentServiceApp : course/class value from skillstechnologies microservice CIRCUIT BREAKER ENABLED. No Response From Skills Tech Service at this moment. Service will be back Shortly.
Suppose skillstechnologies microservice is up now. Just start the microservice again and see the result.
http://localhost:8989/portal/student/getStudentDetails/The%20Basic%20Tech%20Info
2021-06-21 00:27:39.573 INFO 11328 --- [ main] c.n.c.sources.URLConfigurationSource : To enable URLs as dynamic configuration sources, define System property archaius.configurationSource.additionalUrls or make config.properties available on classpath.
2021-06-21 00:27:39.589 WARN 11328 --- [ main] c.n.c.sources.URLConfigurationSource : No URLs will be polled as dynamic configuration sources.
2021-06-21 00:27:39.592 INFO 11328 --- [ main] c.n.c.sources.URLConfigurationSource : To enable URLs as dynamic configuration sources, define System property archaius.configurationSource.additionalUrls or make config.properties available on classpath.
2021-06-21 00:27:39.918 INFO 11328 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
2021-06-21 00:27:43.535 INFO 11328 --- [ main] o.s.c.n.eureka.InstanceInfoFactory : Setting initial instance status as: STARTING
2021-06-21 00:27:43.651 INFO 11328 --- [ main] com.netflix.discovery.DiscoveryClient : Initializing Eureka in region us-east-1
2021-06-21 00:27:44.022 INFO 11328 --- [ main] c.n.d.provider.DiscoveryJerseyProvider : Using JSON encoding codec LegacyJacksonJson
2021-06-21 00:27:44.023 INFO 11328 --- [ main] c.n.d.provider.DiscoveryJerseyProvider : Using JSON decoding codec LegacyJacksonJson
2021-06-21 00:27:44.332 INFO 11328 --- [ main] c.n.d.provider.DiscoveryJerseyProvider : Using XML encoding codec XStreamXml
2021-06-21 00:27:44.333 INFO 11328 --- [ main] c.n.d.provider.DiscoveryJerseyProvider : Using XML decoding codec XStreamXml
2021-06-21 00:27:44.760 INFO 11328 --- [ main] c.n.d.s.r.aws.ConfigClusterResolver : Resolving eureka endpoints via configuration
2021-06-21 00:27:46.191 INFO 11328 --- [ main] com.netflix.discovery.DiscoveryClient : Disable delta property : false
2021-06-21 00:27:46.198 INFO 11328 --- [ main] com.netflix.discovery.DiscoveryClient : Single vip registry refresh property : null
2021-06-21 00:27:46.200 INFO 11328 --- [ main] com.netflix.discovery.DiscoveryClient : Force full registry fetch : false
2021-06-21 00:27:46.206 INFO 11328 --- [ main] com.netflix.discovery.DiscoveryClient : Application is null : false
2021-06-21 00:27:46.211 INFO 11328 --- [ main] com.netflix.discovery.DiscoveryClient : Registered Applications size is zero : true
2021-06-21 00:27:46.218 INFO 11328 --- [ main] com.netflix.discovery.DiscoveryClient : Application version is -1: true
2021-06-21 00:27:46.220 INFO 11328 --- [ main] com.netflix.discovery.DiscoveryClient : Getting all instance registry info from the eureka server
2021-06-21 00:27:47.203 INFO 11328 --- [ main] com.netflix.discovery.DiscoveryClient : The response status is 200
2021-06-21 00:27:47.211 INFO 11328 --- [ main] com.netflix.discovery.DiscoveryClient : Starting heartbeat executor: renew interval is: 30
2021-06-21 00:27:47.225 INFO 11328 --- [ main] c.n.discovery.InstanceInfoReplicator : InstanceInfoReplicator onDemand update allowed rate per min is 4
2021-06-21 00:27:47.235 INFO 11328 --- [ main] com.netflix.discovery.DiscoveryClient : Discovery Client initialized at timestamp 1624215467233 with initial instances count: 2
2021-06-21 00:27:47.240 INFO 11328 --- [ main] o.s.c.n.e.s.EurekaServiceRegistry : Registering application SKILLTECHNOLOGIESSERVICE with eureka with status UP
2021-06-21 00:27:47.249 INFO 11328 --- [ main] com.netflix.discovery.DiscoveryClient : Saw local status change event StatusChangeEvent [timestamp=1624215467249, current=UP, previous=STARTING]
2021-06-21 00:27:47.269 INFO 11328 --- [nfoReplicator-0] com.netflix.discovery.DiscoveryClient : DiscoveryClient_SKILLTECHNOLOGIESSERVICE/LOCALHOST:skillTechnologiesService:8940: registering service...
2021-06-21 00:27:47.391 INFO 11328 --- [nfoReplicator-0] com.netflix.discovery.DiscoveryClient : DiscoveryClient_SKILLTECHNOLOGIESSERVICE/LOCALHOST:skillTechnologiesService:8940 - registration status: 204
2021-06-21 00:27:47.441 INFO 11328 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8940 (http) with context path ''
2021-06-21 00:27:47.445 INFO 11328 --- [ main] .s.c.n.e.s.EurekaAutoServiceRegistration : Updating port to 8940
2021-06-21 00:27:47.450 INFO 11328 --- [ main] .t.b.t.i.s.SkillsTechnologiesApplication : Started SkillsTechnologiesApplication in 14.529 seconds (JVM running for 23.448)
In Spring Boot 2.x, the hystrix.stream endpoint has been moved to /actuator/hystrix.stream. And, this actuator endpoint enabled via following property (in application.yaml file of spring-boot-zuul-gateway-proxy-student-service microservice).
management.endpoints.web.exposure.include=hystrix.stream
Give below URL in Browser: http://localhost:8930/hystrix
Then give below URL in text box in form: http://localhost:8930/actuator/hystrix.stream
Instead of having a separate dashboard for every service we can use Turbine to provide a unified view of all services in a single dashboard.
Turbine provides a way to aggregate this information across all installations of an application in a cluster. Integrating turbine into a Spring-Cloud based application is straightforward, all it requires is information on which clusters to expose information on and how to aggregate information about the specific clusters.
Below maven dependency needs to be added in pom.xml file
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-turbine</artifactId>
<exclusions>
<exclusion>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
</exclusion>
</exclusions>
</dependency>
Happy learning. Have a great day 🙂