Everything fails all the time.
Add spring-retry
dependency together with spring-boot-starter-aop
:
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
You can either specify retry configuration directly via @Retryable
properties or create RetryInterceptor
bean
and set its name in @Retryable
interceptor
property.
For this demo we’ll use simple configuration via @Retryable
.
@Retryable( // (1)
retryFor = { // (2)
QueryTimeoutException.class,
TransientDataAccessResourceException.class,
OptimisticLockingFailureException.class
},
maxAttempts = 5, // (3)
backoff = @Backoff( // (4)
delay = 3,
multiplier = 2,
random = true // (5)
)
)
@RequiredArgsConstructor
@Service
public class MovieService {
private final MovieRepository repository;
private final AerospikeTemplate template;
public void createMovie(Movie movie) {
try {
template.insert(MovieDocument.builder()
.id(movie.getName())
.name(movie.getName())
.description(movie.getDescription())
.rating(movie.getRating())
.version(0L)
.build());
} catch (DuplicateKeyException e) {
throw new IllegalArgumentException("Movie with name: " + movie.getName() + " already exists!");
}
}
public void deleteMovie(String name) {
repository.deleteById(name);
}
public Optional<Movie> findMovie(String name) {
return repository.findById(name)
.map(this::toMovie);
}
public Movie updateMovieRating(String name, double newRating) {
return update(name, existingMovie ->
repository.save(existingMovie.toBuilder().rating(newRating).build()));
}
public Movie updateMovieDescription(String name, String newDescription) {
return update(name, existingMovie ->
repository.save(existingMovie.toBuilder().description(newDescription).build()));
}
private Movie update(String name, Function<MovieDocument, MovieDocument> updateFunction) {
return repository.findById(name)
.map(updateFunction)
.map(this::toMovie)
.orElseThrow(() -> new IllegalArgumentException("Movie with name: " + name + " not found"));
}
private Movie toMovie(MovieDocument doc) {
return new Movie(doc.getName(), doc.getDescription(), doc.getRating());
}
}
-
@Retryable
specifies that the method(s) will be retied. -
retryFor
specifies exception types that should be retried. -
maxAttempts
specifies number of max attempts of retries. -
backoff
specifies backoff configuration for the retries. -
random = true
enables jitter in backoff timeouts.
Add @EnableRetry
either into existing AerospikeConfiguration
or create separate class AerospikeRetryConfiguration
:
@EnableRetry
@Configuration
public class AerospikeRetryConfiguration {
}
@Configuration
@EnableAerospikeRepositories(basePackageClasses = MovieRepository.class)
public class AerospikeConfiguration extends AbstractAerospikeDataConfiguration {
}
In the context of retries there are two types of errors: retryable and non-retryable.
For example, retrieving value by key may result in DataRetrievalFailureException
(key not found),
which in most cases should not be retried; whereas in case there are connectivity issues to Aerospike
or any network congestion issues you would retry. Consider this when configuring your retry policy.
When retrying errors there are two basic backoff policies: fixed and exponential.
Fixed backoff policy does exactly how it’s named, each retry occurs within fixed time interval. This backoff policy is simple and easy for understanding, but is not recommended for production, because in case external resource is overloaded and all clients experience issues, constantly increasing number of requests at the same time may lead to the total resource outage.
To overcome the issues resource should be given some time to heal, and this can be achieved by exponential
backoff policy which increases each retry time interval by a specific multiplier.
This backoff policy is usually used together with jitter — added randomized time interval into the backoff,
which removes retry waves at specific time from multiple clients.
With spring-retry
you can use org.springframework.retry.backoff.ExponentialRandomBackOffPolicy
.
To see demo application go to Basic Error Handling Demo.