项目作者: sorokinigor

项目描述 :
具有可配置策略的异步重试执行程序。
高级语言: Java
项目地址: git://github.com/sorokinigor/yet-another-try.git
创建时间: 2017-01-31T20:52:36Z
项目社区:https://github.com/sorokinigor/yet-another-try

开源协议:Apache License 2.0

下载


Yet Another Try

Build Status
codecov
Maven Central

Features

  • Configure:
    • max number of attempts
    • timeout
    • delay between attempts
    • which exceptions should be retried and which should not.
    • whenever or not use the invocation thread for the first attempt
  • Implements plain java
    ExecutorService,
    therefore it is fully compatible with the code, which uses
    ExecutorService directly.
  • Uses CompletableFuture
    as a return type.
  • Has both asynchronous and synchronous versions.
  • Collects statistics about successful and failed attempts if
    requested.

Dependencies and prerequisites

The library requires Java 8+. Use the following code snippets to add the library to your project:

  • Gradle
    ```Groovy
    dependencies {
    compile “com.github.sorokinigor:yet-another-try:1.1.0”
    }

repositories {
mavenCentral()
}

  1. * Maven
  2. ```xml
  3. <dependency>
  4. <groupId>com.github.sorokinigor</groupId>
  5. <artifactId>yet-another-try</artifactId>
  6. <version>1.1.0</version>
  7. </dependency>

Usage

The main entry point is
Retry utility class.

  1. /*
  2. Uses the current thread for the first attempt
  3. and passed ScheduledExecutorService for the subsequent attempts,
  4. does not retry on malformed request.
  5. */
  6. AsyncRetryExecutor executor = Retry.async(Executors.newSingleThreadScheduledExecutor())
  7. .retryOnce()
  8. .runFirstAttemptInInvocationThread()
  9. .terminateOn(IllegalArgumentException.class)
  10. .terminateOn(HttpGenericException.class, e -> e.statusCode() == 400)
  11. .build();
  12. CompletableFuture<String> future = executor.submit(() -> faultyResource("malformedRequest"));
  13. future.whenComplete((response, exception) -> System.out.println(
  14. "Response '" + response + "', exception '" + exception + "'."
  15. ));
  16. //Uses default lazy singleton instance of AsyncRetryExecutor
  17. Retry.async()
  18. .submit(() -> faultyResource("request"))
  19. .thenAccept(response -> System.out.println("Response is '" + response + "'."));
  20. /*
  21. Uses the current thread for task invocation.
  22. Tries 2 times with fixed rate between attempts.
  23. */
  24. SyncRetryExecutor syncExecutor = Retry.sync()
  25. .maxAttempts(2)
  26. .backOff(Backoffs.fixedRate(1L, TimeUnit.SECONDS))
  27. .build();
  28. String response = syncExecutor.execute(() -> faultyResource("syncRequest"));
  29. /*
  30. Shortcut for ad hoc synchronous execution.
  31. Completes with exception on timeout.
  32. */
  33. String result = Retry.sync()
  34. .timeout(5L, TimeUnit.SECONDS)
  35. .execute(() -> faultyResource("adhoc request"));

Asynchronous

Any arbitrary ScheduledExecutorService
should be passed in order to use asynchronous executor. Example:

  1. AsyncRetryExecutor executor = Retry.async(Executors.newSingleThreadScheduledExecutor())
  2. .maxAttempts(3)
  3. .timeout(10, TimeUnit.SECONDS)
  4. .backOff(Backoffs.fixedDelay(1L, TimeUnit.SECONDS))
  5. .retryOn(NotYetConnectedException.class)
  6. .terminateOn(NullPointerException.class)
  7. .build();
  8. CompletableFuture<Integer> result = executor.submit(() -> {
  9. try (SocketChannel socket = SocketChannel.open(new InetSocketAddress("music.yandex.ru", 80))) {
  10. socket.configureBlocking(false);
  11. ByteBuffer buffer = ByteBuffer.allocate(10);
  12. return socket.read(buffer);
  13. }
  14. })
  15. .thenApply(numberOfBytesRead -> numberOfBytesRead / 2);

Please note that by default
AsyncRetryExecutor
manages the lifecycle of the passed
ScheduledExecutorService.
Consequently, the
AsyncRetryExecutor will shutdown underlying
ScheduledExecutorService.
If you want to prevent it, use:

  1. ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
  2. AsyncRetryExecutor executor = Retry.async(executorService)
  3. .doNotShutdownExecutors()
  4. .build();

Timeout

This code snippet shows how you can specify a timeout for a task:

  1. AsyncRetryExecutor executor = Retry.async(Executors.newSingleThreadScheduledExecutor())
  2. .timeout(10L, TimeUnit.SECONDS)
  3. .build();

After the timeout is expired, the result
CompletableFuture is completed
with
TimeoutException.
Since then, there would be no retries of the task.

