Understanding Circuit Breaker Pattern in Microservices
Introduction
Imagine you are in an entertainment venue and sudden power surges threaten your systems. Fortunately, a small device called a circuit breaker is assigned to protect your systems from these sudden power surges, saving your system from burning out. This is the power of the circuit breaker in the microservices world. In an ever-evolving microservices environment and growing projects, maintaining control is crucial. Providing immediate feedback on potential failures and effectively managing those failures is essential. This is where the circuit breaker comes to our aid. Just like its counterpart in electrical circuits, this model acts as a protective mechanism, protecting microservices during failures and ensuring a sustainable environment.
Why Circuit Breaker Pattern in Microservice?
Microservices provide us with a system made up of many interconnected services. It is important for the communication network in this system to work properly. Problems in the communication network can cause many major issues. Let’s explain this with an example: imagine we have a payment service that is not working properly. We also have an order service that depends on this payment service and keeps trying to access it. These repeated access attempts consume valuable system resources and cause disruptions. This error can lead to system crashes and the rapid consumption of valuable resources.
The Circuit Breaker Pattern provides the following solutions:
- It prevents gradual failures.
- Instead of a complete shutdown, it performs gradual interruptions to protect the systems.
- It monitors faulty services and restarts the flow when a healthy connection is restored.
Three States of a Circuit Breaker
- Closed state: In this case all calls in the circuit breaker system are allowed.
- Open state: In this case the circuit breaker prevents all calls in the system.
- Half-open state: In this case calls in a small part are allowed until the system works healthy again. If the system is perceived to be working properly, the system switches back to the closed state.
The circuit breaker pattern continuously listens for incoming remote calls. If a delay or access problem occurs, it starts counting this. When the number of failures reaches a certain threshold, the system evaluates the situation in three modes. That is, this pattern monitors problems with remote calls and evaluates the system behavior at a certain point, i.e. when a set failure threshold is reached.
Different Types of Circuit Breakers
We have explained how the circuit breaker works in a simple way. There are also many types of circuit breakers. To mention briefly, the most commonly used ones are as follows:
- Time-based circuit breakers: These circuit breakers trip after a certain number of faults occur within a certain period of time.
- Count-based circuit breakers: These circuit breakers trip after a certain number of faults have occurred, regardless of the time period.
- Fault-based circuit breakers: These circuit breakers trip only when certain types of faults occur.
Let’s continue with an example:
We have two services and they make remote calls between them. One is called category service and the other is called user service. The user service will make a remote call to the category service and retrieve the categories.
<?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 https://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>3.2.1</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.hacisimsek</groupId>
<artifactId>user-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>user-service</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>17</java.version>
<spring-cloud.version>2023.0.0</spring-cloud.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-circuitbreaker-resilience4j</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</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>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
The dependencies we use are as pom.xml above.
package com.hacisimsek.userservice.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class OrderDTO {
private int id;
private String name;
private String category;
private String color;
private double price;
}
We create an OrderDTO as above.
package com.hacisimsek.userservice.controller;
import com.hacisimsek.userservice.dto.OrderDTO;
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Lazy;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.Date;
import java.util.List;
import java.util.stream.Stream;
@RestController
@RequestMapping("/user-service")
public class UserController {
@Lazy
@Autowired
private RestTemplate restTemplate;
private static final Logger logger = LoggerFactory.getLogger(UserController.class);
private static final String CATALOG_SERVICE_URL = "http://localhost:9191/orders";
public static final String SERVICE_SERVICE = "userService";
private int attempt = 1;
@GetMapping("/displayOrders")
@CircuitBreaker(name = SERVICE_SERVICE, fallbackMethod = "hardcodedResponse")
//@Retry(name = SERVICE_SERVICE, fallbackMethod = "hardcodedResponse")
public List<OrderDTO> displayOrders(@RequestParam("category") String category){
String url = category == null ? CATALOG_SERVICE_URL : CATALOG_SERVICE_URL + "/" + category;
logger.info("retry method called {} times at {}", attempt++, new Date());
return restTemplate.getForObject(url, List.class);
}
public List<OrderDTO> hardcodedResponse(Exception ex){
return Stream.of(
new OrderDTO(119, "LED TV", "electronics", "white", 45000),
new OrderDTO(345, "Headset", "electronics", "black", 7000),
new OrderDTO(475, "Sound bar", "electronics", "black", 13000),
new OrderDTO(574, "Puma Shoes", "foot wear", "black & white", 4600),
new OrderDTO(678, "Vegetable chopper", "kitchen", "blue", 999),
new OrderDTO(532, "Oven Gloves", "kitchen", "gray", 745)
).toList();
}
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
We have a category service. This user service makes a remote call to the category service and retrieves the category data. This is a healthy scenario. And the system is in closed state. If our category service goes down. Here, the circuit breaker annotation kicks in and starts counting. When it reaches a certain threshold, it goes into open state and the fallback method works. In other words, the procedure that is desired to be executed in the bad scenario condition is executed. Here, mock data is desired to be printed. When the Category service comes back up, the system goes to half open after a certain threshold and then returns to the closed state.
Here we have only used dummy data for the fallback method. Alternatively you can do the following as a solution scenario;
- Using Cached Data
- Referral to Alternative Services
- Returning Error Messages
- Delayed Rhythmic Logic
- User-Initiated Retries
management:
health:
circuitbreakers:
enabled: true
endpoints:
web:
exposure:
include: health
endpoint:
health:
show-details: always
resilience4j:
circuitbreaker:
instances:
userService:
registerHealthIndicator: true
eventConsumerBufferSize: 10
failureRateThreshold: 50
minimumNumberOfCalls: 5
automaticTransitionFromOpenToHalfOpenEnabled: true
waitDurationInOpenState: 5s
permittedNumberOfCallsInHalfOpenState: 3
slidingWindowSize: 10
slidingWindowType: COUNT_BASED
retry:
instances:
userService:
maxRetryAttempts: 5
waitDuration: 10s
In the photo above you can see the settings required for our circuit braker here yourself. You can set the threshold value and time. The file name is application.yml.
But the best practice should be as follows.
- Multiple alternative fallback methods should be used
- Monitoring must be done. Metrics should be followed.
- Alternative behaviors should be well tested.
Conclusion
The Circuit Breakers are a valuable tool for software developers. They can help increase the reliability and scalability of applications and protect them against denial of service attacks. By understanding how circuit breakers work and how to use them in your applications, you can make your applications more robust and durable.
Even when life seems hard, there is always something we can do and achieve. Stephen Hawking
Project In Github
You can access all source codes of the project from this link.
Find Me
About Sisal
Sisal LinkedIn: https://www.linkedin.com/company/sisal-digital-hub-t%C3%BCrkiye
Sisal Medium Page: https://medium.com/sisaldigitalhubturkiye
Resource
- https://microservices.io/patterns/reliability/circuit-breaker.html
- https://martinfowler.com/bliki/CircuitBreaker.html
- https://medium.com/trendyol-tech/nedir-bu-circuit-breaker-pattern-2d3d34948767
- https://en.wikipedia.org/wiki/Circuit_breaker_design_pattern
- https://www.turkninja.com/2023/12/bir-eticaret-platformumuz-var-odeme.html
- https://gokhana.medium.com/circuit-breaker-pattern-nedir-6d36f2fc35d0
- https://learn.microsoft.com/en-us/azure/architecture/patterns/circuit-breaker
- https://blog.bitsrc.io/circuit-breaker-pattern-3389a7b259f1