Spring Cloud Netflix: Hystrix по-русски + Feign Client

Kirill Sereda
7 min readSep 2, 2019

--

Привет.

Я хочу продолжить серию из предыдущих статей по Netflix, а именно поговорить про Hystrix.

На русском языке очень мало информации по стеку Netflix.

И правильно, потому что надо читать официальную документацию, там вы найдете больше всего. Но я все же решил рассказать “своими словами” чтобы сложилась более ясная картина при изучении данного стека для тех, кто предпочитает русскоязычную литературу.

Планирую обновлять статьи по мере изучения нового материала. Если у вас есть предложения на этот счет, буду рад выслушать.

Hystrix - библиотека задержек и отказоустойчивости, которая помогает контролировать взаимодействие между службами, обеспечивая отказоустойчивость и устойчивость к задержкам, благодаря чему повышается устойчивость всей системы в целом.

Другими словами можно сказать, что Hystrix — это имплементация паттерна Circuit Breaker.

Основная идея состоит в том, чтобы остановить каскадный отказ в распределенной системе сервисов, состоящей из их большого числа. Это позволяет отдавать ошибку на раннем этапе и давая возможность "упавшему"" сервису восстановить свою работоспособность.

Hystrix позволяет определить fallback-метод, который будет вызван при неуспешном вызове. Что будет в этом fallback-методе уже решать вам.

Более подробно вы можете прочитать в моей статье, посвященной паттерну Circuit Breaker:

Circuit Breaker pattern link

Если вы используете Hystrix вместе с Feign Client, то чтобы это заработало необходимо в настройках указать

feign:
hystrix:
enabled: true

Но Feign Client по умолчанию интегрирован с Hystrix, поэтому использовать Hystrix явно вовсе не обязательно, если вы используете Feign.

В этой статье рассмотрим Hystrix совместно с Feign Client.

Пример 1:

У нас есть 2 сервиса:

  • Hystrix - микросервис на основе REST, в который внедрен Circuit Breaker.

https://github.com/ksereda/Spring_Cloud_Netflix_Microservices/tree/master/hystrix

  • service_user-details - получает какую-либо информацию по пользователю (к какой группе он принадлежит).

Из сервиса Hystrix мы будем вызывать сервис service_user-details.

Описываем service_user-details:

Указываем порт в application.properties:

server.port=8077

Создаем контроллер UsersServiceController, в котором по URL "/getUsersDetailsByGroup/{group}" будем получать какую-либо информацию по пользователю по группе, к которой он принадлежит. (смотри сервис service_user-details)

@RestController
public class UsersServiceController {

private static Map
<String, List<Users>> map = new HashMap<>();

static {
map
= new HashMap<>();

List<Users> list = new ArrayList();
Users users = new Users("John Wick", 40);
list.add(users);
users = new Users("Nikolas Cage", 42);
list.add(users);

map.put("coolman", list);

list = new ArrayList();
users = new Users("Sylvester Stallone", 43);
list.add(users);
users = new Users("Chuck Norris", 41);
list.add(users);

map.put("badboy", list);

}

@RequestMapping(
value = "/getUsersDetailsByGroup/{group}", method = RequestMethod.GET)
public List
<Users> getUsers(@PathVariable String group) {
List
<Users> usersList = map.get(group);

if (usersList == null || usersList.isEmpty()) {
usersList = new ArrayList<>();
Users users = new Users("Users not found", null);
usersList.add(users);
}

return
usersList;
}

}

модель:

public class Users {

private String name
;
private Integer age;

public Users(String name, Integer age) {
this
.name = name;
this.age = age;
}

public String getName() {
return name
;
}

public void setName(String
name) {
this
.name = name;
}

public Integer getAge() {
return age
;
}

public void setAge(Integer
age) {
this
.age = age;
}
}

После запуска сервиса по URL http://localhost:8077/getUsersDetailsByGroup/coolman

вы увидите данные по этим крутым парням.

Сервис Hystrix:

Указываем порт:

server.port=8076

Над основным классом необходимо указать аннотации @EnableCircuitBreaker и @EnableHystrixDashboard

@SpringBootApplication
@EnableHystrixDashboard
@EnableCircuitBreaker
public class HystrixApplication {
public static void main(String[] args) {
SpringApplication.run(HystrixApplication.class, args);
}
}

Делаем контроллер, в котором вызываем

/getGroupDetails/{group}

