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 .