By default, the same executor is used for both task execution and timeout handling, but it is configurable:

  1. ScheduledExecutorService taskExecutor = Executors.newSingleThreadScheduledExecutor();
  2. ScheduledExecutorService timeoutExecutor = Executors.newSingleThreadScheduledExecutor();
  3. AsyncRetryExecutor executor = Retry.async(taskExecutor)
  4. .timeout(5L, TimeUnit.SECONDS)
  5. .timeoutExecutorService(timeoutExecutor)
  6. .build();

Delay

The library itself contains exponential (default and preferable), fixed delay and fixed rate backoffs
for delay calculation. But, feel free to implement your own backoff strategy, as the
Backoff
interface is a part of the public API.

In order to instantiate the built-in backoff strategies use
Backoffs
utility class.

Exponential

The delay
is exponentially increases until it reaches the upper bound for the delay or the number of attempts.
After the calculation of the exponential backoff, it also adds an additional random delay based on the passed random
factor. For instance, 0.2 adds up to 20% delay. Example:

  1. AsyncRetryExecutor executor = Retry.async(Executors.newSingleThreadScheduledExecutor())
  2. .backOff(Backoffs.exponential(3L, 30L, TimeUnit.SECONDS, 0.2D))
  3. .build();

Fixed delay

It
always uses the same delay for each attempt. Example:

  1. AsyncRetryExecutor executor = Retry.async(Executors.newSingleThreadScheduledExecutor())
  2. .backOff(Backoffs.fixedDelay(1L, TimeUnit.SECONDS))
  3. .build();

Fixed rate

It
subtracts the task execution time from the delay. If the execution time is greater than or equal the delay, the delay is 0. Example:

  1. AsyncRetryExecutor executor = Retry.async(Executors.newSingleThreadScheduledExecutor())
  2. .backOff(Backoffs.fixedRate(1L, TimeUnit.SECONDS))
  3. .build();

Exceptions

The library provides the ability to retry only specific type exception and the exception matching the predicate.
Also, it is possible to configure to stop retrying after a specific exception (by type or predicate too).
Example:

  1. AsyncRetryExecutor executor = Retry.async(Executors.newSingleThreadScheduledExecutor())
  2. .retryOn(SocketException.class)
  3. .retryOn(HttpGenericException.class, e -> e.statusCode() == 500)
  4. .terminateOn(IllegalStateException.class)
  5. .terminateOn(HttpGenericException.class, e -> e.statusCode() == 400)
  6. .terminatePredicate(e -> e instanceof BindException && e.getMessage().contains("in use"))
  7. .build();

Notice that the task is retried only if:

  • Any of the retry predicates returns true or you didn’t specify any (in that case there is a default retry predicate,
    which always returns true).
  • None of the terminate predicates returns true or you didn’t specify any (in that case there is a
    default terminate predicate, which always returns false).

Default executor

A default lazy singleton instance of asynchronous executor is available via
Retry.async()
method. Example:

  1. CompletableFuture<String> future = Retry.async()
  2. .submit(() -> faultyResource("request"));

It is lazily instantiated on first usage and creates a shutdown hook for the internal
ScheduledExecutorService
shutting down.

Statistics

There is a simple wrapper
for the asynchronous executor, which collects the number of
failed attempts and the number of successful and failed tasks. Example:

  1. AsyncRetryExecutor executor = Retry.async(Executors.newSingleThreadScheduledExecutor())
  2. .maxAttempts(2)
  3. .build();
  4. StatisticsExecutorService statsExecutor = Retry.gatherStatisticFor(executor);
  5. CompletableFuture<String> successful = statsExecutor.submit(() -> "successful");
  6. CompletableFuture<String> failed = statsExecutor.submit(() -> { throw new Exception(); });
  7. successful.thenAcceptBoth(failed, (ignored1, ignored2) -> {})
  8. .whenComplete((ignored, ignoredException) -> {
  9. System.out.println(statsExecutor.stats());
  10. //Stats{successful=1, failed=1, failedAttempts=2}
  11. });

Synchronous

The synchronous executor
does not use any thread pool, instead, it uses the current thread for task execution.
It has approximately the same configuration as asynchronous one, except
the settings related to
ScheduledExecutorService.
Example:

  1. SyncRetryExecutor executor = Retry.sync()
  2. .maxAttempts(3)
  3. .timeout(10, TimeUnit.SECONDS)
  4. .backOff(Backoffs.fixedDelay(1L, TimeUnit.SECONDS))
  5. .retryOn(NotYetConnectedException.class)
  6. .terminateOn(NullPointerException.class)
  7. .build();
  8. int numberOfBytesRead = executor.execute(() -> {
  9. try (SocketChannel socket = SocketChannel.open(new InetSocketAddress("music.yandex.ru", 80))) {
  10. socket.configureBlocking(false);
  11. ByteBuffer buffer = ByteBuffer.allocate(10);
  12. return socket.read(buffer);
  13. }
  14. });

If you do not want to store a reference to the executor, you can use a shortcut:

  1. String response = Retry.sync()
  2. .withoutDelay()
  3. .terminateOn(IllegalArgumentException.class)
  4. .terminateOn(UnsupportedOperationException.class)
  5. .execute(() -> faultyResource("request"));