
Библиотека отказоустойчивости для Java, предоставляющая circuit breaker, rate limiter, retry, bulkhead, timeout и декораторы кэша для функционального программирования.
= Библиотека отказоустойчивости, предназначенная для функционального программирования :author: Robert Winkler and Bohdan Storozhuk :icons: :toc: macro :numbered: 1 ifdef::env-github[] :tip-caption: 💡 :note-caption: ℹ️ :important-caption: ❗ :caution-caption: 🔥 :warning-caption: ⚠️ endif::[]
image:https://github.com/resilience4j/resilience4j/actions/workflows/gradle-build.yml/badge.svg["Build Status"] image:https://img.shields.io/nexus/r/io.github.resilience4j/resilience4j-circuitbreaker?server=https%3A%2F%2Foss.sonatype.org["Release"] image:https://img.shields.io/nexus/s/io.github.resilience4j/resilience4j-circuitbreaker?server=https%3A%2F%2Foss.sonatype.org["Snapshot"] image:http://img.shields.io/badge/license-ASF2-blue.svg["Apache License 2", link="http://www.apache.org/licenses/LICENSE-2.0.txt"]
image:https://sonarcloud.io/api/project_badges/measure?project=resilience4j_resilience4j&metric=coverage["Coverage", link="https://sonarcloud.io/dashboard?id=resilience4j_resilience4j"] image:https://sonarcloud.io/api/project_badges/measure?project=resilience4j_resilience4j&metric=sqale_rating["Maintainability", link="https://sonarcloud.io/dashboard?id=resilience4j_resilience4j"] image:https://sonarcloud.io/api/project_badges/measure?project=resilience4j_resilience4j&metric=reliability_rating["Reliability", link="https://sonarcloud.io/dashboard?id=resilience4j_resilience4j"] image:https://sonarcloud.io/api/project_badges/measure?project=resilience4j_resilience4j&metric=security_rating["Security", link="https://sonarcloud.io/dashboard?id=resilience4j_resilience4j"] image:https://sonarcloud.io/api/project_badges/measure?project=resilience4j_resilience4j&metric=vulnerabilities["Vulnerabilities", link="https://sonarcloud.io/dashboard?id=resilience4j_resilience4j"] image:https://sonarcloud.io/api/project_badges/measure?project=resilience4j_resilience4j&metric=bugs["Bugs", link="https://sonarcloud.io/dashboard?id=resilience4j_resilience4j"]
toc::[]
== Введение
Resilience4j — это легковесная библиотека для обеспечения отказоустойчивости, предназначенная для функционального программирования. Resilience4j предоставляет функции высшего порядка (декораторы) для расширения любого функционального интерфейса, лямбда-выражения или ссылки на метод с помощью Circuit Breaker, Rate Limiter, Retry или Bulkhead. Вы можете накладывать несколько декораторов на любой функциональный интерфейс, лямбда-выражение или ссылку на метод. Преимущество в том, что вы можете выбрать только те декораторы, которые вам нужны, и ничего больше.
Resilience4j 3 требует Java 21.
// Create a CircuitBreaker with default configuration CircuitBreaker circuitBreaker = CircuitBreaker.ofDefaults("backendService");
// Create a Retry with default configuration // 3 retry attempts and a fixed time interval between retries of 500ms Retry retry = Retry.ofDefaults("backendService");
// Create a Bulkhead with default configuration Bulkhead bulkhead = Bulkhead.ofDefaults("backendService");
Supplier supplier = () -> backendService .doSomething(param1, param2);
// Decorate your call to backendService.doSomething() // with a Bulkhead, CircuitBreaker and Retry // **note: you will need the resilience4j-all dependency for this Supplier decoratedSupplier = Decorators.ofSupplier(supplier) .withCircuitBreaker(circuitBreaker) .withBulkhead(bulkhead) .withRetry(retry) .decorate();
// Execute the decorated supplier and recover from any exception String result = Try.ofSupplier(decoratedSupplier) .recover(throwable -> "Hello from Recovery").get();
// When you don't want to decorate your lambda expression, // but just execute it and protect the call by a CircuitBreaker. String result = circuitBreaker .executeSupplier(backendService::doSomething);
// You can also run the supplier asynchronously in a ThreadPoolBulkhead ThreadPoolBulkhead threadPoolBulkhead = ThreadPoolBulkhead .ofDefaults("backendService");
// The Scheduler is needed to schedule a timeout on a non-blocking CompletableFuture ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(3); TimeLimiter timeLimiter = TimeLimiter.of(Duration.ofSeconds(1));
NOTE: С Resilience4j вам не нужно действовать по принципу «всё или ничего» — вы можете https://mvnrepository.com/artifact/io.github.resilience4j[*выбрать то, что нужно*].
== Документация
Настройка и использование описаны в нашем https://resilience4j.readme.io/docs[Руководстве пользователя].
https://github.com/resilience4j-docs-ja/resilience4j-docs-ja[Неофициальный перевод на японский язык волонтёрами]
https://github.com/lmhmhl/Resilience4j-Guides-Chinese[Неофициальный перевод документации Resilience4j на китайский язык волонтёрами]
== Обзор
Resilience4j предоставляет несколько основных модулей:
Существуют также дополнительные модули для метрик, Feign, Kotlin, Spring, Ratpack, Vertx, RxJava2 и других.
NOTE: Полный список модулей приведён в нашем https://resilience4j.readme.io/docs#section-modularization[Руководстве пользователя].
TIP: Пакет основных модулей или билдер +Decorators+ см. в https://mvnrepository.com/artifact/io.github.resilience4j/resilience4j-all[resilience4j-all].
== Лучшие практики
=== Управление экземплярами: когда совместно использовать, а когда нет
Одно из самых важных понятий для новичков — понимать, когда создавать отдельные экземпляры, а когда совместно использовать экземпляры для разных удалённых сервисов или бэкендов.
==== Почему важны уникальные экземпляры
Создание отдельных экземпляров для каждого бэкенд-сервиса критически важно для:
==== Паттерны, зависящие от экземпляра, и паттерны, не зависящие от экземпляра
===== Паттерны, зависящие от экземпляра (НЕЛЬЗЯ совместно использовать)
Эти паттерны хранят состояние, специфичное для конкретного сервиса, и ДОЛЖНЫ иметь отдельные экземпляры:
// CORRECT: Separate CircuitBreaker for each service CircuitBreaker paymentServiceCB = CircuitBreaker.ofDefaults("paymentService"); CircuitBreaker inventoryServiceCB = CircuitBreaker.ofDefaults("inventoryService"); CircuitBreaker notificationServiceCB = CircuitBreaker.ofDefaults("notificationService");
// CORRECT: Separate Bulkhead for each service Bulkhead paymentBulkhead = Bulkhead.ofDefaults("paymentService"); Bulkhead inventoryBulkhead = Bulkhead.ofDefaults("inventoryService");
===== Паттерны, не зависящие от экземпляра (можно совместно использовать, но лучше не стоит)
Эти паттерны создают новый контекст для каждого выполнения и не хранят состояние, специфичное для сервиса:
// TECHNICALLY OK: Retry doesn't maintain state between calls Retry sharedRetry = Retry.ofDefaults("shared");
// BETTER: Unique instances provide better metrics and monitoring Retry paymentRetry = Retry.ofDefaults("paymentService"); Retry inventoryRetry = Retry.ofDefaults("inventoryService");
==== Рекомендуемый подход: всегда используйте уникальные экземпляры
Даже для паттернов, которые можно совместно использовать, рекомендуется создавать уникальные экземпляры, потому что:
===== Полный пример: несколько сервисов
// You have 3 backend services to protect public class ServiceOrchestrator {
// Payment Service protection
private final CircuitBreaker paymentCB = CircuitBreaker.ofDefaults("paymentService");
private final Retry paymentRetry = Retry.ofDefaults("paymentService");
private final Bulkhead paymentBulkhead = Bulkhead.ofDefaults("paymentService");
// Inventory Service protection
private final CircuitBreaker inventoryCB = CircuitBreaker.ofDefaults("inventoryService");
private final Retry inventoryRetry = Retry.ofDefaults("inventoryService");
private final Bulkhead inventoryBulkhead = Bulkhead.ofDefaults("inventoryService");
// Notification Service protection
private final CircuitBreaker notificationCB = CircuitBreaker.ofDefaults("notificationService");
private final Retry notificationRetry = Retry.ofDefaults("notificationService");
public Order processOrder(OrderRequest request) {
// Each service call is protected by its own set of resilience instances
// Call payment service
Supplier<PaymentResult> paymentCall = () -> paymentService.charge(request);
PaymentResult payment = Decorators.ofSupplier(paymentCall)
.withCircuitBreaker(paymentCB)
.withRetry(paymentRetry)
.withBulkhead(paymentBulkhead)
.decorate()
.get();
// Call inventory service
Supplier<InventoryResult> inventoryCall = () -> inventoryService.reserve(request);
InventoryResult inventory = Decorators.ofSupplier(inventoryCall)
.withCircuitBreaker(inventoryCB)
.withRetry(inventoryRetry)
.withBulkhead(inventoryBulkhead)
.decorate()
.get();
// Call notification service (demonstrates circuit isolation: the notification circuit breaker remains independent of the payment circuit breaker, even if payment opens its circuit in other calls)
Runnable notificationCall = () -> notificationService.send(request);
Decorators.ofRunnable(notificationCall)
.withCircuitBreaker(notificationCB)
.withRetry(notificationRetry)
.decorate()
.run();
return new Order(payment, inventory);
}
==== Использование реестров для управления экземплярами
Resilience4j предоставляет классы реестров для эффективного управления несколькими экземплярами:
// Create a registry with custom default configuration CircuitBreakerConfig defaultConfig = CircuitBreakerConfig.custom() .failureRateThreshold(50) .waitDurationInOpenState(Duration.ofSeconds(30)) .build();
CircuitBreakerRegistry registry = CircuitBreakerRegistry.of(defaultConfig);
// Get or create instances by name CircuitBreaker paymentCB = registry.circuitBreaker("paymentService"); CircuitBreaker inventoryCB = registry.circuitBreaker("inventoryService");
NOTE: Реестры особенно полезны в приложениях Spring Boot, где они настраиваются автоматически, а экземпляры создаются по требованию на основе свойств конфигурации.
==== Итоги
[cols="<.<*", options="header"] |=== |Паттерн |Можно ли совместно использовать? |Следует ли совместно использовать? |Почему?
|CircuitBreaker |Нет |Нет |Состояние специфично для сервиса. Совместное использование приводит к тому, что сбои одного сервиса влияют на все остальные.
|Bulkhead |Нет |Нет |Лимиты параллельных вызовов должны быть изолированы для каждого сервиса для корректного управления ресурсами.
|RateLimiter |Нет |Нет |Лимиты частоты запросов обычно различаются для каждого сервиса, и совместное использование сводит на нет их назначение.
|Retry |Да |Нет |Хотя технически безопасно совместно использовать, уникальные экземпляры обеспечивают лучшие метрики и наблюдаемость.
|TimeLimiter |Да |Нет |Хотя технически безопасно совместно использовать, уникальные экземпляры обеспечивают лучшие метрики и наблюдаемость.
|Cache |Зависит |Зависит |Совместно используйте только в том случае, если кэшируемые данные действительно идентичны во всех сценариях использования.
|===
== Паттерны отказоустойчивости
[cols="<.<*", options="header"] |=== |название |как это работает? |описание |ссылки
|Retry |повторяет неудачные выполнения |Многие сбои носят временный характер и могут устраниться сами после короткой задержки. |<<circuitbreaker-retry-fallback,обзор>>, https://resilience4j.readme.io/docs/retry[документация], https://resilience4j.readme.io/docs/getting-started-3#annotations[Spring]
|Circuit Breaker |временно блокирует возможные сбои |Когда система серьёзно перегружена, быстрый отказ лучше, чем заставлять клиентов ждать. |<<circuitbreaker-retry-fallback,обзор>>, https://resilience4j.readme.io/docs/circuitbreaker[документация], https://resilience4j.readme.io/docs/feign[Feign], https://resilience4j.readme.io/docs/getting-started-3#annotations[Spring]
|Rate Limiter |ограничивает количество выполнений за период |Ограничьте частоту входящих запросов. |<<ratelimiter,обзор>>, https://resilience4j.readme.io/docs/ratelimiter[документация], https://resilience4j.readme.io/docs/feign[Feign], https://resilience4j.readme.io/docs/getting-started-3#annotations[Spring]
|Time Limiter |ограничивает продолжительность выполнения |После определённого интервала ожидания успешный результат маловероятен. |https://resilience4j.readme.io/docs/timeout[документация], https://resilience4j.readme.io/docs/getting-started-3#annotations[Spring]
|Bulkhead |ограничивает параллельные выполнения |Ресурсы изолируются в пулы, чтобы при сбое одного остальные продолжали работать. |<<bulkhead,обзор>>, https://resilience4j.readme.io/docs/bulkhead[документация], https://resilience4j.readme.io/docs/getting-started-3#annotations[Spring]
|Cache |запоминает успешный результат |Некоторая доля запросов может быть похожей. |https://resilience4j.readme.io/docs/cache[документация]
|Fallback |предоставляет альтернативный результат при сбоях |Сбои всё равно будут — спланируйте, что вы будете делать, когда это произойдёт. |<<circuitbreaker-retry-fallback,Try::recover>>, https://resilience4j.readme.io/docs/getting-started-3#section-annotations[Spring], https://resilience4j.readme.io/docs/feign[Feign]
|===
Приведённая выше таблица основана на https://github.com/App-vNext/Polly#resilience-policies[Polly: политики отказоустойчивости].
NOTE: Чтобы узнать больше о паттернах отказоустойчивости, обратитесь к разделу link:#Talks[Доклады]. Узнайте больше о компонентах в нашем https://resilience4j.readme.io/docs/getting-started-2[Руководстве пользователя].
== Spring Boot
Настройка и использование в Spring Boot 3 продемонстрированы https://github.com/resilience4j/resilience4j-spring-boot3-demo[здесь].
Поддержка виртуальных потоков (Java 21 Project Loom) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Начиная с Resilience4j 3 вы можете переключить внутренние планировщики на Java виртуальные потоки.
То же самое можно включить с помощью аргумента JVM:``` -Dresilience4j.thread.type=virtual
Если свойство (или системное свойство) не указано, библиотека возвращается к обычным *платформенным* потокам.
== Примеры использования
[[circuitbreaker-retry-fallback]]
=== CircuitBreaker, Retry и Fallback
В следующем примере показано, как декорировать лямбда-выражение (Supplier) с помощью CircuitBreaker и как повторить вызов не более 3 раз при возникновении исключения.
Вы можете настроить интервал ожидания между повторами, а также настроить собственный алгоритм backoff.
В примере используется монада Try из Vavr, чтобы восстановиться после исключения и вызвать другое лямбда-выражение в качестве запасного варианта (fallback), когда даже все повторы завершились неудачей.
[source,java]
----
// Simulates a Backend Service
public interface BackendService {
String doSomething();
}
// Create a CircuitBreaker (use default configuration)
CircuitBreaker circuitBreaker = CircuitBreaker.ofDefaults("backendName");
// Create a Retry with at most 3 retries and a fixed time interval between retries of 500ms
Retry retry = Retry.ofDefaults("backendName");
// Decorate your call to BackendService.doSomething() with a CircuitBreaker
Supplier<String> decoratedSupplier = CircuitBreaker
.decorateSupplier(circuitBreaker, backendService::doSomething);
// Decorate your call with automatic retry
decoratedSupplier = Retry
.decorateSupplier(retry, decoratedSupplier);
// Use of Vavr's Try to
// execute the decorated supplier and recover from any exception
String result = Try.ofSupplier(decoratedSupplier)
.recover(throwable -> "Hello from Recovery").get();
// When you don't want to decorate your lambda expression,
// but just execute it and protect the call by a CircuitBreaker.
String result = circuitBreaker.executeSupplier(backendService::doSomething);
----
==== CircuitBreaker и RxJava2
В следующем примере показано, как декорировать Observable с помощью специального оператора RxJava.
[source,java]
----
CircuitBreaker circuitBreaker = CircuitBreaker.ofDefaults("testName");
Observable.fromCallable(backendService::doSomething)
.compose(CircuitBreakerOperator.of(circuitBreaker))
----
NOTE: Resilience4j также предоставляет операторы RxJava для `+RateLimiter+`, `+Bulkhead+`, `+TimeLimiter+` и `+Retry+`.
Узнайте больше в нашем *https://resilience4j.readme.io/docs/getting-started-2[Руководстве пользователя]*.
==== CircuitBreaker и Spring Reactor
В следующем примере показано, как декорировать Mono с помощью специального оператора Reactor.
[source,java]
----
CircuitBreaker circuitBreaker = CircuitBreaker.ofDefaults("testName");
Mono.fromCallable(backendService::doSomething)
.transformDeferred(CircuitBreakerOperator.of(circuitBreaker))
----
NOTE: Resilience4j также предоставляет операторы Reactor для `+RateLimiter+`, `+Bulkhead+`, `+TimeLimiter+` и `+Retry+`.
Узнайте больше в нашем *https://resilience4j.readme.io/docs/getting-started-1[Руководстве пользователя]*.
[[ratelimiter]]
=== RateLimiter
В следующем примере показано, как ограничить частоту вызовов некоторого метода значением не выше 1 запрос/секунду.
[source,java]
----
// Create a custom RateLimiter configuration
RateLimiterConfig config = RateLimiterConfig.custom()
.timeoutDuration(Duration.ofMillis(100))
.limitRefreshPeriod(Duration.ofSeconds(1))
.limitForPeriod(1)
.build();
// Create a RateLimiter
RateLimiter rateLimiter = RateLimiter.of("backendName", config);
// Decorate your call to BackendService.doSomething()
Supplier<String> restrictedSupplier = RateLimiter
.decorateSupplier(rateLimiter, backendService::doSomething);
// First call is successful
Try<String> firstTry = Try.ofSupplier(restrictedSupplier);
assertThat(firstTry.isSuccess()).isTrue();
// Second call fails, because the call was not permitted
Try<String> secondTry = Try.of(restrictedSupplier);
assertThat(secondTry.isFailure()).isTrue();
assertThat(secondTry.getCause()).isInstanceOf(RequestNotPermitted.class);
----
[[bulkhead]]
=== Bulkhead
Существуют две стратегии изоляции и две реализации bulkhead.
==== SemaphoreBulkhead
В следующем примере показано, как декорировать лямбда-выражение с помощью Bulkhead.
Bulkhead можно использовать для ограничения количества параллельных выполнений.
Эта абстракция bulkhead должна хорошо работать в различных моделях потоков и ввода-вывода.
Она основана на семафоре и, в отличие от Hystrix, не предоставляет опцию "теневого" пула потоков.
[source,java]
----
// Create a custom Bulkhead configuration
BulkheadConfig config = BulkheadConfig.custom()
.maxConcurrentCalls(150)
.maxWaitDuration(100)
.build();
Bulkhead bulkhead = Bulkhead.of("backendName", config);
Supplier<String> supplier = Bulkhead
.decorateSupplier(bulkhead, backendService::doSomething);
----
[[threadpoolbulkhead]]
==== ThreadPoolBulkhead
В следующем примере показано, как использовать лямбда-выражение с ThreadPoolBulkhead, который использует ограниченную очередь и фиксированный пул потоков.
[source,java]
----
// Create a custom ThreadPoolBulkhead configuration
ThreadPoolBulkheadConfig config = ThreadPoolBulkheadConfig.custom()
.maxThreadPoolSize(10)
.coreThreadPoolSize(2)
.queueCapacity(20)
.build();
ThreadPoolBulkhead bulkhead = ThreadPoolBulkhead.of("backendName", config);
// Decorate or execute immediately a lambda expression with a ThreadPoolBulkhead.
Supplier<CompletionStage<String>> supplier = ThreadPoolBulkhead
.decorateSupplier(bulkhead, backendService::doSomething);
CompletionStage<String> execution = bulkhead
.executeSupplier(backendService::doSomething);
----
[[events]]
== Потребление испускаемых событий
Компоненты `+CircuitBreaker+`, `+RateLimiter+`, `+Cache+`, `+Bulkhead+`, `+TimeLimiter+` и `+Retry+` испускают поток событий.
Их можно потреблять для логирования, проверок и любых других целей.
=== Примеры
`+CircuitBreakerEvent+` может быть переходом состояния, сбросом circuit breaker, успешным вызовом, зарегистрированной ошибкой или проигнорированной ошибкой.
Все события содержат дополнительную информацию, например, время создания события и продолжительность обработки вызова.
Если вы хотите потреблять события, вам необходимо зарегистрировать потребителя событий.
[source,java]
----
circuitBreaker.getEventPublisher()
.onSuccess(event -> logger.info(...))
.onError(event -> logger.info(...))
.onIgnoredError(event -> logger.info(...))
.onReset(event -> logger.info(...))
.onStateTransition(event -> logger.info(...));
// Or if you want to register a consumer listening to all events, you can do:
circuitBreaker.getEventPublisher()
.onEvent(event -> logger.info(...));
----
Вы можете использовать адаптеры RxJava или Spring Reactor, чтобы преобразовать `+EventPublisher+` в Reactive Stream.
Преимущество Reactive Stream в том, что вы можете использовать оператор `+observeOn+` из RxJava, чтобы указать другой Scheduler, который CircuitBreaker будет использовать для отправки уведомлений своим наблюдателям/потребителям.
[source,java]
----
RxJava2Adapter.toFlowable(circuitBreaker.getEventPublisher())
.filter(event -> event.getEventType() == Type.ERROR)
.cast(CircuitBreakerOnErrorEvent.class)
.subscribe(event -> logger.info(...))
----
NOTE: Вы также можете потреблять события из других компонентов.
Узнайте больше в нашем *https://resilience4j.readme.io/[Руководстве пользователя]*.
== Доклады
[cols="4*"]
|===
|0:34
|https://www.youtube.com/watch?v=kR2sm1zelI4[Битва circuit breaker'ов: Resilience4J против Istio]
|Nicolas Frankel
|GOTO Berlin
|0:33
|https://www.youtube.com/watch?v=AwcjOhD91Q0[Битва circuit breaker'ов: Istio против Hystrix/Resilience4J]
|Nicolas Frankel
|JFuture
|0:42
|https://www.youtube.com/watch?v=KosSsZEqS-k&t=157[Паттерны отказоустойчивости в мире после Hystrix]
|Tomasz Skowroński
|Cloud Native Warsaw
|0:52
|https://www.youtube.com/watch?v=NHVxrLb3jFI[Создание надёжных и отказоустойчивых приложений с помощью Spring Boot и Resilience4j]
|David Caron
|SpringOne
|0:22
|https://www.youtube.com/watch?v=gvDvOWtPLVY&t=140[Hystrix мёртв, что дальше?]
|Tomasz Skowroński
|DevoxxPL
|===
== Компании, использующие Resilience4j
* *Deutsche Telekom* (в приложении с более чем 400 миллионами запросов в день)
* *AOL* (в приложении с требованиями низкой задержки)
* *Netpulse* (в системе с 40+ интеграциями)
* *wescale.de* (в интеграционной платформе B2B)
* *Topia* (в HR-приложении, построенном на архитектуре микросервисов)
* *Auto Trader Group plc* (крупнейшая британская цифровая автомобильная торговая площадка)
* *PlayStation Network* (бэкенд платформы)
* *TUI InfoTec GmbH* (бэкенд-приложения в потоках процессов бронирования мест размещения)
== Лицензия
Авторские права 2020 Robert Winkler, Bohdan Storozhuk, Mahmoud Romeh, Dan Maas и другие
Лицензировано в соответствии с Apache License, версия 2.0 ("Лицензия");
вы не можете использовать этот файл иначе как в соответствии с Лицензией.
Вы можете получить копию Лицензии по адресу:
http://www.apache.org/licenses/LICENSE-2.0
Если иное не требуется применимым законодательством или не согласовано в письменной форме, программное обеспечение, распространяемое в соответствии с Лицензией, распространяется на основе "AS IS",
БЕЗ КАКИХ-ЛИБО ГАРАНТИЙ ИЛИ УСЛОВИЙ, явных или подразумеваемых.
См. Лицензию для получения информации о конкретных разрешениях и ограничениях.