и получаем информацию о группе вместе с данными о пользователе Для вызова информации о пользователе будет вызван уже написанный метод в контроллере в сервисе service_user-details.

@RestController
public class HystrixController {

@Autowired
UserService userservice
;

@RequestMapping(value = "/getGroupDetails/{group}", method = RequestMethod.GET)
public String getUsers(@PathVariable String
group) {
return userservice
.callUserService(group);
}

}

Для этого напишем в этом пакете сервис UserService

@Service
public class UserService {

@Autowired
RestTemplate restTemplate
;

@HystrixCommand(fallbackMethod = "callUserService_Fallback")
public String callUserService(String
group) {
String
response = restTemplate
.exchange("http://localhost:8077/getUsersDetailsByGroup/{group}"
, HttpMethod.GET
, null
, new ParameterizedTypeReference<String>() {
}
, group).getBody();

return "It's OK: group: " + group + " users details " + response + new Date();
}

@SuppressWarnings("unused")
private String callUserService_Fallback(String
group) {
return "Error! "
+ new Date();
}

@Bean
public RestTemplate restTemplate() {
return new RestTemplate()
;
}

}
  • Здесь используем RestTemplate (для разнообразия. В следующем примере попробуем использовать Feign Client)
  • Используем аннотацию @HystrixCommand с параметрами дополнительного метода, который будет вызван в случае, если service_user-details будет недоступен
@HystrixCommand(fallbackMethod = "callUserService_Fallback")

У этого метода должна быть одинаковая сигнатура с методом callUserService (получения группы и данных пользователей)

Теперь запустите оба сервиса. Попробуйте из сервиса Hystrix вызвать сервис service_user-details

http://localhost:8076/getGroupDetails/badboy

Вы увидите Sylvester Stallone и Chuck Norris.

Симулируйте отказ сервиса service_user-details (проблемы в сети, перегруженность сервиса и т.д.) и отключите его Попробуйте снова обратиться по URL

http://localhost:8076/getGroupDetails/badboy

Отработает запасной метод callUserService_Fallback

Error! Default info...

Вывод текста я привел в качестве простого и наглядного примера.
Вместо надписи типа Error вы можете придумать что вам угодно, вплоть до получения из базы каких-то кастомных данных и предоставления их пользователю или перенаправления на другой сервис (реплика сервиса service_user-details) в случае если у вас система по потоковому вещанию, где залержка вплоть до пары секунд очень многое значит для поставщика услуг.

Это был лишь один из простых примеров, как можно использовать Hystrix.

Пример 2

service_user-details остается аналогичным.

Hystrix сервис:

Основной класс

@SpringBootApplication
@EnableHystrixDashboard
@EnableCircuitBreaker
@EnableFeignClients
public class HystrixApplication {

public static void main(String[]
args) {
SpringApplication
.run(HystrixApplication.class, args);
}

}

Делаем интерфейс ServiceFeignClient

В аннотации @FeignClient указываем Fallback класс, который будет вызван в случае неудачной попытки соединения с удаленным сервисом `service_statistics`@FeignClient(name = "service_statistics", fallback = UsersFallback.class)
public interface ServiceFeignClient {

@GetMapping("/users/{id}/statistics")
public List
<UserModel> getStatistics(@PathVariable String id);

}

Модель

public class UserModel {

private Long id
;
private String username;
private String personalUserNumber;
private String title;
private Long postNumber;

public Long getId() {
return id
;
}

public void setId(Long
id) {
this
.id = id;
}

public String getUsername() {
return username
;
}

public void setUsername(String
username) {
this
.username = username;
}

public String getPersonalUserNumber() {
return personalUserNumber
;
}

public void setPersonalUserNumber(String
personalUserNumber) {
this
.personalUserNumber = personalUserNumber;
}

public String getTitle() {
return title
;
}

public void setTitle(String
title) {
this
.title = title;
}

public Long getPostNumber() {
return postNumber
;
}

public void setPostNumber(Long
postNumber) {
this
.postNumber = postNumber;
}
}

Фоллбек класс

@Component
public class UsersFallback implements ServiceFeignClient{

@Override
public List
<UserModel> getStatistics(String id) {
return new ArrayList()
;
}

}

Пример 3

service_user-details остается аналогичным.

Hystrix сервис:

Основной класс

@SpringBootApplication
@EnableHystrixDashboard
@EnableCircuitBreaker
@EnableFeignClients
public class HystrixApplication {

public static void main(String[]
args) {
SpringApplication
.run(HystrixApplication.class, args);
}

}

