Skip to content

Commit 1e063a4

Browse files
RomehRobWin
authored andcommitted
Issue ReactiveX#348: Spring boot2 support for retry
* Add response predicate to retry sync and async for enhancement ReactiveX#259 * ReactiveX#348 add sync retry spring boot annotation and config support for spring boot 1 and 2 * ReactiveX#348 add sync retry spring boot annotation and config support for spring boot 1 and 2 * ReactiveX#348 adding java doc * ReactiveX#348 adding java doc * ReactiveX#348 adding spring override bean option for the retry spring boot starters * ReactiveX#348 adding spring async retry aspect support * ReactiveX#348 adding annotation validation protection of using retry and async retry annotation together in the class level * ReactiveX#348 updating java doc * ReactiveX#348 adding the new prefix of async retry metrics and fixing the merge conflicts * ReactiveX#348 covering review comments * ReactiveX#348 removing unneeded lines * ReactiveX#348 adding the updated spring boot documentation for the retry spring boot usage for spring boot 1 and 2 * ReactiveX#348 documentation review comments * ReactiveX#348 documentation review comments and removing health indicators for retry support in spring boot * ReactiveX#348 documentation review comments
1 parent 6b7078f commit 1e063a4

File tree

49 files changed

+3325
-250
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+3325
-250
lines changed
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Copyright 2019 Mahmoud Romeh
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.github.resilience4j.retry.annotation;
17+
18+
import java.lang.annotation.Documented;
19+
import java.lang.annotation.ElementType;
20+
import java.lang.annotation.Retention;
21+
import java.lang.annotation.RetentionPolicy;
22+
import java.lang.annotation.Target;
23+
24+
/**
25+
* This annotation can be applied to a class or a specific method.
26+
* Applying it on a class is equivalent to applying it on all its public methods.
27+
* The annotation enables backend retry for all methods where it is applied.
28+
* Backend retry is performed via a retry
29+
*/
30+
@Retention(value = RetentionPolicy.RUNTIME)
31+
@Target(value = {ElementType.METHOD, ElementType.TYPE})
32+
@Documented
33+
public @interface AsyncRetry {
34+
35+
/**
36+
* Name of the async retry.
37+
*
38+
* @return return name of the async retry
39+
*/
40+
String name();
41+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Copyright 2019 Mahmoud Romeh
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.github.resilience4j.retry.annotation;
17+
18+
import java.lang.annotation.Documented;
19+
import java.lang.annotation.ElementType;
20+
import java.lang.annotation.Retention;
21+
import java.lang.annotation.RetentionPolicy;
22+
import java.lang.annotation.Target;
23+
24+
/**
25+
* This annotation can be applied to a class or a specific method.
26+
* Applying it on a class is equivalent to applying it on all its public methods.
27+
* The annotation enables backend retry for all methods where it is applied.
28+
* Backend retry is performed via a retry
29+
*/
30+
@Retention(value = RetentionPolicy.RUNTIME)
31+
@Target(value = {ElementType.METHOD, ElementType.TYPE})
32+
@Documented
33+
public @interface Retry {
34+
35+
/**
36+
* Name of the sync retry.
37+
*
38+
* @return the name of the sync retry.
39+
*/
40+
String name();
41+
}

resilience4j-documentation/src/docs/asciidoc/addon_guides/springboot.adoc

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,30 @@ dependencies {
2121
Spring Boot Actuator health information can be used to check the status of your running application.
2222
It is often used by monitoring software to alert someone if a production system has serious issues.
2323

24+
==== Retry
25+
When you enable retry, metrics are automatically published on the metrics endpoint.
26+
27+
For example:
28+
29+
[source,json]
30+
----
31+
{
32+
"names": [
33+
"resilience4j.retry.backendA.failedCallsWithoutRetry",
34+
"resilience4j.retry.backendA.failedCallsWithRetry",
35+
"resilience4j.retry.backendA.successCalls",
36+
"resilience4j.retry.backendA.successCallsWithRetry",
37+
"resilience4j.asyncRetry.backendA.failedCallsWithoutRetry",
38+
"resilience4j.asyncRetry.backendB.failedCallsWithRetry",
39+
"resilience4j.asyncRetry.backendB.successCalls",
40+
"resilience4j.asyncRetry.backendB.successCallsWithRetry"
41+
]
42+
}
43+
----
44+
45+
When you want to publish Retry/AsyncRetry endpoints on the Prometheus endpoint, you have to add the dependency `io.micrometer:micrometer-registry-prometheus`.
46+
and you have same metrics exposed there , check circuit breaker below for more information about the example.
47+
2448
==== CircuitBreaker
2549
This demo publishes the status and metrics of all CircuitBreakers via a custom `CircuitBreakerHealthIndicator`.
2650
A closed CircuitBreaker state is mapped to UP, an open state to DOWN and a half-open state to UNKNOWN.
@@ -133,6 +157,67 @@ For example:
133157

134158
==== Configuration
135159

160+
===== Retry
161+
You can configure your Retries in Spring Boot's `application.yml` config file.
162+
For example
163+
[source,yaml]
164+
----
165+
resilience4j.retry:
166+
retryAspectOrder: 399
167+
backends:
168+
retryBackendA:
169+
maxRetryAttempts: 3
170+
waitDuration: 600
171+
eventConsumerBufferSize: 100
172+
enableExponentialBackoff: false
173+
exponentialBackoffMultiplier: 2
174+
enableRandomizedWait: false
175+
randomizedWaitFactor: 2
176+
retryExceptionPredicate: io.github.resilience4j.circuitbreaker.RecordFailurePredicate
177+
retryExceptions:
178+
- java.io.IOException
179+
ignoreExceptions:
180+
- io.github.resilience4j.circuitbreaker.IgnoredException
181+
----
182+
The rules for Retry configuration :
183+
184+
- By default the same back end configuration will be used for sync and async retry configuration if not defined otherwise.
185+
- enableRandomizedWait and enableExponentialBackoff is false by default.
186+
- You can not enable both enableRandomizedWait and enableExponentialBackoff , validation exception will be thrown if it happen.
187+
- If exponentialBackoffMultiplier is not provided if enableExponentialBackoff is enabled , default ExponentialBackoff will be used , same story for enableRandomizedWait.
188+
189+
The rules for Retry spring annotation usage :
190+
191+
- You can use the same back-end configuration for both sync and async retry if you use both annotations in for the same backed method level wise only ,
192+
if you mix annotations between class level and method level on the same back-end class , validation exception will be thrown
193+
- For `AsyncRetry` annotation , please make sure the return type is instance of Java `CompletionStage` otherwise runtime exception will be thrown
194+
195+
Code example of retry and async retry annotation usage in Java Spring component :
196+
[source,java]
197+
----
198+
@component
199+
public class RetryDummyServiceImpl implements RetryDummyService {
200+
201+
@Retry(name = RetryDummyService.BACKEND)
202+
@Override
203+
public void doSomething(boolean throwBackendTrouble) throws IOException {
204+
if (throwBackendTrouble) {
205+
throw new IOException("Test Message");
206+
}
207+
}
208+
209+
@AsyncRetry(name = RetryDummyService.BACKEND)
210+
@Override
211+
public CompletionStage<String> doSomethingAsync(boolean throwException) throws IOException {
212+
if (throwException) {
213+
throw new IOException("Test Message");
214+
} else {
215+
return CompletableFuture.supplyAsync(() -> "test");
216+
}
217+
}
218+
219+
}
220+
----
136221
===== CircuitBreaker
137222
You can configure your CircuitBreakers in Spring Boot's `application.yml` config file.
138223
For example
@@ -197,6 +282,82 @@ WARNING: Please be careful changing of `CircuitBreaker`/`RateLimiter` ordering c
197282

198283
==== Event Monitoring
199284

285+
===== Retry
286+
287+
The emitted Retry events are stored in a separate circular event consumer buffers. The size of a event consumer buffer can be configured per Retry in the application.yml file (eventConsumerBufferSize).
288+
The demo adds a custom Spring Boot Actuator endpoint which can be used to monitor the emitted events of your Retries.
289+
The endpoint `/management/retries` lists the names of all Retries instances.
290+
For example:
291+
----
292+
{
293+
"retries": [
294+
"retryBackendA",
295+
"retryBackendA"
296+
]
297+
}
298+
----
299+
300+
The endpoint `/management/retries/events` lists the latest 100 emitted events of all Retries instances.
301+
302+
----
303+
{
304+
"retryEvents": [
305+
{
306+
"retryName": "retryBackendC",
307+
"type": "RETRY",
308+
"creationTime": "2019-03-11T17:32:49.648+01:00[Europe/Brussels]",
309+
"errorMessage": "java.io.IOException: Test Message",
310+
"numberOfAttempts": 1
311+
},
312+
{
313+
"retryName": "retryBackendA",
314+
"type": "RETRY",
315+
"creationTime": "2019-03-11T17:32:50.259+01:00[Europe/Brussels]",
316+
"errorMessage": "java.io.IOException: Test Message",
317+
"numberOfAttempts": 2
318+
},
319+
{
320+
"retryName": "retryBackendA",
321+
"type": "ERROR",
322+
"creationTime": "2019-03-11T17:32:50.866+01:00[Europe/Brussels]",
323+
"errorMessage": "java.io.IOException: Test Message",
324+
"numberOfAttempts": 3
325+
}
326+
]
327+
}
328+
----
329+
330+
The endpoint `/management/retries/events/{retryrName}` lists the latest emitted events of a specific Retry.
331+
For example `/management/retries/events/retryBackendA`:
332+
333+
----
334+
{
335+
"retryEvents": [
336+
{
337+
"retryName": "retryBackendA",
338+
"type": "RETRY",
339+
"creationTime": "2019-03-11T17:32:49.648+01:00[Europe/Brussels]",
340+
"errorMessage": "java.io.IOException: Test Message",
341+
"numberOfAttempts": 1
342+
},
343+
{
344+
"retryName": "retryBackendA",
345+
"type": "RETRY",
346+
"creationTime": "2019-03-11T17:32:50.259+01:00[Europe/Brussels]",
347+
"errorMessage": "java.io.IOException: Test Message",
348+
"numberOfAttempts": 2
349+
},
350+
{
351+
"retryName": "retryBackendA",
352+
"type": "ERROR",
353+
"creationTime": "2019-03-11T17:32:50.866+01:00[Europe/Brussels]",
354+
"errorMessage": "java.io.IOException: Test Message",
355+
"numberOfAttempts": 3
356+
}
357+
]
358+
}
359+
----
360+
200361
===== CircuitBreaker
201362

202363
The emitted CircuitBreaker events are stored in a separate circular event consumer buffers. The size of a event consumer buffer can be configured per CircuitBreaker in the application.yml file (eventConsumerBufferSize).

0 commit comments

Comments
 (0)