Перейти к основному содержимому

Повторные попытки ложных вызовов

· 5 мин. чтения

1. Введение

Вызов внешних служб через конечную точку REST — обычное действие, которое стало очень простым благодаря таким библиотекам, как Feign . Однако во время таких звонков многое может пойти не так. Многие из этих проблем случайны или временны.

В этом руководстве мы узнаем, как повторять неудачные вызовы и создавать более отказоустойчивые клиенты REST.

2. Имитация установки клиента

Во-первых, давайте создадим простой конструктор клиента Feign, который позже мы добавим в повторные попытки. Мы будем использовать OkHttpClient в качестве HTTP-клиента. Кроме того, мы будем использовать GsonEncoder и GsonDecoder для кодирования и декодирования запросов и ответов. Наконец, нам нужно указать URI цели и тип ответа:

public class ResilientFeignClientBuilder {
public static <T> T createClient(Class<T> type, String uri) {
return Feign.builder()
.client(new OkHttpClient())
.encoder(new GsonEncoder())
.decoder(new GsonDecoder())
.target(type, uri);
}
}

В качестве альтернативы, если мы используем Spring, мы можем позволить ему автоматически подключать клиента Feign к доступным bean-компонентам.

3. Притвориться Ретриером

К счастью, в Feign заложены способности повторных попыток, и их просто нужно настроить. Мы можем сделать это, предоставив реализацию интерфейса Retryer для построителя клиента.

Его самый важный метод, continueOrPropagate, принимает RetryableException в качестве аргумента и ничего не возвращает. При выполнении он либо выдает исключение, либо успешно завершается (обычно после сна). Если исключение не возникнет, Feign продолжит повторять вызов. Если возникнет исключение, оно будет распространено и фактически завершит вызов с ошибкой.

3.1. Наивная реализация

Давайте напишем очень простую реализацию Retryer, которая всегда будет повторять вызовы после ожидания в одну секунду:

public class NaiveRetryer implements feign.Retryer {
@Override
public void continueOrPropagate(RetryableException e) {
try {
Thread.sleep(1000L);
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
throw e;
}
}
}

Поскольку Retryer реализует интерфейс Cloneable , нам также нужно было переопределить метод клонирования .

@Override
public Retryer clone() {
return new NaiveRetryer();
}

Наконец, нам нужно добавить нашу реализацию в построитель клиента:

public static <T> T createClient(Class<T> type, String uri) {
return Feign.builder()
// ...
.retryer(new NaiveRetryer())
// ...
}

В качестве альтернативы, если мы используем Spring, мы могли бы аннотировать NaiveRetryer аннотацией @Component или определить bean -компонент в классе конфигурации и позволить Spring сделать остальную работу:

@Bean
public Retryer retryer() {
return new NaiveRetryer();
}

3.2. Реализация по умолчанию

Feign предоставляет разумную реализацию интерфейса Retryer по умолчанию . Он будет повторять только заданное количество раз, будет начинаться с некоторого интервала времени, а затем увеличивать его с каждой повторной попыткой до заданного максимума. Давайте определим его с начальным интервалом 100 миллисекунд, максимальным интервалом 3 секунды и максимальным количеством попыток 5:

public static <T> T createClient(Class<T> type, String uri) {
return Feign.builder()
// ...
.retryer(new Retryer.Default(100L, TimeUnit.SECONDS.toMillis(3L), 5))
// ...
}

3.3. Нет повторной попытки

Если мы не хотим, чтобы Feign когда-либо повторял какие-либо вызовы, мы можем предоставить реализацию Retryer.NEVER_RETRY для построителя клиента. Он просто будет распространять исключение каждый раз.

4. Создание повторяющихся исключений

В предыдущем разделе мы научились управлять частотой повторных вызовов. Теперь давайте посмотрим, как контролировать, когда мы хотим повторить вызов, а когда мы хотим просто сгенерировать исключение.

4.1. ErrorDecoder и RetryableException

Когда мы получаем ошибочный ответ, Feign передает его экземпляру интерфейса ErrorDecoder , который решает, что с ним делать. Что наиболее важно, декодер может сопоставить исключение с экземпляром RetryableException, позволяя Retryer повторить вызов. Реализация ErrorDecoder по умолчанию создает экземпляр RetryableExeception только тогда , когда ответ содержит заголовок «Retry-After». Чаще всего мы можем найти его в ответах 503 Service Unreachable.

Это хорошее поведение по умолчанию, но иногда нам нужно быть более гибкими. Например, мы можем общаться с внешней службой, которая время от времени случайным образом отвечает 500 Internal Server Error, и у нас нет возможности это исправить. Что мы можем сделать, так это повторить вызов, потому что мы знаем, что он, вероятно, сработает в следующий раз. Для этого нам нужно написать собственную реализацию ErrorDecoder .

4.2. Создание собственного декодера ошибок

Есть только один метод, который нам нужно реализовать в нашем пользовательском декодере: decode . Он принимает два аргумента, ключ метода String и объект Response . Он возвращает исключение, если это экземпляр RetryableException или какое-либо другое исключение, зависящее от реализации.

Наш метод декодирования просто проверит, больше ли код состояния ответа или равен 500. Если это так, он создаст RetryableException . Если нет, он вернет базовое исключение FeignException, созданное с помощью фабричной функции errorStatus из класса FeignException :

public class Custom5xxErrorDecoder implements ErrorDecoder {
@Override
public Exception decode(String methodKey, Response response) {
FeignException exception = feign.FeignException.errorStatus(methodKey, response);
int status = response.status();
if (status >= 500) {
return new RetryableException(
response.status(),
exception.getMessage(),
response.request().httpMethod(),
exception,
null,
response.request());
}
return exception;
}
}

Имейте в виду, что в этом случае мы создаем и возвращаем исключение, а не выбрасываем его.

Наконец, нам нужно подключить наш декодер в сборщике клиентов:

public static <T> T createClient(Class<T> type, String uri) {
return Feign.builder()
// ...
.errorDecoder(new Custom5xxErrorDecoder())
// ...
}

5. Резюме

В этой статье мы узнали, как управлять логикой повторных попыток библиотеки Feign. Мы изучили интерфейс Retryer и то, как его можно использовать для управления временем и количеством повторных попыток. Затем мы создали наш ErrorDecoder , чтобы контролировать, какие ответы требуют повторной попытки.

Как всегда, все примеры кода можно найти на GitHub .