Feign Client

Например наш Feign Client отправляет запрос /users/{id}/statistics на удаленный сервис service_statistics. Чтобы разрешить Feign использовать класс Fallback, который может обрабатывать сообщения об ошибках, нам необходимо указать в аннотации fallbackFactory.

@FeignClient(name = "service_statistics", fallbackFactory = StatisticFallbackFactory.class)
public interface ServiceFeignClient {

@GetMapping("/users/{id}/statistics")
public List
<UserModel> getStatistics(@PathVariable String id);

}

Делаем фабрику

Класс должен быть помечен как бин (@Component) и должен имплементировать интерфейс FallbackFactory (в дженериках наш Feign Client) У нас есть доступ к объекту Throwable, с помощью которого можно получить необходимую информацию об ошибке. Возвращаемое значение - ServiceFeignClientFallback - новый кастомный класс для обработки ошибки.

@Component
public class StatisticFallbackFactory implements FallbackFactory
<ServiceFeignClient> {

@Override
public ServiceFeignClient create(Throwable
cause) {
return new ServiceFeignClientFallback(
cause);
}

}

Пишем ServiceFeignClientFallback

Мы передаем объект ошибки (из класса StatisticFallbackFactory) в наш новый класс ServiceFeignClientFallback, который используется для обработки этой ошибки.

public class ServiceFeignClientFallback implements ServiceFeignClient {

Logger logger
= LoggerFactory.getLogger(this.getClass());
private final Throwable cause;

public ServiceFeignClientFallback(Throwable cause) {
this
.cause = cause;
}

@Override
public List
<UserModel> getStatistics(String id) {
if (cause instanceof FeignException
&& ((FeignException) cause).status() == 404) {
logger
.error("404 page not found" + id
+ "error message: " + cause.getLocalizedMessage());
} else {
logger
.error("Other error took place: " + cause.getLocalizedMessage());
}

return new ArrayList()
;
}

}

Модель

public class UserModel {

private Long id
;
private String username;
private String personalUserNumber;
private String title;
private Long postNumber;

public Long getId() {
return id
;
}

public void setId(Long
id) {
this
.id = id;
}

public String getUsername() {
return username
;
}

public void setUsername(String
username) {
this
.username = username;
}

public String getPersonalUserNumber() {
return personalUserNumber
;
}

public void setPersonalUserNumber(String
personalUserNumber) {
this
.personalUserNumber = personalUserNumber;
}

public String getTitle() {
return title
;
}

public void setTitle(String
title) {
this
.title = title;
}

public Long getPostNumber() {
return postNumber
;
}

public void setPostNumber(Long
postNumber) {
this
.postNumber = postNumber;
}
}

Hystrix Dashboard

Естественно, для того, чтобы понять, что происходит с вашей системой, насколько она эффективна, нужно собирать метрики.

В Hystrix сервис необходимо добавить настройки

management:
endpoints:
web:
exposure:
include: hystrix.stream

Hystrix также предоставляет дашборд по URL

Теперь идем в браузер по нашему сервису Hystrix

localhost:8076/hystrix

Открывается Hystrix Dashboard

Введите туда

localhost:8076/actuator/hystrix.stream

любое значение (скажем 2000) и имя (произвольное).

Теперь попробуйте получить данные у service_user-details

Посмотрите в Hystrix Dashboard — вы увидите изменяющееся значение в режиме реального времени. Выполните запрос несколько раз.

Теперь выключите service_user-details и попробуйте выполнить этот же запрос еще несколько раз подряд.

Обратите внимание что в Hystrix Dashboard теперь будут отображаться неудачные попытки (красные цифры).

Я привел простой пример работы Hystrix в случае недоступности удаленного сервиса. На самом деле при помощи Hystrix можно выполнять разные невероятные вещи. Можно очень гибко настроить вашу систему, если вам очень важен перфоманс и устойчивость к ошибкам.

Надеюсь данная статья была полезна.

Посмотрите мой доклад на тему Netflix OSS:

https://github.com/ksereda/Gallery-Service

Если вы нашли неточности в описании данной статьи, вы можете написать мне на email и я с радостью вам отвечу.

Kirill Sereda

email: kirill.serada@gmail.com

skype: kirill-sereda

linkedin: www.linkedin.com/in/ksereda

--

--