What is a Circuit Breaker?
In microservice architecture Circuit Breaker is a mechanism to avoid cascading failure across multiple layers of service calls.
What is Hystrix?
The spring cloud uses Hystrix (a Netflix library) to implement the Circuit Breaker.
What CircuitBreaker does?
The circuit breaker trips (opens) when the following conditions are met:
- The service (method annotated with @HystrixCommand) receives number of calls exceeding a limit. This limit is specified by circuitBreaker.requestVolumeThreshold (default: 20 calls)
- And these calls are received within a a particular time period. This time period is specified by metrics.rollingStats.timeInMilliseconds (default: 10 seconds)
- And the number of failures of calling the service method is greater than a particular percentage. This percentage is specified by circuitBreaker.errorThresholdPercentage (default: >50%)
What is a failure?
The failure while calling a service can be because of an exception in the service method or because of a response timeout (specified by execution.isolation.thread.timeoutInMilliseconds - default is 1 second)
What happens after circuit trips?
When circuit breaker trips, the further calls are not made to the target service and failure response is returned immediately, so the caller is not blocked for a response, hence avoiding cascading failure. After circuit breaker trips, the circuit remains open for circuitBreaker.sleepWindowInMilliseconds (default 5 seconds).
Fallback method
The annotation @HystrixCommand can specify a fallback method name, so call can be redirected to that method which can execute an alternative default logic.
When should a Circuit Breaker be used?
@HystrixCommand is typically used with microservices which call other services, but it can be used in a standalone application as well.
Example
In following example we will understand the basics of Hystrix circuit breaker by implementing it in a standalone application and on normal bean's methods.
Maven dependencies
pom.xml<project .....> <modelVersion>4.0.0</modelVersion> <groupId>com.logicbig.example</groupId> <artifactId>circuit-breaker-hystrix-basics</artifactId> <version>1.0-SNAPSHOT</version> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.6.RELEASE</version> </parent> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Greenwich.SR2</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> </project>
Using @HystrixCommand
package com.logicbig.example;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class MyService {
@HystrixCommand(fallbackMethod = "defaultDoSomething")
public void doSomething(int input) {
System.out.println("input: " + input);
//in case of exception fallbackMethod is called
System.out.println("output: " + 10 / input);
}
public void defaultDoSomething(int input) {
System.out.println("in default method, the input number: " + input);
}
@HystrixCommand(fallbackMethod = "defaultDoSomething")
public void doSomething2(int input) {
try {
TimeUnit.MILLISECONDS.sleep(1500);// timeout scenario
} catch (InterruptedException e) {
return;
}
System.out.println("input: " + input);
System.out.println("output: " + 10 / input);
}
}
As seen above the method doSomething() can throw an exception if input is 0, whereas doSomething2() internally delays to demonstrate the timeout scenarios.
Main method
package com.logicbig.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.context.ConfigurableApplicationContext;
@SpringBootApplication
@EnableCircuitBreaker
public class CircuitBreakerMain {
public static void main(String[] args) {
ConfigurableApplicationContext ctx = SpringApplication.run(CircuitBreakerMain.class, args);
MyService myService = ctx.getBean(MyService.class);
System.out.println("-- calling doSomething(2) --");
myService.doSomething(2);
System.out.println("-- calling doSomething(0) --");
myService.doSomething(0);
System.out.println("-- calling doSomething(5) --");
myService.doSomething(5);
System.out.println("-- calling doSomething2(2) --");
myService.doSomething2(2);
}
}
Output
-- calling doSomething(2) --
input: 2
output: 5
-- calling doSomething(0) --
input: 0
in default method, the input number: 0
-- calling doSomething(5) --
input: 5
output: 2
-- calling doSomething2(2) --
in default method, the input number: 2
In above example none of the caller causes the circuit to trip. When circuit trips (as stated in the beginning of this tutorial) further call to the service method is not attempted for a certain time period.
Tripping of CircuitBreaker
Following example makes multiple consecutive calls to the service method (annotated with @HystrixCommand ) causing the CircuitBreaker to trip.
package com.logicbig.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.context.ConfigurableApplicationContext;
import java.util.concurrent.TimeUnit;
@SpringBootApplication
@EnableCircuitBreaker
public class CircuitBreakerMain2 {
public static void main(String[] args) throws InterruptedException {
ConfigurableApplicationContext ctx = SpringApplication.run(CircuitBreakerMain2.class, args);
MyService myService = ctx.getBean(MyService.class);
System.out.println("-- calling doSomething(1) 40 times --");
int n = 40;
for (int i = 0; i < n; i++) {
myService.doSomething(i < (n * 0.6) ? 0 : 2);
TimeUnit.MILLISECONDS.sleep(100);
}
TimeUnit.SECONDS.sleep(6);
System.out.println("-- final call --");
myService.doSomething(2);
}
}
Output
-- calling doSomething(1) 30 times --
input: 0
in default method, the input number: 0
input: 0
in default method, the input number: 0
input: 0
in default method, the input number: 0
input: 0
in default method, the input number: 0
input: 0
in default method, the input number: 0
input: 0
in default method, the input number: 0
input: 0
in default method, the input number: 0
input: 0
in default method, the input number: 0
input: 0
in default method, the input number: 0
input: 0
in default method, the input number: 0
input: 0
in default method, the input number: 0
input: 0
in default method, the input number: 0
input: 0
in default method, the input number: 0
input: 0
in default method, the input number: 0
input: 0
in default method, the input number: 0
input: 0
in default method, the input number: 0
input: 0
in default method, the input number: 0
input: 0
in default method, the input number: 0
input: 0
in default method, the input number: 0
input: 0
in default method, the input number: 0
input: 0
in default method, the input number: 0
input: 0
in default method, the input number: 0
input: 0
in default method, the input number: 0
in default method, the input number: 0
in default method, the input number: 2
in default method, the input number: 2
in default method, the input number: 2
in default method, the input number: 2
in default method, the input number: 2
in default method, the input number: 2
in default method, the input number: 2
in default method, the input number: 2
in default method, the input number: 2
in default method, the input number: 2
in default method, the input number: 2
in default method, the input number: 2
in default method, the input number: 2
in default method, the input number: 2
in default method, the input number: 2
in default method, the input number: 2
-- final call --
input: 2
output: 5
As seen above, the fallback method in the beginning is called because of the exception, but when more than 20 such calls made within 10 seconds and more than 50% calls failed during that time then the circuit breaker trips (opens) and further calls to the method is not made but instead the fallback method is called directly. After a time specified by circuitBreaker.sleepWindowInMilliseconds (default 5 seconds), circuit is reset (closed) automatically and the service method is called again.
Example ProjectDependencies and Technologies Used: - Spring Boot 2.1.6.RELEASE
Corresponding Spring Version 5.1.8.RELEASE - Spring Cloud Greenwich.SR2
- spring-boot-starter : Core starter, including auto-configuration support, logging and YAML.
- spring-cloud-starter-netflix-hystrix 2.1.2.RELEASE: Spring Cloud Starter Netflix Hystrix.
- JDK 1.8
- Maven 3.5.4
|