Search code examples
resilience4j

Why Resilience4j circuit breaker does not spin up new threads


I am trying to migrate from Hystrix to Resilience4j and tried the option of configuring resilience4j using annotations (https://resilience4j.readme.io/docs/getting-started-3) which is using resilience4j-spring-boot2 dependency.

When I test using this and analyse the logs, even the method which is decorated with @CircuitBreaker is running in the same http-nio thread:

logs with CircuitBreaker annotation:


2020-01-10 10:31:15,996 [http-nio-8080-exec-1] INFO APP=test-app | ENV=LOCAL | REQUEST_ID=1 | TRACE_ID=eb88d5c53ab97a40 | SPAN_ID=eb88d5c53ab97a40 | CLIENT_ID=1 | CLIENT_VERSION=1 | a.c.s.i.m.p.dependent.DependentApi - Request: http://example.api.com/api/customers/John

2020-01-10 10:31:15,997 [http-nio-8080-exec-1] DEBUG APP=test-app | ENV=LOCAL | REQUEST_ID=1 | TRACE_ID=eb88d5c53ab97a40 | SPAN_ID=eb88d5c53ab97a40 | CLIENT_ID=1 | CLIENT_VERSION=1 | a.c.s.i.myservice.aop.LogingAspect - Enter: au.com.suncorp.insurance.myservice.config.DependentApiRestOperation.getRestOperations() with argument[s] = []

2020-01-10 10:31:15,999 [http-nio-8080-exec-1] DEBUG APP=test-app | ENV=LOCAL | REQUEST_ID=1 | TRACE_ID=eb88d5c53ab97a40 | SPAN_ID=eb88d5c53ab97a40 | CLIENT_ID=1 | CLIENT_VERSION=1 | a.c.s.i.myservice.aop.LoggingAspect - Exit: au.com.suncorp.insurance.myservice.config.DependentApiRestOperation.getRestOperations() with result = org.springframework.web.client.RestTemplate@1f3111d1

2020-01-10 10:31:16,065 [http-nio-8080-exec-1] ERROR APP=test-app | ENV=LOCAL | REQUEST_ID=1 | TRACE_ID=eb88d5c53ab97a40 | SPAN_ID=eb88d5c53ab97a40 | CLIENT_ID=1 | CLIENT_VERSION=1 | a.c.s.i.myservice.aop.LoggingAspect


On the contrary when I create a CircuitBreaker from CircuitBreakerFactory in that case I can see new circuit breaker threads spinning up when the control in inside the method protected by circuit breaker.

Logs with CircuitBreakerFactory:


2020-01-10 10:50:04,178 [hystrix-HystrixCircuitBreakerFactory-1] DEBUG APP=test-app | ENV=LOCAL | REQUEST_ID= | TRACE_ID=bde6e74d65833730 | SPAN_ID=d5dc68689645201a | CLIENT_ID= | CLIENT_VERSION= | a.c.s.i.myservice.aop.LoggingAspect - Enter: au.com.suncorp.insurance.myservice.config.properties.DependentApiProperties.getAddress() with argument[s] = []

2020-01-10 10:50:04,178 [hystrix-HystrixCircuitBreakerFactory-1] DEBUG APP=test-app | ENV=LOCAL | REQUEST_ID= | TRACE_ID=bde6e74d65833730 | SPAN_ID=d5dc68689645201a | CLIENT_ID= | CLIENT_VERSION= | a.c.s.i.myservice.aop.LoggingAspect - Exit: au.com.suncorp.insurance.myservice.config.properties.DependentApiProperties.getAddress() with result = au.com.suncorp.insurance.myservice.config.properties.DependentApiProperties$Address@1928e7f3

2020-01-10 10:50:04,179 [hystrix-HystrixCircuitBreakerFactory-1] DEBUG APP=test-app | ENV=LOCAL | REQUEST_ID= | TRACE_ID=bde6e74d65833730 | SPAN_ID=d5dc68689645201a | CLIENT_ID= | CLIENT_VERSION= | a.c.s.i.myservice.aop.LoggingAspect - Enter: au.com.suncorp.insurance.myservice.config.properties.DependentApiProperties.getAddress() with argument[s] = []

2020-01-10 10:50:04,179 [hystrix-HystrixCircuitBreakerFactory-1] DEBUG APP=test-app | ENV=LOCAL | REQUEST_ID= | TRACE_ID=bde6e74d65833730 | SPAN_ID=d5dc68689645201a | CLIENT_ID= | CLIENT_VERSION= | a.c.s.i.myservice.aop.LoggingAspect - Exit: au.com.suncorp.insurance.myservice.config.properties.DependentApiProperties.getAddress() with result = au.com.suncorp.insurance.myservice.config.properties.DependentApiProperties$Address@1928e7f3

2020-01-10 10:50:04,184 [hystrix-HystrixCircuitBreakerFactory-1] INFO APP=test-app | ENV=LOCAL | REQUEST_ID= | TRACE_ID=bde6e74d65833730 | SPAN_ID=d5dc68689645201a | CLIENT_ID= | CLIENT_VERSION= | a.c.s.i.m.p.dependent.DependentApi - Request: http://example.api.com/api/customers/John

2020-01-10 10:50:04,186 [hystrix-HystrixCircuitBreakerFactory-1] DEBUG APP=test-app | ENV=LOCAL | REQUEST_ID= | TRACE_ID=bde6e74d65833730 | SPAN_ID=d5dc68689645201a | CLIENT_ID= | CLIENT_VERSION= | a.c.s.i.myservice.aop.LoggingAspect - Enter: au.com.suncorp.insurance.myservice.config.DependentApiRestOperation.getRestOperations() with argument[s] = []



Solution

  • The Resilience4j Spring Boot Starter und Spring Cloud CircuitBreaker are two different/seperate projects.

    Spring Cloud CircuitBreaker runs methods in a seperate thread pool. See -> https://github.com/spring-cloud/spring-cloud-circuitbreaker/blob/master/spring-cloud-circuitbreaker-resilience4j/src/main/java/org/springframework/cloud/circuitbreaker/resilience4j/Resilience4JCircuitBreaker.java#L68

    In Resilience4j and the Spring Boot Starter the CircuitBreaker and Threadpool-based Bulkhead are two different resilience patterns which you might want to combine or not. If you want to combine them, you have to apply two Annnotations to your method -> @CircuitBreaker and @Bulkhead(type = Type.THREADPOOL).

    Have a look at the demo -> https://github.com/resilience4j/resilience4j-spring-boot2-demo