BulkHead Pattern
BulkHead Pattern
A ship is split into small multiple compartments using Bulkheads. Bulkheads are used to seal parts of the
ship to prevent the entire ship from sinking in case of a flood. Similarly, failures should be expected when
we design software. The application should be split into multiple components and resources should be
isolated in such a way that failure of one component is not affecting the other.
For ex: let's assume that there are 2 services A and B. Some of the APIs of A depends on B. For some
reason, B is very slow. So, when we get multiple concurrent requests to A which depends on B, A’s
performance will also get affected. It could block A’s threads. Due to that A might not be able to serve
other requests which do NOT depend on B. So, the idea here is to isolate resources / allocate some
threads in A for B. So that We do not consume all the threads of A and prevent A from hanging for all the
requests!
Bulkhead pattern:
The Bulkhead pattern is a design pattern used to improve the resilience and fault tolerance of a
distributed system by isolating different parts of the system from each other. By creating separate
compartments for different resources or services, the pattern can help prevent cascading failures and
limit the impact of failures on the rest of the system.
The Bulkhead pattern can be used in combination with other resilience patterns, such as the Circuit
Breaker pattern, Retry pattern, and Timeout pattern, to create a more resilient and fault-tolerant system.
1. A normal microservice
i. Lombok
ii. Resilience4j
maxConcurrentCalls→ The maximum number of concurrent calls that are allowed to execute at
the same time. If the maximum number of concurrent calls is exceeded, additional calls will be
rejected with a BulkheadFullException.
To use this configuration in your Java code, you can create a Bulkhead instance using
the BulkheadRegistry and BulkheadConfig classes from the Resilience4j library, like this:
.maxWaitDuration(Duration.ofMillis(3000))
.maxConcurrentCalls(7)
.build();
Fallbackmethod called when concurrent call exceeds the limit of time and number of calls.
import io.github.resilience4j.bulkhead.annotation.Bulkhead;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.time.LocalTime;
@RestController
public class ShopControllerwithBulkhead {
@Bean
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
@Autowired
private RestTemplate restTemplate;
private int attempts=0;
@GetMapping("/order")
@Bulkhead(name=ORDER_SERVICE,fallbackMethod = "bulkHeadFallback")
public ResponseEntity<String> createOrder()
{
String response =
restTemplate.getForObject("http://localhost:9191/orders",
String.class);
logger.info(LocalTime.now() + " Call processing finished = " +
Thread.currentThread().getName());
return new ResponseEntity<String>(response, HttpStatus.OK);
}
4. On localhost:9191→Other service is a normal order service returns all orders and sleep for a
period of time.[Order Service]
package com.javatechie.os;
import com.javatechie.os.entity.Order;
import com.javatechie.os.repository.OrderRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.*;
import javax.annotation.PostConstruct;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@SpringBootApplication
@RestController
@RequestMapping("/orders")
public class OrderServiceApplication {
@Autowired
private OrderRepository orderRepository;
@PostConstruct
public void initOrdersTable() {
orderRepository.saveAll(Stream.of(
new Order("mobile", "electronics", "white",
20000),
new Order("T-Shirt", "clothes", "black", 999),
new Order("Jeans", "clothes", "blue", 1999),
new Order("Laptop", "electronics", "gray", 50000),
new Order("digital watch", "electronics", "black",
2500),
new Order("Fan", "electronics", "black", 50000)
).
collect(Collectors.toList()));
}
@GetMapping
public List<Order> getOrders(){
try{
Thread.sleep(7000); //we will try with 7000 and 2000
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("Worked");
return orderRepository.findAll();
}
@GetMapping("/{category}")
public List<Order> getOrdersByCategory(@PathVariable String category){
return orderRepository.findByCategory(category);
}
import
com.example.resilience4j.springresilience4jbulkhead.SpringResilience4jB
ulkheadApplication;
import org.springframework.boot.SpringApplication;
import org.springframework.web.client.RestTemplate;
import java.util.stream.IntStream;
int i=1;
IntStream.range(i,7).parallel().forEach(t->{
String response = new
RestTemplate().getForObject("http://localhost:8080/order",
String.class);
System.out.println(response);
});
}
}
We will get this result when run CallinConcurrent class with (concurrent calls = 5) =<
(maxConcurrentCall=7) → five calls are succeeded
2. Second, we will go for normal case and set the thread sleep in order service with 2000ms.
We will get this result when run CallinConcurrent class with (concurrent calls = 20) >
(maxConcurrentCall=7) → 20 calls are succeeded
3.Third, we will go for not-normal case and set the thread sleep in order service with 7000ms.
We will get this result when run CallinConcurrent class with (concurrent calls = 5) <
(maxConcurrentCall=7) → 5 calls are succeeded because time and number of calls did not affect or
exceed the bulkhead configuration.
4.Fourth, we will go for not-normal case and set the thread sleep in order service with 7000ms.
We will get this result when run CallinConcurrent class with (concurrent calls = 20) >
(maxConcurrentCall=7) →
5.Fifth, we will go for not-normal case and set the thread sleep in order service with 7000ms.
We will get this result when run CallinConcurrent class with (concurrent calls = 500) >
(maxConcurrentCall=7) → we will get the same results and if we browse localhost:8080\order
we can see this result:
After 500 requests are done, we refresh the page and we will get:
You can use bulkhead pattern with different services by write the bulkhead
configuration for each service and relate it to @Bulkhead and service a name
@Bulkhead(name=service name→,fallbackMethod = bulkHeadFallbackname
for this service→)
resilience4j.bulkhead:
instances:
Servicename→:
maxWaitDuration: 3000 #wait duaration
maxConcurrentCalls: 7
References:
1. https://dzone.com/articles/resilient-microservices-pattern-bulkhead-pattern
2. https://www.vinsguru.com/bulkhead-pattern/
3. https://github.com/shameed1910/spring-resilience-bulkhead
4. https://www.youtube.com/watch?v=VjmUW6H3GCM