From f5142320bf4809d6dabe6c03fc3108286fd7228e Mon Sep 17 00:00:00 2001 From: priyank-sriv Date: Mon, 22 Jul 2019 00:54:49 +0530 Subject: [PATCH 1/4] first commit - basic test --- patterns/backoff-jitter/pom.xml | 61 ++++++++++++++ .../backoff/jitter/BackoffWithJitterTest.java | 81 +++++++++++++++++++ patterns/pom.xml | 3 +- 3 files changed, 144 insertions(+), 1 deletion(-) create mode 100644 patterns/backoff-jitter/pom.xml create mode 100644 patterns/backoff-jitter/src/test/java/com/baeldung/backoff/jitter/BackoffWithJitterTest.java diff --git a/patterns/backoff-jitter/pom.xml b/patterns/backoff-jitter/pom.xml new file mode 100644 index 0000000000..0a48a18abb --- /dev/null +++ b/patterns/backoff-jitter/pom.xml @@ -0,0 +1,61 @@ + + + 4.0.0 + com.baeldung + backoff-jitter + 1.0.0-SNAPSHOT + pom + + + + + + + + + + + junit + junit + ${junit.version} + test + + + org.mockito + mockito-core + ${mockito-core.version} + test + + + io.github.resilience4j + resilience4j-retry + ${resilience4j.version} + test + + + org.slf4j + slf4j-api + ${slf4j.version} + test + + + org.slf4j + slf4j-simple + ${slf4j.version} + test + + + + + UTF-8 + 1.8 + 1.8 + 4.12 + 2.27.0 + 1.7.26 + 0.16.0 + + + \ No newline at end of file diff --git a/patterns/backoff-jitter/src/test/java/com/baeldung/backoff/jitter/BackoffWithJitterTest.java b/patterns/backoff-jitter/src/test/java/com/baeldung/backoff/jitter/BackoffWithJitterTest.java new file mode 100644 index 0000000000..aef9f7fab7 --- /dev/null +++ b/patterns/backoff-jitter/src/test/java/com/baeldung/backoff/jitter/BackoffWithJitterTest.java @@ -0,0 +1,81 @@ +package com.baeldung.backoff.jitter; + +import io.github.resilience4j.retry.IntervalFunction; +import io.github.resilience4j.retry.Retry; +import io.github.resilience4j.retry.RetryConfig; +import org.junit.Before; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.function.Function; + +import static com.baeldung.backoff.jitter.BackoffWithJitterTest.RetryProperties.*; +import static io.github.resilience4j.retry.IntervalFunction.ofExponentialBackoff; +import static io.github.resilience4j.retry.IntervalFunction.ofExponentialRandomBackoff; +import static java.util.Collections.nCopies; +import static java.util.concurrent.Executors.newFixedThreadPool; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.*; + +public class BackoffWithJitterTest { + + static Logger log = LoggerFactory.getLogger(BackoffWithJitterTest.class); + + interface PingPongService { + + String call(String ping) throws PingPongServiceException; + } + + class PingPongServiceException extends RuntimeException { + + public PingPongServiceException(String reason) { + super(reason); + } + } + + private PingPongService service; + private static final int NUM_TASKS = 8; + + @Before + public void setUp() { + service = mock(PingPongService.class); + } + + @Test + public void whenRetryExponentialBackoff_thenRetriedConfiguredNoOfTimes() { + IntervalFunction intervalFn = ofExponentialBackoff(INITIAL_INTERVAL, MULTIPLIER); + Function pingPongFn = getRetryablePingPongFn(intervalFn); + + when(service.call(anyString())).thenThrow(PingPongServiceException.class); + try { + pingPongFn.apply("Hello"); + } catch (PingPongServiceException e) { + verify(service, times(MAX_TRIES)).call(anyString()); + } + } + + private Function getRetryablePingPongFn(IntervalFunction intervalFn) { + RetryConfig retryConfig = RetryConfig.custom() + .maxAttempts(MAX_TRIES) + .intervalFunction(intervalFn) + .retryExceptions(PingPongServiceException.class) + .build(); + Retry retry = Retry.of("pingpong", retryConfig); + return Retry.decorateFunction(retry, ping -> { + log.info("Invoked at {}", LocalDateTime.now()); + return service.call(ping); + }); + } + + static class RetryProperties { + static final Long INITIAL_INTERVAL = 1000L; + static final Double MULTIPLIER = 2.0D; + static final Double RANDOMIZATION_FACTOR = 0.6D; + static final Integer MAX_TRIES = 4; + } +} diff --git a/patterns/pom.xml b/patterns/pom.xml index 2be9d2519e..7f7368ca07 100644 --- a/patterns/pom.xml +++ b/patterns/pom.xml @@ -19,7 +19,8 @@ design-patterns design-patterns-2 solid - dip + dip + backoff-jitter From 412e8b7634df712ad966d4ddce22f3ecc76d488b Mon Sep 17 00:00:00 2001 From: priyank-sriv Date: Mon, 22 Jul 2019 00:55:25 +0530 Subject: [PATCH 2/4] simple exponential backoff --- .../backoff/jitter/BackoffWithJitterTest.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/patterns/backoff-jitter/src/test/java/com/baeldung/backoff/jitter/BackoffWithJitterTest.java b/patterns/backoff-jitter/src/test/java/com/baeldung/backoff/jitter/BackoffWithJitterTest.java index aef9f7fab7..966207f430 100644 --- a/patterns/backoff-jitter/src/test/java/com/baeldung/backoff/jitter/BackoffWithJitterTest.java +++ b/patterns/backoff-jitter/src/test/java/com/baeldung/backoff/jitter/BackoffWithJitterTest.java @@ -59,6 +59,22 @@ public class BackoffWithJitterTest { } } + @Test + public void whenRetryExponentialBackoffWithoutJitter_thenThunderingHerdProblemOccurs() throws InterruptedException { + IntervalFunction intervalFn = ofExponentialBackoff(INITIAL_INTERVAL, MULTIPLIER); + test(intervalFn); + } + + private void test(IntervalFunction intervalFn) throws InterruptedException { + Function pingPongFn = getRetryablePingPongFn(intervalFn); + ExecutorService executors = newFixedThreadPool(NUM_TASKS); + List> tasks = nCopies(NUM_TASKS, () -> pingPongFn.apply("Hello")); + + when(service.call(anyString())).thenThrow(PingPongServiceException.class); + + executors.invokeAll(tasks); + } + private Function getRetryablePingPongFn(IntervalFunction intervalFn) { RetryConfig retryConfig = RetryConfig.custom() .maxAttempts(MAX_TRIES) From ae1807fca2a9170abd547f6caf22017974aa3ac7 Mon Sep 17 00:00:00 2001 From: priyank-sriv Date: Mon, 22 Jul 2019 00:55:41 +0530 Subject: [PATCH 3/4] backoff with jitter --- .../com/baeldung/backoff/jitter/BackoffWithJitterTest.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/patterns/backoff-jitter/src/test/java/com/baeldung/backoff/jitter/BackoffWithJitterTest.java b/patterns/backoff-jitter/src/test/java/com/baeldung/backoff/jitter/BackoffWithJitterTest.java index 966207f430..0f0e652596 100644 --- a/patterns/backoff-jitter/src/test/java/com/baeldung/backoff/jitter/BackoffWithJitterTest.java +++ b/patterns/backoff-jitter/src/test/java/com/baeldung/backoff/jitter/BackoffWithJitterTest.java @@ -65,6 +65,12 @@ public class BackoffWithJitterTest { test(intervalFn); } + @Test + public void whenRetryExponentialBackoffWithJitter_thenRetriesAreSpread() throws InterruptedException { + IntervalFunction intervalFn = ofExponentialRandomBackoff(INITIAL_INTERVAL, MULTIPLIER, RANDOMIZATION_FACTOR); + test(intervalFn); + } + private void test(IntervalFunction intervalFn) throws InterruptedException { Function pingPongFn = getRetryablePingPongFn(intervalFn); ExecutorService executors = newFixedThreadPool(NUM_TASKS); From cb686cf9c4f1587d729e66668ec9387631b4a1f9 Mon Sep 17 00:00:00 2001 From: priyank-sriv Date: Tue, 23 Jul 2019 01:52:22 +0530 Subject: [PATCH 4/4] minor modifications --- patterns/backoff-jitter/README.md | 2 ++ patterns/backoff-jitter/pom.xml | 7 ------- .../backoff/jitter/BackoffWithJitterTest.java | 12 ++++++------ 3 files changed, 8 insertions(+), 13 deletions(-) create mode 100644 patterns/backoff-jitter/README.md diff --git a/patterns/backoff-jitter/README.md b/patterns/backoff-jitter/README.md new file mode 100644 index 0000000000..6459e4c8e0 --- /dev/null +++ b/patterns/backoff-jitter/README.md @@ -0,0 +1,2 @@ +### Relevant Articles: +- [Better Retries with Exponential Backoff and Jitter](https://baeldung.com/retries-with-exponential-backoff-and-jitter) diff --git a/patterns/backoff-jitter/pom.xml b/patterns/backoff-jitter/pom.xml index 0a48a18abb..6b0d016609 100644 --- a/patterns/backoff-jitter/pom.xml +++ b/patterns/backoff-jitter/pom.xml @@ -8,13 +8,6 @@ 1.0.0-SNAPSHOT pom - - - - - - - junit diff --git a/patterns/backoff-jitter/src/test/java/com/baeldung/backoff/jitter/BackoffWithJitterTest.java b/patterns/backoff-jitter/src/test/java/com/baeldung/backoff/jitter/BackoffWithJitterTest.java index 0f0e652596..f6b3ebbe45 100644 --- a/patterns/backoff-jitter/src/test/java/com/baeldung/backoff/jitter/BackoffWithJitterTest.java +++ b/patterns/backoff-jitter/src/test/java/com/baeldung/backoff/jitter/BackoffWithJitterTest.java @@ -39,7 +39,7 @@ public class BackoffWithJitterTest { } private PingPongService service; - private static final int NUM_TASKS = 8; + private static final int NUM_CONCURRENT_CLIENTS = 8; @Before public void setUp() { @@ -55,7 +55,7 @@ public class BackoffWithJitterTest { try { pingPongFn.apply("Hello"); } catch (PingPongServiceException e) { - verify(service, times(MAX_TRIES)).call(anyString()); + verify(service, times(MAX_RETRIES)).call(anyString()); } } @@ -73,8 +73,8 @@ public class BackoffWithJitterTest { private void test(IntervalFunction intervalFn) throws InterruptedException { Function pingPongFn = getRetryablePingPongFn(intervalFn); - ExecutorService executors = newFixedThreadPool(NUM_TASKS); - List> tasks = nCopies(NUM_TASKS, () -> pingPongFn.apply("Hello")); + ExecutorService executors = newFixedThreadPool(NUM_CONCURRENT_CLIENTS); + List> tasks = nCopies(NUM_CONCURRENT_CLIENTS, () -> pingPongFn.apply("Hello")); when(service.call(anyString())).thenThrow(PingPongServiceException.class); @@ -83,7 +83,7 @@ public class BackoffWithJitterTest { private Function getRetryablePingPongFn(IntervalFunction intervalFn) { RetryConfig retryConfig = RetryConfig.custom() - .maxAttempts(MAX_TRIES) + .maxAttempts(MAX_RETRIES) .intervalFunction(intervalFn) .retryExceptions(PingPongServiceException.class) .build(); @@ -98,6 +98,6 @@ public class BackoffWithJitterTest { static final Long INITIAL_INTERVAL = 1000L; static final Double MULTIPLIER = 2.0D; static final Double RANDOMIZATION_FACTOR = 0.6D; - static final Integer MAX_TRIES = 4; + static final Integer MAX_RETRIES = 4; } }