Spring Boot Resilience4j circuit breaker and fall back not implementing

2.1k Views Asked by At

I have a simple rest api with Resilience4j implementation but for some reason the circuit breaker or fallback implementation is not working. I am not sure If I am using right dependency. I have a simple member-info-api which consume another api called benefit-api. I have implemented the circuit breaker on benefit api call and a logic to create a timeout exception. When it is run its still wait on the timeout and then throw the TimeOutException. Look like my Circuit breaker is not being implemented. here is my code:

build.gradle

plugins {
    id 'org.springframework.boot' version '2.7.2'
    id 'io.spring.dependency-management' version '1.0.12.RELEASE'
    id 'java'
}

group = 'com.thomsoncodes'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'

repositories {
    mavenCentral()
}

ext {
    set('springCloudVersion', "2021.0.3")
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-webflux'
    implementation 'org.springframework.cloud:spring-cloud-starter-circuitbreaker-reactor-resilience4j'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testImplementation 'io.projectreactor:reactor-test'
}

dependencyManagement {
    imports {
        mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
    }
}

tasks.named('test') {
    useJUnitPlatform()
}

controller class

@RestController
public class WebController {
    public static final Logger LOG = LoggerFactory.getLogger(WebController.class);
    @Autowired
    private MemberInfoService memberInfoService;
    
    @GetMapping("member/{memId}")
    public ResponseEntity<MemberInfo> memberInfo(@PathVariable("memId") String memId) throws TimeoutException { 
        LOG.info("---Beginning of the WebController.methodmemberInfo()---");
        MemberInfo resp = null;
        resp = memberInfoService.getMemberInfo(memId);
        
        LOG.info("---End of the WebController.methodmemberInfo()---");
        return new ResponseEntity<MemberInfo>(resp, (HttpStatus.OK));
    }
}

MemberInfoService.java

@Service
public class MemberInfoService {
    public static final Logger LOG = LoggerFactory.getLogger(MemberInfoService.class);
    
    @Autowired
    private UserInfoService userInfoService;
    
    @Autowired
    private BenefitService benefitService;

    public MemberInfo getMemberInfo(String memId) throws TimeoutException {
        LOG.info("---End of the WebController.MemberInfoService()---");
        
        MemberInfo memberInfo = null;
        memberInfo = userInfoService.getUserInfo(memId);
        Benefit benefit = null;
        benefit = benefitService.getBenefitInfo(memId);
        memberInfo.setBenefit(benefit);
        
        LOG.info("---End of the WebController.MemberInfoService()---");
        return memberInfo;
    }
}

BenefitService.java

@Service
public class BenefitService {
    public static final Logger LOG = LoggerFactory.getLogger(BenefitService.class);

    @Autowired
    private WebClient benefitApiClient;
    
    @CircuitBreaker(name = "benefitService", fallbackMethod = "buildFallbackBenefitInfo")
    @RateLimiter(name = "benefitService", fallbackMethod = "buildFallbackBenefitInfo")
    @Retry(name = "retryBenefitService", fallbackMethod = "buildFallbackBenefitInfo")
    public Benefit getBenefitInfo(String memId) throws TimeoutException {
        LOG.info("---Beginning of the BenefitService.getBenefitInfo()---");
        randomlyRunLong();
        return benefitApiClient.get()
                .uri("/member/benefit/" + memId)
                .retrieve()
                .bodyToMono(Benefit.class)
                .block();
    }
    
    public Benefit buildFallbackBenefitInfo(String memId, Throwable t) throws TimeoutException {
        Benefit benefit = null;
        benefit = new Benefit();
        benefit.setBenefitId("00000");
        benefit.setMemeberId("00000");
        
        return benefit;
    }
    
    
    private void randomlyRunLong() throws TimeoutException{
        Random rand = new Random();
        int randomNum = rand.nextInt((3 - 1) + 1) + 1;
        if (randomNum==3) sleep();
    }
    private void sleep() throws TimeoutException{
        try {
            System.out.println("Sleep");
            Thread.sleep(5000);
            throw new java.util.concurrent.TimeoutException();
        } catch (InterruptedException e) {
            LOG.error(e.getMessage());
        }
    }       
}

application.yml

server:
  port: 9095

management.endpoints.enabled-by-default: false
management.endpoint.health:
 enabled: true
 show-details: always
 
resilience4j.circuitbreaker:
  instances:
    benefitService:
      registerHealthIndicator: true
      ringBufferSizeInClosedState: 5
      ringBufferSizeInHalfOpenState: 3
      waitDurationInOpenState: 10s
      failureRateThreshold: 50
      recordExceptions:
        - org.springframework.web.client.HttpServerErrorException
        - java.io.IOException
        - java.util.concurrent.TimeoutException
        - org.springframework.web.client.ResourceAccessException

resilience4j.ratelimiter:
  instances:
    benefitService:
      limitForPeriod: 5
      limitRefreshPeriod: 5000
      timeoutDuration: 1000ms

resilience4j.retry:
  instances:
    retryBenefitService:
      maxRetryAttempts: 5
      waitDuration: 10000
      retry-exceptions:
        - java.util.concurrent.TimeoutException

resilience4j.bulkhead:
  instances:
    bulkheadBenefitService:
      maxWaitDuration: 2ms
      maxConcurrentCalls: 20


resilience4j.thread-pool-bulkhead:
  instances:
    bulkheadBenefitService:
      maxThreadPoolSize: 1
      coreThreadPoolSize: 1
      queueCapacity: 1

I am not sure what wrong I am doing here. A help would be really appreciated. Thanks in advance

1

There are 1 best solutions below

1
On

The default Resilience4j aspect order is

Retry( CircuitBreaker( RateLimiter( TimeLimiter( Bulkhead( function)))))

Your RateLimiter has a fallback, so it never throws an exception, so CircuitBreaker never sees a failed invocation. Specify a fallback on only the last aspect that will execute.