Setup
Add the Spring Boot 2 Starter of Resilience4j to your compile dependency.
The module expects that org.springframework.boot:spring-boot-starter-actuator
and org.springframework.boot:spring-boot-starter-aop
are already provided at runtime. If you are using webflux with spring boot2, you also need io.github.resilience4j:resilience4j-reactor
repositories {
jcenter()
}
dependencies {
compile "io.github.resilience4j:resilience4j-spring-boot2:${resilience4jVersion}"
compile('org.springframework.boot:spring-boot-starter-actuator')
compile('org.springframework.boot:spring-boot-starter-aop')
}
Demo
Setup and usage in Spring Boot 2 is demonstrated into a demo.
Configuration
You can configure your CircuitBreaker, Retry, RateLimiter, Bulkhead, Thread pool bulkhead and TimeLimiter instances in Spring Boot’s application.yml
config file.
For example:
resilience4j.circuitbreaker:
instances:
backendA:
registerHealthIndicator: true
slidingWindowSize: 100
backendB:
registerHealthIndicator: true
slidingWindowSize: 10
permittedNumberOfCallsInHalfOpenState: 3
slidingWindowType: TIME_BASED
minimumNumberOfCalls: 20
waitDurationInOpenState: 50s
failureRateThreshold: 50
eventConsumerBufferSize: 10
recordFailurePredicate: io.github.robwin.exception.RecordFailurePredicate
resilience4j.retry:
instances:
backendA:
maxRetryAttempts: 3
waitDuration: 10s
enableExponentialBackoff: true
exponentialBackoffMultiplier: 2
retryExceptions:
- org.springframework.web.client.HttpServerErrorException
- java.io.IOException
ignoreExceptions:
- io.github.robwin.exception.BusinessException
backendB:
maxRetryAttempts: 3
waitDuration: 10s
retryExceptions:
- org.springframework.web.client.HttpServerErrorException
- java.io.IOException
ignoreExceptions:
- io.github.robwin.exception.BusinessException
resilience4j.bulkhead:
instances:
backendA:
maxConcurrentCalls: 10
backendB:
maxWaitDuration: 10ms
maxConcurrentCalls: 20
resilience4j.thread-pool-bulkhead:
instances:
backendC:
maxThreadPoolSize: 1
coreThreadPoolSize: 1
queueCapacity: 1
resilience4j.ratelimiter:
instances:
backendA:
limitForPeriod: 10
limitRefreshPeriod: 1s
timeoutDuration: 0
registerHealthIndicator: true
eventConsumerBufferSize: 100
backendB:
limitForPeriod: 6
limitRefreshPeriod: 500ms
timeoutDuration: 3s
resilience4j.timelimiter:
instances:
backendA:
timeoutDuration: 2s
cancelRunningFuture: true
backendB:
timeoutDuration: 1s
cancelRunningFuture: false
You can also override the default configuration, define shared configurations and overwrite them in Spring Boot’s application.yml
config file.
For example:
resilience4j.circuitbreaker:
configs:
default:
slidingWindowSize: 100
permittedNumberOfCallsInHalfOpenState: 10
waitDurationInOpenState: 10000
failureRateThreshold: 60
eventConsumerBufferSize: 10
registerHealthIndicator: true
someShared:
slidingWindowSize: 50
permittedNumberOfCallsInHalfOpenState: 10
instances:
backendA:
baseConfig: default
waitDurationInOpenState: 5000
backendB:
baseConfig: someShared
Example of a properties file:
resilience4j.bulkhead.instances.backendA.maxConcurrentCalls=10
resilience4j.bulkhead.instances.backendB.maxWaitDuration=10ms
resilience4j.bulkhead.instances.backendB.maxConcurrentCalls=20
resilience4j.circuitbreaker.instances.backendA.registerHealthIndicator=true
resilience4j.circuitbreaker.instances.backendA.slidingWindowSize=100
resilience4j.circuitbreaker.instances.backendB.registerHealthIndicator=true
resilience4j.circuitbreaker.instances.backendB.slidingWindowSize=10
resilience4j.circuitbreaker.instances.backendB.permittedNumberOfCallsInHalfOpenState=3
resilience4j.circuitbreaker.instances.backendB.slidingWindowType=TIME_BASED
resilience4j.circuitbreaker.instances.backendB.minimumNumberOfCalls=20
resilience4j.circuitbreaker.instances.backendB.waitDurationInOpenState=50s
resilience4j.circuitbreaker.instances.backendB.failureRateThreshold=50
resilience4j.circuitbreaker.instances.backendB.eventConsumerBufferSize=10
resilience4j.circuitbreaker.instances.backendB.recordFailurePredicate=io.github.robwin.exception.RecordFailurePredicate
resilience4j.ratelimiter.instances.backendA.limitForPeriod=10
resilience4j.ratelimiter.instances.backendA.limitRefreshPeriod=1s
resilience4j.ratelimiter.instances.backendA.timeoutDuration=0
resilience4j.ratelimiter.instances.backendA.registerHealthIndicator=true
resilience4j.ratelimiter.instances.backendA.eventConsumerBufferSize=100
resilience4j.ratelimiter.instances.backendB.limitForPeriod=6
resilience4j.ratelimiter.instances.backendB.limitRefreshPeriod=500ms
resilience4j.ratelimiter.instances.backendB.timeoutDuration=3s
resilience4j.retry.instances.backendA.maxRetryAttempts=3
resilience4j.retry.instances.backendA.waitDuration=10s
resilience4j.retry.instances.backendA.enableExponentialBackoff=true
resilience4j.retry.instances.backendA.exponentialBackoffMultiplier=2
resilience4j.retry.instances.backendA.retryExceptions[0]=org.springframework.web.client.HttpServerErrorException
resilience4j.retry.instances.backendA.retryExceptions[1]=java.io.IOException
resilience4j.retry.instances.backendA.ignoreExceptions[0]=io.github.robwin.exception.BusinessException
resilience4j.retry.instances.backendB.maxRetryAttempts=3
resilience4j.retry.instances.backendB.waitDuration=10s
resilience4j.retry.instances.backendB.retryExceptions[0]=org.springframework.web.client.HttpServerErrorException
resilience4j.retry.instances.backendB.retryExceptions[1]=java.io.IOException
resilience4j.retry.instances.backendB.ignoreExceptions[0]=io.github.robwin.exception.BusinessException
resilience4j.thread-pool-bulkhead.instances.backendC.maxThreadPoolSize=1
resilience4j.thread-pool-bulkhead.instances.backendC.coreThreadPoolSize=1
resilience4j.thread-pool-bulkhead.instances.backendC.queueCapacity=1
resilience4j.timelimiter.instances.backendA.timeoutDuration=2s
resilience4j.timelimiter.instances.backendA.cancelRunningFuture=true
resilience4j.timelimiter.instances.backendB.timeoutDuration=1s
resilience4j.timelimiter.instances.backendB.cancelRunningFuture=false
Override configuration via code
You can also override the configuration of a specific CircuitBreaker, Bulkhead, Retry, RateLimiter or TimeLimiter instances by using Customizer for specific instance names. The following shows an example of how to override a configured CircuitBreaker backendA in the above in the YAML file:
@Bean
public CircuitBreakerConfigCustomizer testCustomizer() {
return CircuitBreakerConfigCustomizer
.of("backendA", builder -> builder.slidingWindowSize(100));
}
Resilience4j has its own customizer types which can be used as shown above:
Resilienc4j Type | Instance Customizer class |
---|---|
Circuit breaker | CircuitBreakerConfigCustomizer |
Retry | RetryConfigCustomizer |
Rate limiter | RateLimiterConfigCustomizer |
Bulkhead | BulkheadConfigCustomizer |
ThreadPoolBulkhead | ThreadPoolBulkheadConfigCustomizer |
Time Limiter | TimeLimiterConfigCustomizer |
Annotations
The Spring Boot2 starter provides annotations and AOP Aspects which are auto-configured.
RateLimiter, Retry, CircuitBreaker and Bulkhead annotations support synchronous return types and asynchronous types like CompletableFuture and reactive types like Spring Reactor's Flux and Mono (if you imported appropriate package like resilience4j-reactor
).
Bulkhead annotation has a type attribute to define which bulkhead implementation will be used. By default it is semaphore but you can switch to thread pool by setting the type attribute in the annotation:
@Bulkhead(name = BACKEND, type = Bulkhead.Type.THREADPOOL)
public CompletableFuture<String> doSomethingAsync() throws InterruptedException {
Thread.sleep(500);
return CompletableFuture.completedFuture("Test");
}
An example of all resilience4j supported spring aspects
@CircuitBreaker(name = BACKEND, fallbackMethod = "fallback")
@RateLimiter(name = BACKEND)
@Bulkhead(name = BACKEND)
@Retry(name = BACKEND, fallbackMethod = "fallback")
@TimeLimiter(name = BACKEND)
public Mono<String> method(String param1) {
return Mono.error(new NumberFormatException());
}
private Mono<String> fallback(String param1, IllegalArgumentException e) {
return Mono.just("test");
}
private Mono<String> fallback(String param1, RuntimeException e) {
return Mono.just("test");
}
Fallback methods
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.
If there are multiple fallbackMethod methods, the method that has the most closest match will be invoked, for example:
If you try to recover from NumberFormatException
, the method with signature String fallback(String parameter, IllegalArgumentException exception)}
will be invoked.
You can define one global fallback method with an exception parameter only if multiple methods have the same return type and you want to define the same fallback method for them only once.
Aspect order
The Resilience4j Aspects order is following:
Retry ( CircuitBreaker ( RateLimiter ( TimeLimiter ( Bulkhead ( Function ) ) ) ) )
so Retry
is applied at the end (if needed).
If you need a different order, you must use the functional chaining style instead of the Spring annotations style or explicitly set aspect order using following properties:
- resilience4j.retry.retryAspectOrder
- resilience4j.circuitbreaker.circuitBreakerAspectOrder
- resilience4j.ratelimiter.rateLimiterAspectOrder
- resilience4j.timelimiter.timeLimiterAspectOrder
For example - to make Circuit Breaker starts after Retry finish its work you must set retryAspectOrder
property to greater value than circuitBreakerAspectOrder
value (the higher value = the higher priority).
resilience4j:
circuitbreaker:
circuitBreakerAspectOrder: 1
retry:
retryAspectOrder: 2
Registry event consumer
You can add a RegistryEventConsumer Bean in order to add event consumers to newly created instances.
For example, you can add an RegistryEventConsumer to the RetryRegistry in order to register a logging event consumer to every newly created Retry instance.
@Bean
public RegistryEventConsumer<Retry> myRetryRegistryEventConsumer() {
return new RegistryEventConsumer<Retry>() {
@Override
public void onEntryAddedEvent(EntryAddedEvent<Retry> entryAddedEvent) {
entryAddedEvent.getAddedEntry().getEventPublisher().onEvent(event -> LOG.info(event.toString()));
}
@Override
public void onEntryRemovedEvent(EntryRemovedEvent<Retry> entryRemoveEvent) {
}
@Override
public void onEntryReplacedEvent(EntryReplacedEvent<Retry> entryReplacedEvent) {
}
};
}
Retry log entry would look as follows:
2020-10-26T13:00:19.807034700+01:00[Europe/Berlin]: Retry 'backendA', waiting PT0.1S until attempt '1'. Last attempt failed with exception 'org.springframework.web.client.HttpServerErrorException: 500 This is a remote exception'.
2020-10-26T13:00:19.912028800+01:00[Europe/Berlin]: Retry 'backendA', waiting PT0.1S until attempt '2'. Last attempt failed with exception 'org.springframework.web.client.HttpServerErrorException: 500 This is a remote exception'.
2020-10-26T13:00:20.023250+01:00[Europe/Berlin]: Retry 'backendA' recorded a failed retry attempt. Number of retry attempts: '3'. Giving up. Last exception was: 'org.springframework.web.client.HttpServerErrorException: 500 This is a remote exception'.
Metrics endpoint
CircuitBreaker, Retry, RateLimiter, Bulkhead and TimeLimiter Metrics are automatically published on the Metrics endpoint. To retrieve the names of the available metrics, make a GET request to /actuator/metrics
. Please see Actuator Metrics documentation for more details.
{
"names": [
"resilience4j.circuitbreaker.calls",
"resilience4j.circuitbreaker.buffered.calls",
"resilience4j.circuitbreaker.state",
"resilience4j.circuitbreaker.failure.rate"
]
}
To retrieve a metric, make a GET request to /actuator/metrics/{metric.name}
.
For example: /actuator/metrics/resilience4j.circuitbreaker.calls
{
"name": "resilience4j.circuitbreaker.calls",
"measurements": [
{
"statistic": "VALUE",
"value": 3
}
],
"availableTags": [
{
"tag": "kind",
"values": [
"not_permitted",
"successful",
"failed"
]
},
{
"tag": "name",
"values": [
"backendB",
"backendA"
]
}
]
}
When you want to publish CircuitBreaker endpoints on the Prometheus endpoint, you have to add the dependency io.micrometer:micrometer-registry-prometheus
.
To retrieve metrics, make a GET request to /actuator/prometheus
. For more details please see Micrometer Getting Started
Health endpoint
Spring Boot Actuator health information can be used to check the status of your running application. It is often used by monitoring software to alert someone if a production system has serious issues.
By default the CircuitBreaker or RateLimiter health indicators are disabled, but you can enable them via the configuration. Health Indicators are disabled, because the application status is DOWN, when a CircuitBreaker is OPEN. This might not be what you want to achieve.
management.health.circuitbreakers.enabled: true
management.health.ratelimiters.enabled: true
resilience4j.circuitbreaker:
configs:
default:
registerHealthIndicator: true
resilience4j.ratelimiter:
configs:
default:
registerHealthIndicator: true
A closed CircuitBreaker state is mapped to UP, an open state to DOWN and a half-open state to UNKNOWN.
For example:
{
"status": "UP",
"details": {
"circuitBreakers": {
"status": "UP",
"details": {
"backendB": {
"status": "UP",
"details": {
"failureRate": "-1.0%",
"failureRateThreshold": "50.0%",
"slowCallRate": "-1.0%",
"slowCallRateThreshold": "100.0%",
"bufferedCalls": 0,
"slowCalls": 0,
"slowFailedCalls": 0,
"failedCalls": 0,
"notPermittedCalls": 0,
"state": "CLOSED"
}
},
"backendA": {
"status": "UP",
"details": {
"failureRate": "-1.0%",
"failureRateThreshold": "50.0%",
"slowCallRate": "-1.0%",
"slowCallRateThreshold": "100.0%",
"bufferedCalls": 0,
"slowCalls": 0,
"slowFailedCalls": 0,
"failedCalls": 0,
"notPermittedCalls": 0,
"state": "CLOSED"
}
}
}
}
}
}
Circuitbreakers endpoint
The emitted CircuitBreaker, Retry, RateLimiter, Bulkhead and TimeLimiter events are stored in a separate circular event consumer buffers. The size of a event consumer buffer can be configured in the application.yml file (eventConsumerBufferSize).
The endpoint /actuator/circuitbreakers
lists the names of all CircuitBreaker instances. The endpoint is also available for Retry, RateLimiter, Bulkhead and TimeLimiter.
For example:
{
"circuitBreakers": [
"backendA",
"backendB"
]
}
You can send a post request to /acutator/circuitbreakers/{circuitbreakername}
to update the state to CLOSE, FORCE_OPEN, DISABLE at run time.
You can post a request with the following body
{
"updateState": "FORCE_OPEN"
}
Circuitbreaker events endpoint
The endpoint /actuator/circuitbreakerevents
lists by default the latest 100 emitted events of all CircuitBreaker instances. The endpoint is also available for Retry, RateLimiter, Bulkhead and TimeLimiter.
{
"circuitBreakerEvents": [
{
"circuitBreakerName": "backendA",
"type": "ERROR",
"creationTime": "2017-01-10T15:39:17.117+01:00[Europe/Berlin]",
"errorMessage": "org.springframework.web.client.HttpServerErrorException: 500 This is a remote exception",
"durationInMs": 0
},
{
"circuitBreakerName": "backendA",
"type": "SUCCESS",
"creationTime": "2017-01-10T15:39:20.518+01:00[Europe/Berlin]",
"durationInMs": 0
},
{
"circuitBreakerName": "backendB",
"type": "ERROR",
"creationTime": "2017-01-10T15:41:31.159+01:00[Europe/Berlin]",
"errorMessage": "org.springframework.web.client.HttpServerErrorException: 500 This is a remote exception",
"durationInMs": 0
},
{
"circuitBreakerName": "backendB",
"type": "SUCCESS",
"creationTime": "2017-01-10T15:41:33.526+01:00[Europe/Berlin]",
"durationInMs": 0
}
]
}
Event Streaming endpoint
The following endpoints are automatically generated and events are produced as Server Sent Event(SSE)
/actuator/stream-circuitbreaker-events
/actuator/stream-circuitbreaker-events/{circuitbreakername}
/actuator/stream-circuitbreaker-events/{circuitbreakername}/{errorType}
Hystrix Event Streaming endpoint
The following endpoints are automatically generated and events are produced as Server Sent Event(SSE).
This SSE data can be easily mapped to hystrix compatible data format (specific K V pairs) and be used in Turbine or hystrix dashboard or vizceral. The data that is produced as part of hystrix SSE is enriching events endpoint with state data
This is created as a bridge to support the legacy hystrix eco system of monitoring tools especially for those that are migrating from hystrix to resilence4j to continue to use hystrix eco tools.
/actuator/hystrix-stream-circuitbreaker-events
/actuator/hystrix-stream-circuitbreaker-events/{circuitbreakername}
/actuator/hystrix-stream-circuitbreaker-events/{circuitbreakername}/{errorType}
Updated 8 days ago