1. Обзор
В этом руководстве мы рассмотрим Spring Cloud Netflix Hystrix — библиотеку отказоустойчивости. Мы воспользуемся библиотекой и реализуем корпоративный шаблон Circuit Breaker, описывающий стратегию предотвращения каскадного сбоя на разных уровнях приложения.
Принцип аналогичен электронике: Hystrix отслеживает способы неудачных вызовов соответствующих служб. В случае такого отказа он разомкнет цепь и перенаправит вызов резервному методу.
Библиотека будет терпеть сбои до порогового значения. Кроме того, он оставляет цепь открытой. Это означает, что он будет перенаправлять все последующие вызовы на резервный метод, чтобы предотвратить будущие сбои. Это создает временной буфер для связанной службы, чтобы восстановиться из состояния сбоя.
2. Производитель ОТДЫХА
Чтобы создать сценарий, демонстрирующий шаблон прерывателя цепи, нам сначала понадобится служба. Мы назовем его «REST Producer», поскольку он предоставляет данные для «REST Consumer» с поддержкой Hystrix, который мы создадим на следующем шаге.
Давайте создадим новый проект Maven, используя зависимость spring-boot-starter-web :
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.2.6.RELEASE</version>
</dependency>
Сам проект намеренно сделан простым. Он состоит из интерфейса контроллера с одним аннотированным методом GET @RequestMapping
, возвращающим просто String,
@RestController ,
реализующим этот интерфейс, и @SpringBootApplication
.
Начнем с интерфейса:
public interface GreetingController {
@GetMapping("/greeting/{username}")
String greeting(@PathVariable("username") String username);
}
И реализация:
@RestController
public class GreetingControllerImpl implements GreetingController {
@Override
public String greeting(@PathVariable("username") String username) {
return String.format("Hello %s!\n", username);
}
}
Далее мы запишем основной класс приложения:
@SpringBootApplication
public class RestProducerApplication {
public static void main(String[] args) {
SpringApplication.run(RestProducerApplication.class, args);
}
}
Чтобы завершить этот раздел, осталось только настроить порт приложения, который мы будем прослушивать. Мы не будем использовать порт 8080 по умолчанию, потому что порт должен оставаться зарезервированным для приложения, описанного в следующем шаге.
Кроме того, мы определяем имя приложения, чтобы иметь возможность искать нашего производителя из клиентского приложения, которое мы представим позже.
Затем укажем порт 9090
и имя rest-producer
в нашем файле application.properties :
server.port=9090
spring.application.name=rest-producer
Теперь мы можем протестировать нашего производителя с помощью cURL:
$> curl http://localhost:9090/greeting/Cid
Hello Cid!
3. Потребитель REST с Hystrix
В нашем демонстрационном сценарии мы будем реализовывать веб-приложение, которое использует службу REST из предыдущего шага, используя RestTemplate
и Hystrix
. Для простоты мы назовем его «REST Consumer».
Следовательно, мы создаем новый проект Maven с зависимостями spring-cloud-starter-
hystrix , spring-boot-starter-web
и spring-boot-starter-thymeleaf
:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
<version>1.4.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.2.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
<version>2.2.6.RELEASE</version>
</dependency>
Чтобы автоматический выключатель работал, Hystix сканирует аннотированные классы @Component
или @Service
на наличие аннотированных методов @HystixCommand
, реализует для него прокси и отслеживает его вызовы.
Сначала мы создадим класс @Service
, который будет внедрен в @Controller
. Поскольку мы создаем веб-приложение с использованием Thymeleaf, нам также нужен шаблон HTML, который будет служить представлением.
Это будет наш внедряемый @Service
, реализующий @HystrixCommand
с соответствующим резервным методом. Этот запасной вариант должен использовать ту же подпись, что и оригинал:
@Service
public class GreetingService {
@HystrixCommand(fallbackMethod = "defaultGreeting")
public String getGreeting(String username) {
return new RestTemplate()
.getForObject("http://localhost:9090/greeting/{username}",
String.class, username);
}
private String defaultGreeting(String username) {
return "Hello User!";
}
}
RestConsumerApplication
будет нашим основным классом приложения. Аннотация @EnableCircuitBreaker будет сканировать путь
к классам в поисках любой совместимой реализации прерывателя цепи.
Чтобы использовать Hystrix явно, мы должны аннотировать этот класс с помощью @EnableHystrix
:
@SpringBootApplication
@EnableCircuitBreaker
public class RestConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(RestConsumerApplication.class, args);
}
}
Мы настроим контроллер, используя наш GreetingService
:
@Controller
public class GreetingController {
@Autowired
private GreetingService greetingService;
@GetMapping("/get-greeting/{username}")
public String getGreeting(Model model, @PathVariable("username") String username) {
model.addAttribute("greeting", greetingService.getGreeting(username));
return "greeting-view";
}
}
А вот HTML-шаблон:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Greetings from Hystrix</title>
</head>
<body>
<h2 th:text="${greeting}"/>
</body>
</html>
Чтобы убедиться, что приложение прослушивает определенный порт, мы помещаем в файл application.properties следующее:
server.port=8080
Чтобы увидеть автоматический выключатель Hystix в действии, мы запускаем нашего потребителя и указываем в браузере http://localhost:8080/get-greeting/Cid
. В обычных условиях будет показано следующее:
Hello Cid!
Чтобы имитировать сбой нашего производителя, мы просто остановим его, и после того, как мы закончим обновление браузера, мы должны увидеть общее сообщение, возвращенное резервным методом в нашем @Service
:
Hello User!
4. REST Consumer с Hystrix и Feign
Теперь мы собираемся изменить проект из предыдущего шага, чтобы использовать Spring Netflix Feign в качестве декларативного клиента REST вместо Spring RestTemplate
.
Преимущество заключается в том, что позже мы можем легко реорганизовать наш интерфейс Feign Client, чтобы использовать Spring Netflix Eureka для обнаружения сервисов.
Чтобы начать новый проект, мы сделаем копию нашего потребителя и добавим нашего производителя и spring-cloud-starter-feign
в качестве зависимостей:
<dependency>
<groupId>com.foreach.spring.cloud</groupId>
<artifactId>spring-cloud-hystrix-rest-producer</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
<version>1.1.5.RELEASE</version>
</dependency>
Теперь мы можем использовать наш GreetingController
для расширения Feign Client. Мы реализуем резервный вариант Hystrix
как статический внутренний класс с аннотацией @Component
.
В качестве альтернативы мы могли бы определить аннотированный метод @Bean
, возвращающий экземпляр этого резервного класса.
Свойство name @FeignClient
является обязательным. Он используется для поиска приложения либо путем обнаружения службы через клиент Eureka, либо по URL-адресу, если задано это свойство:
@FeignClient(
name = "rest-producer"
url = "http://localhost:9090",
fallback = GreetingClient.GreetingClientFallback.class
)
public interface GreetingClient extends GreetingController {
@Component
public static class GreetingClientFallback implements GreetingController {
@Override
public String greeting(@PathVariable("username") String username) {
return "Hello User!";
}
}
}
Подробнее об использовании Spring Netflix Eureka для обнаружения сервисов читайте в этой статье .
В RestConsumerFeignApplication
добавим дополнительную аннотацию для включения интеграции Feign, фактически @EnableFeignClients
, к основному классу приложения:
@SpringBootApplication
@EnableCircuitBreaker
@EnableFeignClients
public class RestConsumerFeignApplication {
public static void main(String[] args) {
SpringApplication.run(RestConsumerFeignApplication.class, args);
}
}
Мы собираемся модифицировать контроллер, чтобы он использовал автоматически подключаемый Feign Client вместо ранее введенного @Service
для получения нашего приветствия:
@Controller
public class GreetingController {
@Autowired
private GreetingClient greetingClient;
@GetMapping("/get-greeting/{username}")
public String getGreeting(Model model, @PathVariable("username") String username) {
model.addAttribute("greeting", greetingClient.greeting(username));
return "greeting-view";
}
}
Чтобы отличить этот пример от предыдущего, мы изменим порт прослушивания приложения в application.properties
:
server.port=8082
Наконец, мы протестируем этого потребителя с поддержкой Feign, как и в предыдущем разделе. Ожидаемый результат должен быть таким же.
5. Резервный кэш с помощью Hystrix
Теперь мы собираемся добавить Hystrix в наш проект Spring Cloud . В этом облачном проекте у нас есть рейтинговый сервис, который общается с базой данных и получает оценки книг.
Предположим, что наша база данных является востребованным ресурсом, и ее задержка ответа может меняться во времени или может быть недоступна в разы. Мы справимся с этим сценарием, когда Hystrix Circuit Breaker использует кэш для данных.
5.1. Установка и конфигурация
Давайте добавим зависимость spring-cloud-starter-hystrix
в наш модуль рейтинга:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
Когда рейтинги вставляются/обновляются/удаляются в базе данных, мы реплицируем их в кеш Redis с помощью репозитория
. Чтобы узнать больше о Redis, ознакомьтесь с этой статьей.
Давайте обновим RatingService
, чтобы обернуть методы запросов к базе данных в команду Hystrix с помощью @HystrixCommand
и настроить его с резервным чтением из Redis:
@HystrixCommand(
commandKey = "ratingsByIdFromDB",
fallbackMethod = "findCachedRatingById",
ignoreExceptions = { RatingNotFoundException.class })
public Rating findRatingById(Long ratingId) {
return Optional.ofNullable(ratingRepository.findOne(ratingId))
.orElseThrow(() ->
new RatingNotFoundException("Rating not found. ID: " + ratingId));
}
public Rating findCachedRatingById(Long ratingId) {
return cacheRepository.findCachedRatingById(ratingId);
}
Обратите внимание, что резервный метод должен иметь ту же сигнатуру, что и обернутый метод, и должен находиться в том же классе. Теперь, когда findRatingById
терпит неудачу или задерживается больше заданного порога, Hystrix откатывается к findCachedRatingById.
Поскольку возможности Hystrix прозрачно внедряются в виде рекомендаций АОП, нам необходимо настроить порядок, в котором рекомендации складываются, на случай, если у нас есть другие рекомендации, такие как рекомендации Spring по транзакциям. Здесь мы скорректировали рекомендацию Spring AOP по транзакциям, чтобы она имела более низкий приоритет, чем рекомендация Hystrix AOP:
@EnableHystrix
@EnableTransactionManagement(
order=Ordered.LOWEST_PRECEDENCE,
mode=AdviceMode.ASPECTJ)
public class RatingServiceApplication {
@Bean
@Primary
@Order(value=Ordered.HIGHEST_PRECEDENCE)
public HystrixCommandAspect hystrixAspect() {
return new HystrixCommandAspect();
}
// other beans, configurations
}
Здесь мы скорректировали рекомендацию Spring AOP по транзакциям, чтобы она имела более низкий приоритет, чем рекомендация Hystrix AOP.
5.2. Тестирование Hystrix Fallback
Теперь, когда мы настроили схему, мы можем протестировать ее, отключив базу данных H2, с которой взаимодействует наш репозиторий. Но сначала давайте запустим экземпляр H2 как внешний процесс, а не как встроенную базу данных.
Скопируем библиотеку H2 ( h2-1.4.193.jar
) в известную директорию и запустим сервер H2:
>java -cp h2-1.4.193.jar org.h2.tools.Server -tcp
TCP server running at tcp://192.168.99.1:9092 (only local connections)
Давайте теперь обновим URL-адрес источника данных нашего модуля в rating-service.properties
, чтобы он указывал на этот сервер H2:
spring.datasource.url = jdbc:h2:tcp://localhost/~/ratings
Мы можем запустить наши службы, как указано в нашей предыдущей статье из серии Spring Cloud, и протестировать рейтинги каждой книги, отключив внешний экземпляр H2, который мы запускаем.
Мы могли видеть, что, когда база данных H2 недоступна, Hystrix автоматически возвращается к Redis, чтобы прочитать рейтинги для каждой книги. Исходный код, демонстрирующий этот вариант использования, можно найти здесь .
6. Использование областей
Обычно аннотированный метод @HytrixCommand
выполняется в контексте пула потоков. Но иногда его нужно запускать в локальной области, например @SessionScope
или @RequestScope
. Это можно сделать, указав аргументы в аннотации команды:
@HystrixCommand(fallbackMethod = "getSomeDefault", commandProperties = {
@HystrixProperty(name = "execution.isolation.strategy", value = "SEMAPHORE")
})
7. Панель инструментов Hystrix
Приятной дополнительной функцией Hystrix является возможность отслеживать его статус на панели инструментов.
Чтобы включить его, мы поместим spring-cloud-starter-hystrix-dashboard
и spring-boot-starter-actuator
в pom.xml
нашего потребителя:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
<version>1.4.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<version>2.2.6.RELEASE</version>
</dependency>
Первый необходимо включить с помощью аннотации @Configuration
с помощью @EnableHystrixDashboard,
а последний автоматически включает необходимые показатели в нашем веб-приложении.
После перезапуска приложения мы укажем в браузере http://localhost:8080/hystrix
, введем URL-адрес метрик потока Hystrix и начнем мониторинг.
В итоге мы должны увидеть что-то вроде этого:
Мониторинг потока Hystrix — это хорошо, но если нам нужно смотреть несколько приложений с поддержкой Hystrix, это станет неудобным. Для этой цели Spring Cloud предоставляет инструмент под названием Turbine, который может объединять потоки для представления на одной панели инструментов Hystrix.
Настройка Turbine выходит за рамки этой статьи, но здесь следует упомянуть эту возможность. Таким образом, также возможно собирать эти потоки через обмен сообщениями, используя поток Turbine.
8. Заключение
Как мы уже видели, теперь мы можем реализовать шаблон прерывателя цепи, используя Spring Netflix Hystrix вместе с Spring RestTemplate
или Spring Netflix Feign.
Это означает, что мы можем использовать службы с включенным резервным вариантом, используя данные по умолчанию, и мы можем отслеживать использование этих данных.
Как обычно, исходники мы можем найти на GitHub .