Spring Cloud Netflix: Hystrix по-русски + Feign Client
Привет.
Я хочу продолжить серию из предыдущих статей по Netflix, а именно поговорить про Hystrix.
На русском языке очень мало информации по стеку Netflix.
И правильно, потому что надо читать официальную документацию, там вы найдете больше всего. Но я все же решил рассказать “своими словами” чтобы сложилась более ясная картина при изучении данного стека для тех, кто предпочитает русскоязычную литературу.
Планирую обновлять статьи по мере изучения нового материала. Если у вас есть предложения на этот счет, буду рад выслушать.
Hystrix
- библиотека задержек и отказоустойчивости, которая помогает контролировать взаимодействие между службами, обеспечивая отказоустойчивость и устойчивость к задержкам, благодаря чему повышается устойчивость всей системы в целом.
Другими словами можно сказать, что Hystrix
— это имплементация паттерна Circuit Breaker
.
Основная идея состоит в том, чтобы остановить каскадный отказ в распределенной системе сервисов, состоящей из их большого числа. Это позволяет отдавать ошибку на раннем этапе и давая возможность "упавшему"" сервису восстановить свою работоспособность.
Hystrix позволяет определить fallback-метод, который будет вызван при неуспешном вызове. Что будет в этом fallback-методе уже решать вам.
Более подробно вы можете прочитать в моей статье, посвященной паттерну Circuit Breaker:
Если вы используете 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:
Если вы нашли неточности в описании данной статьи, вы можете написать мне на email и я с радостью вам отвечу.
Kirill Sereda
email: kirill.serada@gmail.com
skype: kirill-sereda
linkedin: www.linkedin.com/in/ksereda