Riptide: Failsafe adds Failsafe support to Riptide. It offers retries and a circuit breaker to every remote call.
Http.builder().requestFactory(new HttpComponentsClientHttpRequestFactory())
.plugin(new FailsafePlugin()
.withPolicy(circuitBreaker)
.withPolicy(new RetryRequestPolicy(retryPolicy)))
.build();
- seamlessly integrates Riptide with Failsafe
- Riptide Core
- Failsafe
Add the following dependency to your project:
<dependency>
<groupId>org.zalando</groupId>
<artifactId>riptide-failsafe</artifactId>
<version>${riptide.version}</version>
</dependency>
The failsafe plugin will not perform retries nor apply circuit breakers unless they were explicitly configured:
Http.builder().requestFactory(new HttpComponentsClientHttpRequestFactory())
.plugin(new FailsafePlugin()
.withPolicy(
new RetryRequestPolicy(
RetryPolicy.<ClientHttpResponse>builder()
.withDelay(Duration.ofMillis(25))
.withDelayFn(new RetryAfterDelayFunction(clock))
.withMaxRetries(4)
.build())
.withListener(myRetryListener))
.withPolicy(
CircuitBreaker.<ClientHttpResponse>builder()
.withFailureThreshold(3, 10)
.withSuccessThreshold(5)
.withDelay(Duration.ofMinutes(1))
.build()))
.build();
Please visit the Failsafe readme in order to see possible configurations.
Beware when using retryOn
to retry conditionally on certain exception types.
You'll need to register RetryException
in order for the retry()
route to work:
RetryPolicy.<ClientHttpResponse>builder()
.handle(SocketTimeoutException.class)
.handle(RetryException.class)
.build();
Failsafe supports dynamically computed delays using a custom function.
Riptide: Failsafe offers implementations that understand:
Http.builder().requestFactory(new HttpComponentsClientHttpRequestFactory())
.plugin(new FailsafePlugin()
.withPolicy(RetryPolicy.<ClientHttpResponse>builder()
.withDelayFn(new CompositeDelayFunction<>(Arrays.asList(
new RetryAfterDelayFunction(clock),
new RateLimitResetDelayFunction(clock)
)))
.withMaxDuration(Duration.ofSeconds(5))
.build()))
.build();
Make sure you check out zalando/failsafe-actuator for a seamless integration of Failsafe and Spring Boot.
The BackupRequest
policy implements the backup request pattern, also known as hedged requests:
Http.builder().requestFactory(new HttpComponentsClientHttpRequestFactory())
.plugin(new FailsafePlugin()
.withPolicy(new BackupRequest(1, SECONDS)))
.build();
The withExecutor
method allows to specify a custom ExecutorService
being used to perform asynchronous executions and listen for callbacks:
Http.builder().requestFactory(new HttpComponentsClientHttpRequestFactory())
.plugin(new FailsafePlugin()
.withPolicy(
CircuitBreaker.<ClientHttpResponse>builder()
.withFailureThreshold(3, 10)
.withSuccessThreshold(5)
.withDelay(Duration.ofMinutes(1))
.build())
.withExecutor(Executors.newFixedThreadPool(2)))
.build();
If no executor is specified, the default executor configured by Failsafe
is used. See Failsafe DelegatingScheduler class,
and also Failsafe documentation for more information.
Beware when specifying a custom ExecutorService
:
- The
ExecutorService
should have a core pool size or parallelism of at least 2 in order for timeouts to work - In general, it is not recommended to specify the same
ExecutorService
for multipleHttp
clients
Given the failsafe plugin was configured as shown in the last section: A regular call like the following will now be retried up to 4 times if the server did not respond within the socket timeout.
http.get("/users/me")
.dispatch(series(),
on(SUCCESSFUL).call(User.class, this::greet),
anySeries().call(problemHandling()))
Handling certain technical issues automatically, like socket timeouts, is quite useful.
But there might be cases where the server did respond, but the response indicates something that is worth
retrying, e.g. a 409 Conflict
or a 503 Service Unavailable
. Use the predefined retry
route that comes with the
failsafe plugin:
http.get("/users/me")
.dispatch(series(),
on(SUCCESSFUL).call(User.class, this::greet),
on(CLIENT_ERROR).dispatch(status(),
on(CONFLICT).call(retry())),
on(SERVER_ERROR).dispatch(status(),
on(SERVICE_UNAVAILABLE).call(retry())),
anySeries().call(problemHandling()))
Only safe and idempontent methods are retried by default. The following request methods can be detected:
- Standard HTTP method
- HTTP method override
- Conditional Requests
Idempotency-Key
header
You also have the option to declare any request to be idempotent
by setting the respective request attribute. This is
useful in situation where none of the options are above would detect it but based on the contract of the API you may know
that a certain operation is in fact idempotent.
http.post("/subscriptions/{id}/cursors", subscriptionId)
.attribute(MethodDetector.IDEMPOTENT, true)
.header("X-Nakadi-StreamId", streamId)
.body(cursors)
.dispatch(series(),
on(SUCCESSFUL).call(pass()),
anySeries().call(problemHandling()))
In case those options are insufficient you may specify your own method detector:
Http.builder().requestFactory(new HttpComponentsClientHttpRequestFactory())
.plugin(new FailsafePlugin()
.withPolicy(retryPolicy)
.withDecorator(new CustomIdempotentMethodDetector()))
.build();
If you have questions, concerns, bug reports, etc., please file an issue in this repository's Issue Tracker.
To contribute, simply open a pull request and add a brief description (1-2 sentences) of your addition or change. For more details, check the contribution guidelines.