Merge pull request #7382 from priyank-sriv/BAEL-3087

BAEL 3087: Exponential Backoff with Jitter
This commit is contained in:
rpvilao 2019-07-30 13:10:33 +02:00 committed by GitHub
commit 5a13c67fda
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 161 additions and 1 deletions

View File

@ -0,0 +1,2 @@
### Relevant Articles:
- [Better Retries with Exponential Backoff and Jitter](https://baeldung.com/retries-with-exponential-backoff-and-jitter)

View File

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.baeldung</groupId>
<artifactId>backoff-jitter</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>pom</packaging>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>${mockito-core.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-retry</artifactId>
<version>${resilience4j.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>${slf4j.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<junit.version>4.12</junit.version>
<mockito-core.version>2.27.0</mockito-core.version>
<slf4j.version>1.7.26</slf4j.version>
<resilience4j.version>0.16.0</resilience4j.version>
</properties>
</project>

View File

@ -0,0 +1,103 @@
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_CONCURRENT_CLIENTS = 8;
@Before
public void setUp() {
service = mock(PingPongService.class);
}
@Test
public void whenRetryExponentialBackoff_thenRetriedConfiguredNoOfTimes() {
IntervalFunction intervalFn = ofExponentialBackoff(INITIAL_INTERVAL, MULTIPLIER);
Function<String, String> pingPongFn = getRetryablePingPongFn(intervalFn);
when(service.call(anyString())).thenThrow(PingPongServiceException.class);
try {
pingPongFn.apply("Hello");
} catch (PingPongServiceException e) {
verify(service, times(MAX_RETRIES)).call(anyString());
}
}
@Test
public void whenRetryExponentialBackoffWithoutJitter_thenThunderingHerdProblemOccurs() throws InterruptedException {
IntervalFunction intervalFn = ofExponentialBackoff(INITIAL_INTERVAL, MULTIPLIER);
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<String, String> pingPongFn = getRetryablePingPongFn(intervalFn);
ExecutorService executors = newFixedThreadPool(NUM_CONCURRENT_CLIENTS);
List<Callable<String>> tasks = nCopies(NUM_CONCURRENT_CLIENTS, () -> pingPongFn.apply("Hello"));
when(service.call(anyString())).thenThrow(PingPongServiceException.class);
executors.invokeAll(tasks);
}
private Function<String, String> getRetryablePingPongFn(IntervalFunction intervalFn) {
RetryConfig retryConfig = RetryConfig.custom()
.maxAttempts(MAX_RETRIES)
.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_RETRIES = 4;
}
}

View File

@ -19,7 +19,8 @@
<module>design-patterns</module> <module>design-patterns</module>
<module>design-patterns-2</module> <module>design-patterns-2</module>
<module>solid</module> <module>solid</module>
<module>dip</module> <module>dip</module>
<module>backoff-jitter</module>
</modules> </modules>
<dependencyManagement> <dependencyManagement>