Skip to content

Commit

Permalink
Adjust servicetalk-examples-http-retry (#2746)
Browse files Browse the repository at this point in the history
Motivation:

Currently, this example shows that any 5xx response can be retryable
and applies immediate retries. We should teach users in a conservative
way to retry only specific status codes with exponential backoff and
jitter.

Modifications:

- Retry only 429, 502, 503, 504 status codes;
- Apply exponential backoff with jitter;

Result:

Example teaches users better practices.
  • Loading branch information
idelpivnitskiy committed Nov 11, 2023
1 parent b42e9c4 commit 25b09db
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,28 +16,56 @@
package io.servicetalk.examples.http.retry;

import io.servicetalk.http.api.HttpClient;
import io.servicetalk.http.api.HttpResponseStatus;
import io.servicetalk.http.netty.HttpClients;
import io.servicetalk.http.netty.RetryingHttpRequesterFilter;
import io.servicetalk.http.netty.RetryingHttpRequesterFilter.BackOffPolicy;

import static io.servicetalk.http.api.HttpResponseStatus.StatusClass.SERVER_ERROR_5XX;
import java.time.Duration;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

import static io.servicetalk.http.api.HttpResponseStatus.BAD_GATEWAY;
import static io.servicetalk.http.api.HttpResponseStatus.GATEWAY_TIMEOUT;
import static io.servicetalk.http.api.HttpResponseStatus.SERVICE_UNAVAILABLE;
import static io.servicetalk.http.api.HttpResponseStatus.TOO_MANY_REQUESTS;
import static io.servicetalk.http.api.HttpSerializers.textSerializerUtf8;

/**
* Extends the Async "Hello World" client to immediately retry requests that get a 5XX response. Up to three attempts
* total, one initial attempt and up to two retries, will be made before failure.
* Extends the Async "Hello World" client to retry requests that receive a retryable status code in response.
* Up to three attempts total (one initial attempt and up to two retries) with exponential backoff and jitter will be
* made before failure.
*/
public final class RetryClient {

private static final Set<HttpResponseStatus> RETRYABLE_STATUS_CODES;

static {
Set<HttpResponseStatus> set = new HashSet<>();
// Modify the set of status codes as needed
set.add(TOO_MANY_REQUESTS); // 429
set.add(BAD_GATEWAY); // 502
set.add(SERVICE_UNAVAILABLE); // 503
set.add(GATEWAY_TIMEOUT); // 504
RETRYABLE_STATUS_CODES = Collections.unmodifiableSet(set);
}

public static void main(String[] args) throws Exception {
try (HttpClient client = HttpClients.forSingleAddress("localhost", 8080)
.appendClientFilter(new RetryingHttpRequesterFilter.Builder()
.responseMapper(httpResponseMetaData ->
SERVER_ERROR_5XX.contains(httpResponseMetaData.status()) ?
// Response status is 500-599, we request a retry
RETRYABLE_STATUS_CODES.contains(httpResponseMetaData.status()) ?
// Response status is retryable
new RetryingHttpRequesterFilter.HttpResponseException(
"Retry 5XX", httpResponseMetaData)
// Not a 5XX response, we do not know whether retry is required
// Not a retryable status
: null)
.retryResponses((meta, error) -> RetryingHttpRequesterFilter.BackOffPolicy.ofImmediate(2))
.retryResponses((meta, error) -> BackOffPolicy.ofExponentialBackoffDeltaJitter(
Duration.ofMillis(10), // initialDelay
Duration.ofMillis(10), // jitter
Duration.ofMillis(100), // maxDelay
2)) // maxRetries
.build())
.build()) {
client.request(client.get("/sayHello"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,29 +16,56 @@
package io.servicetalk.examples.http.retry;

import io.servicetalk.http.api.HttpClient;
import io.servicetalk.http.api.HttpResponseStatus;
import io.servicetalk.http.netty.HttpClients;
import io.servicetalk.http.netty.RetryingHttpRequesterFilter;

import static io.servicetalk.http.api.HttpResponseStatus.StatusClass.SERVER_ERROR_5XX;
import java.time.Duration;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

import static io.servicetalk.http.api.HttpResponseStatus.BAD_GATEWAY;
import static io.servicetalk.http.api.HttpResponseStatus.GATEWAY_TIMEOUT;
import static io.servicetalk.http.api.HttpResponseStatus.SERVICE_UNAVAILABLE;
import static io.servicetalk.http.api.HttpResponseStatus.TOO_MANY_REQUESTS;
import static io.servicetalk.http.api.HttpSerializers.textSerializerUtf8;

/**
* Extends the Async "Hello World" multi-address client to immediately retry requests that get a 5XX response. Up to
* three attempts total, one initial attempt and up to two retries, will be made before failure.
* Extends the Async "Hello World" multi-address client to retry requests that receive a retryable status code in
* response. Up to three attempts total (one initial attempt and up to two retries) with exponential backoff and jitter
* will be made before failure.
*/
public class RetryUrlClient {

private static final Set<HttpResponseStatus> RETRYABLE_STATUS_CODES;

static {
Set<HttpResponseStatus> set = new HashSet<>();
// Modify the set of status codes as needed
set.add(TOO_MANY_REQUESTS); // 429
set.add(BAD_GATEWAY); // 502
set.add(SERVICE_UNAVAILABLE); // 503
set.add(GATEWAY_TIMEOUT); // 504
RETRYABLE_STATUS_CODES = Collections.unmodifiableSet(set);
}

public static void main(String[] args) throws Exception {
try (HttpClient client = HttpClients.forMultiAddressUrl().initializer((scheme, address, builder) -> {
// If necessary, users can set different retry strategies based on `scheme` and/or `address`.
builder.appendClientFilter(new RetryingHttpRequesterFilter.Builder()
.responseMapper(httpResponseMetaData ->
SERVER_ERROR_5XX.contains(httpResponseMetaData.status()) ?
// Response status is 500-599, we request a retry
RETRYABLE_STATUS_CODES.contains(httpResponseMetaData.status()) ?
// Response status is retryable
new RetryingHttpRequesterFilter.HttpResponseException(
"Retry 5XX", httpResponseMetaData)
// Not a 5XX response, we do not know whether retry is required
// Not a retryable status
: null)
.retryResponses((meta, error) -> RetryingHttpRequesterFilter.BackOffPolicy.ofImmediate(2))
.retryResponses((meta, error) -> RetryingHttpRequesterFilter.BackOffPolicy.ofExponentialBackoffDeltaJitter(
Duration.ofMillis(10), // initialDelay
Duration.ofMillis(10), // jitter
Duration.ofMillis(100), // maxDelay
2)) // maxRetries
.build());
}).build()) {
client.request(client.get("http://localhost:8080/sayHello"))
Expand Down

0 comments on commit 25b09db

Please sign in to comment.