Независимо от того, внедряете ли вы микросервисы или нет, есть вероятность, что вы вызываете конечные точки HTTP. С HTTP-вызовами многое может пойти не так. Опытные разработчики планируют это и проектируют не только успешные пути. В общем отказоустойчивость (Fault Tolerance) включает в себя следующие функции:
Microprofile определяет аннотации для каждой функции: @Timeout, @Retry Policy, @Fallback, @Circuit Breakerи @Bulkhead. Он также определяет аннотацию @Asynchronous.
Поскольку среда выполнения считывает аннотации, следует внимательно прочитать документацию, чтобы понять, как они взаимодействуют, если установлено более одной аннотации.
Resilience4J основан на нескольких основных концепциях:
Каждая функция отказоустойчивости построена на основе одного и того же шаблона, показанного выше. Можно создать конвейер из нескольких функций, используя композицию функций, каждая из которых вызывает другую.
Проанализируем пример:
Может быть трудно разобраться с порядком, в котором расположены эти функции. Поэтому проект предлагает класс Decorators, объединяющий их с помощью гибкого API. Вы можете найти его в модуле resilience4j-all. Можно переписать приведенный выше код как:
var pipeline = Decorators.ofSupplier(() -> server.call())
.withRetry(Retry.ofDefaults("retry"))
.withCircuitBreaker(CircuitBreaker.of("circuit-breaker", config))
.withFallback(
List.of(IllegalStateException.class, CallNotPermittedException.class),
e -> "fallback"
);
Это делает цель более ясной.
В то время как функция отказоустойчивости Resilience4J не имеет общей иерархии классов, Failsafe предоставляет концепцию Policy:
Я считаю, что основное отличие Failsafe от Resilience4J заключается в его конвейерном подходе.
API Resilience4J требует, чтобы вы сначала предоставили «базовую» функцию, а затем встроили ее в любую функцию обертку (wrapper function). Вы не можете использовать цепочку из различных базовых функций.
Failsafe разрешает это с помощью класса FailsafeExecutor.
Вот как можно создать конвейер, т. е. экземпляр FailsafeExecutor. Обратите внимание, что в коде нет ссылки на базовый вызов:
Теперь можно обернуть вызов:
pipeline.get(() -> server.call());
Failsafe также предоставляет удобный API. С его помощью можно переписать приведенный выше код так:
var pipeline = Failsafe.with(Fallback.of("fallback"))
.compose(RetryPolicy.ofDefault())
.compose(Timeout.ofDuration(Duration.of(2000, MILLIS)));
Failsafe и Resilience4J основаны на композиции функций и очень похожи. Если вам нужно определить цепочку функций независимо от вызова базовых, отдайте предпочтение Failsafe. В противном случае можно выбрать любой из них.
Поскольку я лучше знаком с Resilience4J, я, вероятно, буду использовать Failsafe в своем следующем проекте, чтобы получить больше опыта.
- Retry (повтор попытки)
- Timeout (тайм-аут)
- Circuit Breaker (автоматический выключатель)
- Fallback (откат)
- Rate Limiter (ограничитель скорости), чтобы избежать кода ответа 429 от сервера
- Bulkhead: ограничитель скорости ограничивает количество вызовов в определенный период времени, а Bulkhead ограничивает количество одновременных вызовов.
Отказоустойчивость Microprofile
Отказоустойчивость Microprofile является частью зонтичного проекта Microprofile. Она отличается от двух других тем, что это спецификация, которая полагается на среду выполнения для обеспечения этих возможностей. Например, Open Liberty является одной из таких сред выполнения. SmallRye Fault Tolerance — еще одна. В свою очередь, другие компоненты, такие как Quarkus и WildFly, включают SmallRye.Microprofile определяет аннотации для каждой функции: @Timeout, @Retry Policy, @Fallback, @Circuit Breakerи @Bulkhead. Он также определяет аннотацию @Asynchronous.
Поскольку среда выполнения считывает аннотации, следует внимательно прочитать документацию, чтобы понять, как они взаимодействуют, если установлено более одной аннотации.
Можно указать аннотацию @Fallback, и она будет вызываться, если генерируется исключение TimeoutException. Если используется аннотация @Timeout вместе с @Retry, TimoutException вызовет @Retry. Когда используется @Timeout с @CircuitBreakerи если происходит TimeoutException, отказ будет вызывать @Circuit Breaker.
— Timeout Usage
Resilience4J
Я наткнулся на Resilience4J, когда вел свой доклад о шаблоне Circuit Breaker. Доклад включал демонстрацию, и она опиралась на Hystrix. Однажды я захотел обновить демоверсию до последней версии Hystrix и заметил, что разработчики отказались от нее в пользу Resilience4J.Resilience4J основан на нескольких основных концепциях:
- Один JAR-файл для каждой функции отказоустойчивости с дополнительными JAR-файлами для определенных интеграций, например, с Kotlin.
- Статические фабрики
- Композиция функций с помощью шаблона Decorator, примененного к функциям
- Интеграция с функциональными интерфейсами Java, например, Runnable, Callable, Function, и т. д.
- Распространение исключений: можно использовать функциональный интерфейс, который выбрасывает исключение, и библиотека будет распространять его по цепочке вызовов.
Каждая функция отказоустойчивости построена на основе одного и того же шаблона, показанного выше. Можно создать конвейер из нескольких функций, используя композицию функций, каждая из которых вызывает другую.
Проанализируем пример:
1 | Декорируем базовую функцию server.call() с помощью Retry: эта функция должна быть protected. |
2 | Используем конфигурацию по умолчанию |
3 | Создаем новую конфигурацию Circuit Breaker |
4 | Установим порог, выше которого вызов считается медленным |
5 | Подсчитаем скользящее окно из 2 вызовов |
6 | Минимальное количество вызовов для принятия решения о размыкании Circuit Breaker |
7 | Декорируем функцию повтора Circuit Breaker с приведенной выше конфигурацией. |
8 | Создаем значение fallback, которое будет возвращаться при отключении Circuit Breaker. |
9 | Список исключений для обработки: они не будут распространяться. Resilience4J генерирует исключение CallNotPermittedException когда цепь разомкнута. |
10 | В случае возникновения какого-либо из настроенных исключений вызываем эту функцию |
var pipeline = Decorators.ofSupplier(() -> server.call())
.withRetry(Retry.ofDefaults("retry"))
.withCircuitBreaker(CircuitBreaker.of("circuit-breaker", config))
.withFallback(
List.of(IllegalStateException.class, CallNotPermittedException.class),
e -> "fallback"
);
Это делает цель более ясной.
Failsafe
Я наткнулся на Failsafe не так давно. Его принципы аналогичны Resilience4J: статические фабрики, композиция функций и распространение исключений.В то время как функция отказоустойчивости Resilience4J не имеет общей иерархии классов, Failsafe предоставляет концепцию Policy:
Я считаю, что основное отличие Failsafe от Resilience4J заключается в его конвейерном подходе.
API Resilience4J требует, чтобы вы сначала предоставили «базовую» функцию, а затем встроили ее в любую функцию обертку (wrapper function). Вы не можете использовать цепочку из различных базовых функций.
Failsafe разрешает это с помощью класса FailsafeExecutor.
Вот как можно создать конвейер, т. е. экземпляр FailsafeExecutor. Обратите внимание, что в коде нет ссылки на базовый вызов:
1 | Определяем список политик, применяемых от последней к первой по порядку. |
2 | Fallback значение |
3 | Если время вызова превышает 2000 мс, генерируется исключение TimeoutExceededException. |
4 | Политика повторных попыток по умолчанию |
pipeline.get(() -> server.call());
Failsafe также предоставляет удобный API. С его помощью можно переписать приведенный выше код так:
var pipeline = Failsafe.with(Fallback.of("fallback"))
.compose(RetryPolicy.ofDefault())
.compose(Timeout.ofDuration(Duration.of(2000, MILLIS)));
Вывод
Все три библиотеки предоставляют более или менее равноценные функции. Если вы не используете среду выполнения, совместимую с CDI, такую как сервер приложений или Quarkus, забудьте о Microprofile Fault Tolerance.Failsafe и Resilience4J основаны на композиции функций и очень похожи. Если вам нужно определить цепочку функций независимо от вызова базовых, отдайте предпочтение Failsafe. В противном случае можно выбрать любой из них.
Поскольку я лучше знаком с Resilience4J, я, вероятно, буду использовать Failsafe в своем следующем проекте, чтобы получить больше опыта.
Краткое сравнение библиотек отказоустойчивости на JVM
Независимо от того, внедряете ли вы микросервисы или нет, есть вероятность, что вы вызываете конечные точки HTTP. С HTTP-вызовами многое может пойти не так. Опытные разработчики планируют это и...
habr.com