Spring Resiliency API
👉 Learn Spring Cloud concepts.
11. Using Resilience4j - retry
Project ref: b9-curcuit-breacker
- Purpose / Feature
Resilience4jis a replacement ofnetflix-hystrixfor circuit breaker framework.- Resilience4j is a lightweight fault tolerance library designed for functional programming.
- Resilience4j provides higher-order functions (decorators) to enhance any functional interface, lambda expression or method reference with a Circuit Breaker, Rate Limiter, Retry or Bulkhead.
- *NOTE: ** *Resilience4j 1 requires Java 8, while Resilience4j 2 requires Java 17.
- Steps
- Project setup: Create a new springboot project with below dependencies.
- Step-1: Add following dependencies i n
POM.xml.org.springframework.boot:spring-boot-starter-actuatororg.springframework.boot:spring-boot-starter-weborg.springframework.boot:spring-boot-starter-aopio.github.resilience4j:resilience4j-spring-boot2:2.2.0
- Step-2: Annotate the controller API method with
@Retry(name = "default").- This enables
retiesfor the API with default configuration with max attempt 3.
- This enables
- Step-3: We can provide a custom name and provide customized config for
reties.- Annotate the controller API method with
@Retry(name = "b9-cb-retries").
- Annotate the controller API method with
- Step-4: Add below properties in
application.propertiesto control max retries.resilience4j.retry.instances.b9-cb-retries.maxAttempts=5#NEW#resilience4j.retry.instances.b9-cb-retries.maxRetryAttempts=5#OLD
- Step-5: Add below properties in
application.propertiesto wait duration between each retries, in this example - 1 seconds.resilience4j.retry.instances.b9-cb-retries.wait-duration=1s
- Step-6: Increase the wait duration exponentionally between each reties.
- So first retry after 1s, next 2s, next 4s, next 8s and so on.
resilience4j.retry.instances.b9-cb-retries.enable-exponential-backoff=true
- Step-7: Configure fallbackMathod property to return hard-coded response if failed after reties.
@Retry(name = "default", fallbackMethod="hardcodedResponse")- Define a method names
hardcodedResponsewith method argument acceptingThrowableintance. - We can overload this method with different Exception types, to process
fallbackfor different APIs.
- Maven / External dependency
Required dependency. ```xml
org.springframework.boot spring-boot-starter-actuator org.springframework.boot spring-boot-starter-web <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <dependency> <groupId>io.github.resilience4j</groupId> <artifactId>resilience4j-spring-boot2</artifactId> <version>2.2.0</version> </dependency>
- Code / Config changes
- Controller:HelloWorldController.java
- imports
import io.github.resilience4j.retry.annotation.Retry;
- Annotate the method with
@Retryannotation.@RestController public class HelloWorldController { private static final Logger logger = LoggerFactory.getLogger(HelloWorldController.class); private Integer counter = 1; /** * Retry default return failure after 3 retries. * @return */ @GetMapping("/greet") @Retry(name = "default", fallbackMethod = "hardcodedResponseFallbackMethod") public String greeting() { logger.info("***** HelloWorldController.greeting() method called."); logger.info("***** Request id : {}", counter); try { if (counter % 5 == 0) { throw new DefaultRetryRuntimeException("curcuite breacker test. Request Id : " + counter); } } catch (Exception ex) { logger.info("**** Failed for request id : {}", counter); throw ex; } finally { counter++; } return "Guten Morgen"; } /** * Custom retry as configured in the application.properties. * @return */ @GetMapping("/greet-cr") @Retry(name = "b9-cb-retries", fallbackMethod = "hardcodedResponseFallbackMethod") public String greetingCustomRetries() { logger.info("***** HelloWorldController.greeting() method called."); logger.info("***** Request id : {}", counter); try { if (counter % 6 != 0) { throw new CustomRetryRuntimeException("curcuite breacker test. Request Id : " + counter); } } catch (Exception ex) { logger.info("**** Failed for request id : {}", counter); throw ex; } finally { counter++; } return "Guten Morgen"; } /** * Circuit breaker fallback method. * @param ex */ public String hardcodedResponseFallbackMethod(Exception ex) { if (ex instanceof DefaultRetryRuntimeException) { return "Guten Morgen, default resonse for DefaultRetryRuntimeException"; } else if (ex instanceof CustomRetryRuntimeException) { return "Guten Morgen, default resonse for CustomRetryRuntimeException"; } else { return "default response."; } } }
- imports
- Application Config:application.properties
spring.application.name=b9-curcuit-breacker # Start: Circuit breaker config # custom Retry - Max Retries for 5 resilience4j.retry.instances.b9-cb-retries.max-attempts=5 # Wait duration between each retries resilience4j.retry.instances.b9-cb-retries.wait-duration=1s # Increase the wait duration exponentionally between each reties resilience4j.retry.instances.b9-cb-retries.enable-exponential-backoff=true # End: Circuit breaker config
- Controller:HelloWorldController.java
Note: This is an important note.
- Notes:
- Some important key point / takeaway note.
- Some takeaway:
- Sub topic takeaway.
- References:
12. Using Resilience4j - circuit breaker
Project ref: b9-curcuit-breacker
- Purpose / Feature
- Note: This project is extention to
Using Resilience4j - retry. So all settings will remain same except for that instead of @Retry** annotation we’ll use **@CircuitBreaker* annotation. - If there are continious failure identified for a microservice, then after certain attemts, circuit breaker stop processing the API and send the response directly from
fallbackMethod. - The CircuitBreaker is implemented via a finite state machine with three normal states: CLOSED, OPEN and HALF_OPEN and two special states DISABLED and FORCED_OPEN.
- It uses a sliding window to store and aggregate the outcome of calls. You can choose between a count-based sliding window and a time-based sliding window.
- The count-based sliding window aggregrates the outcome of the last N calls.
- The time-based sliding window aggregrates the outcome of the calls of the last N seconds.
- Aspect order: The Resilience4j Aspects order is the following:
- Retry ( CircuitBreaker ( RateLimiter ( TimeLimiter ( Bulkhead ( Function ) ) ) ) )
- Note: This project is extention to
- Steps
- Project setup: Follow
Section 11. Using Resilience4j - retryfor project setup. - Step-1: Replace
@Retryannotation with@CircuitBreakerannotation.@CircuitBreaker(name = "default", fallbackMethod = "hardcodedResponseFallbackMethod")
- Step-2: keep other settings as it.
- Step-3: Configure to increase retry duration exponentilly in case all requests keep failing.
resilience4j.retry.instances.b9-cb-retries.enable-exponential-backoff=true
- Project setup: Follow
- Maven / External dependency
Required dependency. ```xml
org.springframework.boot spring-boot-starter-actuator org.springframework.boot spring-boot-starter-web <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <dependency> <groupId>io.github.resilience4j</groupId> <artifactId>resilience4j-spring-boot2</artifactId> <version>2.2.0</version> </dependency>
- Code / Config changes
- Controller:HelloWorldControllerCircuitBreaker.java
- imports
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
- Annotate the method with
@CircuitBreakerannotation.@RestController public class HelloWorldControllerCircuitBreaker { private static final Logger logger = LoggerFactory.getLogger(HelloWorldControllerCircuitBreaker.class); private Integer counter = 1; /** * Retry default return failure after 3 retries. * @return */ @GetMapping("/cb/greet") // @Retry(name = "default", fallbackMethod = "hardcodedResponseFallbackMethod") @CircuitBreaker(name = "default", fallbackMethod = "hardcodedResponseFallbackMethod") public String greeting() { logger.info("***** HelloWorldController.greeting() method called."); logger.info("***** Request id : {}", counter); try { if (counter % 2 == 0) { throw new DefaultRetryRuntimeException("curcuite breacker test. Request Id : " + counter); } } catch (Exception ex) { logger.info("**** Failed for request id : {}", counter); throw ex; } finally { counter++; } return "Guten Morgen"; } /** * Circuit breaker fallback method. * @param ex */ public String hardcodedResponseFallbackMethod(Exception ex) { if (ex instanceof DefaultRetryRuntimeException) { return "Guten Morgen, default resonse for DefaultRetryRuntimeException"; } else if (ex instanceof CustomRetryRuntimeException) { return "Guten Morgen, default resonse for CustomRetryRuntimeException"; } else { return "default response."; } } }
- imports
- Application Config:application.properties
spring.application.name=b9-curcuit-breacker # Start: Circuit breaker config # custom Retry - Max Retries for 5 resilience4j.retry.instances.b9-cb-retries.max-attempts=5 # Wait duration between each retries resilience4j.retry.instances.b9-cb-retries.wait-duration=1s # Increase the wait duration exponentionally between each reties resilience4j.retry.instances.b9-cb-retries.enable-exponential-backoff=true # End: Circuit breaker config
- Controller:HelloWorldControllerCircuitBreaker.java
Note: This is an important note.
- Notes:
- There’s many more advance configutation for circuit breaked and retry.
- Follow the link in
Referencesection.
- Follow the link in
- The fallback method mechanism works like a try/catch block. If a fallback method is configured, every exception is forwarded to a fallback method executor.
- The fallback method executor is searching for the best matching fallback method which can handle the exception. Similar to a catch block.
- The fallback is executed independently of the current state of the circuit breaker.
- It’s important to remember that a fallback method should be placed in the same class and must have the same method signature with just ONE extra target exception parameter.
- There’s many more advance configutation for circuit breaked and retry.
- References:
- Advance configurations:
13. Using Resilience4j - API advance configuration
Project ref: b9-curcuit-breacker
- Purpose / Feature
- Note: Advance API configuration.
- Aspect order: The Resilience4j Aspects order is the following:
- Retry ( CircuitBreaker ( RateLimiter ( TimeLimiter ( Bulkhead ( Function ) ) ) ) )
- Rate Limiting: Setting up the max allowed API calls to the annotated API method during a time period.
- Time Limiting: Setting up max API calls allowed in a defined time duration.
- Bulk Head: Define the max concurrent requests to be processed by the API.
- We can also enable Health checks circuit breaked and Ratelimiter which are by default disabled.
- Steps
- Project setup: Follow
Section 11. Using Resilience4j - retryfor project setup. - Step-1: Add annotation @RateLimiter, to configure the no. of requests allowed in a give time period.
- Step-2: Add annotation @Bulkhead, to configure the no. of requests allowed for concurrent execution.
- Step-3: Add annotation @TimeLimiter, for configure allowed execution time.
- Project setup: Follow
- Maven / External dependency
Required dependency. ```xml
org.springframework.boot spring-boot-starter-actuator org.springframework.boot spring-boot-starter-web <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <dependency> <groupId>io.github.resilience4j</groupId> <artifactId>resilience4j-spring-boot2</artifactId> <version>2.2.0</version> </dependency>
- Code / Config changes
- Controller:HelloWorldControllerCircuitBreaker.java
- imports
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
- Annotate the method with
@CircuitBreakerannotation.@RestController public class HelloWorldControllerCircuitBreaker { private static final Logger logger = LoggerFactory.getLogger(HelloWorldControllerCircuitBreaker.class); private Integer counter = 1; /** * Retry default return failure after 3 retries. * * @return */ @GetMapping("/cb/greet") @CircuitBreaker(name = "greeting-api", fallbackMethod = "hardcodedResponseFallbackMethod") @Retry(name = "greeting-api", fallbackMethod = "hardcodedResponseFallbackMethod") public String greeting() { logger.info("***** HelloWorldController.greeting() method called."); logger.info("***** Request id : {}", counter); try { if (counter % 2 == 0) { throw new DefaultRetryRuntimeException("curcuite breacker test. Request Id : " + counter); } } catch (Exception ex) { logger.info("**** Failed for request id : {}", counter); throw ex; } finally { counter++; } return "Guten Morgen"; } /** * Testing other annotations. * * @return */ @GetMapping("/cb/greet-adv") @CircuitBreaker(name = "greeting-api", fallbackMethod = "hardcodedResponseFallbackMethod") // we can also add fallbackMethod = "RateLimiterFallbackMethod" @RateLimiter(name = "greeting-api") // we can also add fallbackMethod = "bulkHeadFallbackMethod" @Bulkhead(name = "greeting-api") @Retry(name = "greeting-api", fallbackMethod = "hardcodedResponseFallbackMethod") // We can also add fallbackMethod = "timeLimiterFallbackMethod" @TimeLimiter(name = "greeting-api") public String greetingAdvance() { logger.info("***** HelloWorldController.greeting() method called."); logger.info("***** Request id : {}", counter); try { if (counter % 2 == 0) { throw new DefaultRetryRuntimeException("curcuite breacker test. Request Id : " + counter); } } catch (Exception ex) { logger.info("**** Failed for request id : {}", counter); throw ex; } finally { counter++; } return "Guten Morgen"; } /** * Circuit breaker fallback method. * * @param ex */ public String hardcodedResponseFallbackMethod(Exception ex) { if (ex instanceof DefaultRetryRuntimeException) { return "Guten Morgen, default resonse for DefaultRetryRuntimeException"; } else if (ex instanceof CustomRetryRuntimeException) { return "Guten Morgen, default resonse for CustomRetryRuntimeException"; } else { return "default response."; } } }
- imports
- Application Config:application.properties
spring.application.name=b9-curcuit-breacker # Start: Circuit breaker config # custom Retry - Max Retries for 5 resilience4j.retry.instances.b9-cb-retries.max-attempts=5 # Wait duration between each retries resilience4j.retry.instances.b9-cb-retries.wait-duration=1s # Increase the wait duration exponentionally between each reties resilience4j.retry.instances.b9-cb-retries.enable-exponential-backoff=true # Start: Enable health checks for actuator management.health.circuitbreakers.enabled: true management.health.ratelimiters.enabled: true resilience4j.ratelimiter.instances.default.register-health-indicator=true resilience4j.ratelimiter.instances.greeting-api.register-health-indicator=true resilience4j.ratelimiter.instances.b9-cb-retries.register-health-indicator=true # End: Enable health checks for actuator # Ratelimiter - allows max of 2 reuests in every 10 seconds. resilience4j.ratelimiter.instances.greeting-api.limit-for-period=2 resilience4j.ratelimiter.instances.greeting-api.limit-refresh-period=10s resilience4j.ratelimiter.instances.greeting-api.timeout-duration=3s resilience4j.ratelimiter.instances.greeting-api.event-consumer-buffer-size=100 # BulkHead - Allows maximum of only 10 concurrent calls. resilience4j.bulkhead.instances.greeting-api.max-concurrent-calls=10 resilience4j.thread-pool-bulkhead.instances.greeting-api.core-thread-pool-size=2 resilience4j.thread-pool-bulkhead.instances.greeting-api.max-thread-pool-size=5 resilience4j.thread-pool-bulkhead.instances.greeting-api.queue-capacity=1 resilience4j.thread-pool-bulkhead.instances.greeting-api.writable-stack-trace-enabled=true resilience4j.thread-pool-bulkhead.instances.greeting-api.keep-alive-duration=600s # TimeLimier - resilience4j.timelimiter.instances.greeting-api.timeout-duration=2s resilience4j.timelimiter.instances.greeting-api.cancel-running-future=true # End: Circuit breaker config
- Controller:HelloWorldControllerCircuitBreaker.java
Note: This is an important note.
- Notes:
- There’s many more advance configutation for circuit breaked and retry.
- Follow the link in
Referencesection.
- Follow the link in
- There’s many more advance configutation for circuit breaked and retry.
- References:
- Advance configurations: