Пошаговое руководство по внедрению Reactive Resilience4j

Table of Contents · Why Reactive? · Blocking vs Non Blocking · Why Resilience4j? · Step 1. Add POM Dependency · Step 2. Add Circuit Breaker Configuration Bean · Step 3. Add Configuration for Circuit Breaker Properties · Step 4. Implementing Circuit Breaker · Troubleshooting · Swagger UI not Working for Webflux · How to Implement Circuit Breaker for Endpoints Which Don’t Have Return Type
В этой статье основное внимание будет уделено реализации реактивного прерывателя цепи с использованием Resilience4j, библиотеки Spring Cloud Circuit Breaker.
Почему реактивный?
Давайте сделаем шаг назад и подробнее рассмотрим некоторые параметры, доступные для использования REST API.

Блокировка против неблокировки
«Блокирующая» сторона параметров на диаграмме выше в основном основана на модели «поток на запрос». Это означает, что поток будет заблокирован до тех пор, пока клиент REST API не получит ответ. Если у нас будет много входящих запросов, приложение создаст много потоков, которые исчерпают пул потоков или займут всю доступную память. Пользователи могут столкнуться со снижением производительности.
Фреймворк Spring 5 Reactive представил WebClient, асинхронное неблокирующее решение. Фреймворк Reactive использует архитектуру, управляемую событиями. Он предоставляет средства для создания асинхронной логики через API Reactive Streams. По сравнению с синхронным/блокирующим методом реактивный подход может обрабатывать больше логики при использовании меньшего количества потоков и системных ресурсов. Более того, используя WebClient, мы можем выполнять синхронные или асинхронные HTTP-запросы с помощью функционального API Fluent, который можно напрямую интегрировать в нашу существующую конфигурацию Spring и реактивную среду WebFlux.
При использовании REST API победитель очевиден! Неблокирующий реактивный WebClient это так!
Почему Resilience4j?
Есть две основные библиотеки, которые мы можем использовать для реализации Circuit Breaker. Netflix Hystrix с объектно-ориентированным дизайном, в котором вызовы внешних систем должны быть заключены в HystrixCommand, предлагающий несколько функций. Однако в SpringOne 2019 Spring объявила, что Hystrix Dashboard будет удалена из версии Spring Cloud 3.1, что делает ее официально устаревшей. Не рекомендуется использовать устаревшую библиотеку. Итак, выбор ясен, Resilience4j!
Resilience4j — это отдельная библиотека, вдохновленная Hystrix, но основанная на принципах функционального программирования. Resilience4J предоставляет функции высшего порядка (декораторы) для улучшения любого функционального интерфейса, лямбда-выражения или ссылки на метод с помощью автоматического выключателя, ограничителя скорости или переборки.
Другие преимущества Resilience4J включают в себя более точные параметры конфигурации (например, количество успешных выполнений, необходимых для закрытия шаблона прерывателя цепи) и меньшее количество зависимостей.
Мы собираемся использовать два микросервиса Spring Boot, чтобы продемонстрировать, как реализовать реактивный прерыватель цепи:
- Customer-service, который выступает в качестве поставщика REST API, предлагая клиентам конечные точки CRUD.
- клиент-сервис-клиент, который использует
WebClientчерез библиотеку Spring Boot Starter Webflux для вызова REST API.

Теперь давайте подробно рассмотрим шаги по реализации Resilience4j для реактивного выключателя.
Шаг 1. Добавьте зависимость POM
Поскольку мы выбрали WebClient для использования REST API, нам нужно добавить зависимость Spring Cloud Circuit Breaker Reactor Resilience4J в наше клиентское приложение REST.
Шаг 2. Добавьте компонент конфигурации прерывателя цепи
CircuitBreakerConfig поставляется с набором значений по умолчанию для конфигурации прерывателя цепи, если мы решим использовать значения конфигурации по умолчанию для всех наших прерывателей цепи, мы можем создать bean-компонент Customize, которому передается ReactiveResilience4JCircuitBreakerFactory. Заводской метод configureDefault можно использовать для обеспечения конфигурации по умолчанию. Пример фрагмента следующим образом:
Если мы решим использовать настраиваемые значения конфигурации, нам нужно будет определить наш bean-компонент следующим образом («customer-service» — это просто образец экземпляра клиента REST, вы можете использовать любое имя экземпляра, которое вы даете своему клиентскому приложению REST):
Шаг 3. Добавьте конфигурацию для свойств автоматического выключателя
Если мы определим наш настроенный bean-компонент конфигурации, нам также потребуется добавить конфигурацию прерывателя цепи, например, в application.yml (только примерные значения, числа должны быть изменены в зависимости от сценариев использования приложения):
failureRateThreshold: Когда частота отказов равна или превышает пороговое значение, автоматический выключатель переходит в размыкание и начинает замыкать вызовы. В нашем случае это значение равно 50%, что означает, что если 1 из 2 запросов не будет выполнен, будет достигнут порог, при котором автоматический выключатель перейдет в состояние OPEN.minimumNumberOfCalls: этот атрибут обеспечивает расчет частоты отказов после выполнения минимального количества вызовов. В нашем случае необходимо выполнить 10 запросов, прежде чем начнется расчет частоты отказов.slidingWindowType: настраивает тип скользящего окна, которое используется для записи результатов вызовов, когда прерыватель цепи замкнут.
Скользящее окно может быть основано либо на счете, либо на времени.slidingWindowSize: настраивает размер скользящего окна, которое используется для записи результатов вызовов, когда автоматический выключатель замкнут.waitDurationInOpenState: время, в течение которого автоматический выключатель должен ждать перед переходом из разомкнутого состояния в полуоткрытое. В нашем случае это 50 секунд.permittedNumberOfCallsInHalfOpenState: настраивает количество разрешенных вызовов, когда автоматический выключатель наполовину разомкнут. В нашем случае ограничение равно 3, что означает, что только 3 запроса будут обработаны в 10-секундном окне.
Шаг 4. Внедрение автоматического выключателя
Теперь, когда вся конфигурация готова, мы можем начать украшать наши вызовы REST API со стороны клиента с помощью Circuit Breaker. В приведенном ниже примере мы внедряем WebClient и ReactiveCircuitBreakerFactory в CustomerCientController посредством внедрения конструктора. Затем мы используем webClient , чтобы инициировать CRUD-вызовы для переданного CustomerVO и/или customerId. Обратите внимание на разделы «transform», где мы создаем экземпляр ReactiveCircuitBreaker для «обслуживания клиентов» (rcb типа ReactiveCircuitBreaker) с помощью ReactiveCircuitBreakerFactory. Строка для выполнения автоматического выключателя rcb.run(...). В приведенном ниже примере контроллера при возникновении исключений мы возвращаем пустой объект CustomerVO object для вызовов POST/GET/PUT в качестве резервного ответа. Для вызова DELETE мы возвращаем customerId passed в качестве запасного варианта. Таким образом, вместо того, чтобы получать 500 Internal Server Error в случае, если поставщик REST API не работает, с правильно реализованным прерывателем цепи мы получаем резервные ответы.
Поиск неисправностей
Пользовательский интерфейс Swagger не работает для Webflux
Поскольку мы представили библиотеку Webflux для использования WebClient, вы можете заметить, что ваш пользовательский интерфейс swagger изначально не работает. Чтобы заставить его работать, убедитесь, что реализованы следующие шаги:
- Добавьте следующую зависимость в pom:
- Удалите аннотацию
@EnableSwagger2WebFlux, если она у вас реализована. - URL-адрес для доступа к swagger теперь должен быть: http://‹YOUR_APP_SERVER›:‹YOUR_APP_PORT›/swagger-ui/, обязательно добавьте окончание /. Например, http://localhost:8100/swagger-ui/.
Как реализовать прерыватель цепи для конечных точек, которые не имеют типа возврата
Для конечных точек, которые не возвращают содержимое в своем теле ответа, например следующей конечной точки в поставщике REST API, на стороне клиента REST, если мы пометим соответствующий метод, который вызывает эту конечную точку для возврата Mono<Void>, ReactiveCircuitBreaker не будет работать. Вы увидите ошибку сервера 500 в случае, если поставщик REST API не работает, что полностью противоречит цели использования прерывателя цепи.
В реализации нереактивного прерывателя цепи для методов, которые не имеют возвращаемого типа, мы можем использовать «CheckedRunnable», выполнив следующие действия (пример):
НО, в реактивном прерывателе ReactiveCircuitBreaker нет такого интерфейса для украшения CheckedRunnable, так что же нам делать? После некоторых исследований и экспериментов я заметил, что мы можем манипулировать типом возвращаемого значения для таких конечных точек, чтобы возвращать общий тип, такой как String. Проще говоря, если конечная точка, такая как вызов DELETE, возвращает Void на стороне сервера, мы все еще можем манипулировать возвращаемым типом этого вызова DELETE со стороны клиента, чтобы вернуть простой тип как String, просто передав входную строку, которая была передана. в эту конечную точку. Например, на стороне клиента мы можем реализовать вызов DELETE следующим образом:
Обратите внимание, что мы возвращаем Mono<String> вместо Mono<Void>, и мы указываем возвращаемый тип String в строке .bodyToMono(String.class), поэтому мы можем просто вызвать run метод ReactiveCircuitBreaker для вызова оформленной реактивной функции прерывателя цепи. Это единственный способ, которым я могу обойти ReactiveCircuitBreaker, не имея его метода decorateCheckedRunnable для обработки методов, которые не имеют возвращаемых типов.
Исходный код демонстрационных приложений, упомянутых в этой статье, можно найти в моем репозитории GitHub.
Удачного кодирования! Удачного крафта!
Ссылки
https://resilience4j.readme.io/docs/ Circuitbreaker
https://resilience4j.readme.io/docs/comparison-to-netflix-hystrix
Переход с Hystrix на Resilience4J
https://github.com/springfox/springfox#migrating-from-earlier-snapshot
Отправка HTTP-запросов с помощью Spring WebClient
Не стесняйтесь проверить некоторые из моих других статей о Spring Boot: