As we moved to distributed microservices architecture from the monoliths system, this infrastructure change led us to some distributed communication issues such as slow response time, timeouts while calling another service and interruption in the network connections etc. Actually, In the monolithic system, all these were not the highest priority but now in distributed systems, these issues are the highest priority so by making our services resilient we can handle above issues very easily. There are multiple frameworks which we can use like – Hystrix, Resilience4j and Failsafe etc. All these frameworks provide the implementation of following resilience patterns.
- Bulkhead
- Circuit Breaker
- Fallback
- Retry
- Timeout
Here we are going to talk about Resilience 4j bulkhead pattern. what it is and runtime behaviour of it.
What is Resilience4j?
Resilience4j is a lightweight fault tolerance library, inspired by netflix Hystrix. It is kind of a replacement of Hystrix because Hystrix is not in active development, instead in maintenance mode. It means they won’t review issues, merge pull requests and release new versions. Resilience4j is designed for Java 8 and functional programming and it depends only on one functional library which is Vavr.
Bulkhead –
It ensures the failure in one service doesn’t cause the whole system to go down. It means do not burden service with calls more than its capacity and for that it controls the number of concurrent requests the service can take, the number of resources waiting for the response from the service can be limited by this way. There are two implementations of bulkhead patterns in Resilience4j.
- Semaphore – In this approach, we limit the number of concurrent requests to the service. It will reject the incoming requests once the limit is hit.
- FixedThreadPoolBulkhead – In this approach, we isolate a set of thread pool from system resources, using only that thread pool for the service. We also use a waiting queue apart from the thread pool, if both the thread pool and queue are full then in that case, the request will get rejected with BulkheadFullException.
Bulkhead with Spring boot –
Step-1. Add the custom configuration of the bulkhead according to use case in the application.yaml.
resilience4j.bulkhead: | |
instances: | |
bulkheadService1: | |
maxWaitDuration: 1000ms | |
maxConcurrentCall: 2 | |
resilience4j.thread-pool-bulkhead: | |
instances: | |
bulkheadService1: | |
maxThreadPoolSize: 1 | |
coreThreadPoolSize: 1 | |
queueCapacity: 1 |
Here maxConcurrentCalls is the max amount of parallel execution allowed by the bulkhead and maxWaitDuration is the max amount of time a thread will be blocked for attempting to enter the maxConcurrentCalls count.
Step-2. Create a controller class which will have the following endpoint.
Here, for demo the behaviour of bulkhead, will hit the target endpoint 100 times.
@RestController | |
public class UserRegistrationController { | |
@PostMapping("/register/seller") | |
public String registerAsSeller(@RequestBody SellerDto sellerDto) throws InterruptedException { | |
String registerSeller = null; | |
for (int i = 0; i < 100; i++) { | |
long start = System.currentTimeMillis(); | |
new Thread(() -> { | |
try { | |
userRegistrationResilience4j.registerSeller(sellerDto); | |
} catch (InterruptedException e) { | |
e.printStackTrace(); | |
} | |
}, "service-call").start(); | |
Thread.sleep(50); | |
} | |
return registerSeller; | |
} | |
} |
Here service Implementation is wrapped with @Bulkhead annotation. This annotation supports fallbackMethod attribute and redirects the call to the fallback functions in case of failures observed by pattern. We would also need to define the implementation of the fallback method.
@Service | |
public class UserRegistrationResilience4j { | |
@Bulkhead(name = "bulkheadService1", fallbackMethod = "bulkHeadFallback") | |
public String registerSeller(SellerDto sellerDto) throws InterruptedException { | |
String response = restTemplate.postForObject("/addSeller", sellerDto, String.class); | |
return response; | |
} | |
public String bulkHeadFallback(SellerDto sellerDto, Throwable t) { | |
logger.error("Inside bulkHeadFallback, cause - {}", t.toString()); | |
return "Inside bulkHeadFallback method. Some error occurred while calling service for seller registration"; | |
} | |
} |
Here UserRegistration service will hit another service endpoint(”/sellersList’) and the target service will take about 2 sec for processing every request.
After execution we get the following result. As we can observe after some successful calls the target service has overloaded and some threads got rejected after maxWaitDuration.

That’s all for this blog, we will cover other patterns in our next blog till then stay tuned. If you have any more queries or want to know more about it you can add the comment. I am happy to answer them.