Перейти к основному содержимому

Введение в Хайстрикс

· 7 мин. чтения

Задача: Сумма двух чисел

Напишите функцию twoSum. Которая получает массив целых чисел nums и целую сумму target, а возвращает индексы двух чисел, сумма которых равна target. Любой набор входных данных имеет ровно одно решение, и вы не можете использовать один и тот же элемент дважды. Ответ можно возвращать в любом порядке...

ANDROMEDA

1. Обзор

Типичная распределенная система состоит из множества взаимодействующих между собой служб.

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

Конечно, существуют доступные решения, которые помогают сделать приложения отказоустойчивыми и отказоустойчивыми — одной из таких сред является Hystrix.

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

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

2. Простой пример

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

В этом простом примере мы заключаем вызов в метод run() HystrixCommand :

class CommandHelloWorld extends HystrixCommand<String> {

private String name;

CommandHelloWorld(String name) {
super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));
this.name = name;
}

@Override
protected String run() {
return "Hello " + name + "!";
}
}

и мы выполняем вызов следующим образом:

@Test
public void givenInputBobAndDefaultSettings_whenCommandExecuted_thenReturnHelloBob(){
assertThat(new CommandHelloWorld("Bob").execute(), equalTo("Hello Bob!"));
}

3. Настройка Мавена

Чтобы использовать Hystrix в проектах Maven, нам нужно иметь зависимость hystrix-core и rxjava-core от Netflix в проекте pom.xml :

<dependency>
<groupId>com.netflix.hystrix</groupId>
<artifactId>hystrix-core</artifactId>
<version>1.5.4</version>
</dependency>

Последнюю версию всегда можно найти здесь .

<dependency>
<groupId>com.netflix.rxjava</groupId>
<artifactId>rxjava-core</artifactId>
<version>0.20.7</version>
</dependency>

Последнюю версию этой библиотеки всегда можно найти здесь .

4. Настройка удаленного обслуживания

Давайте начнем с моделирования примера из реального мира.

В приведенном ниже примере класс RemoteServiceTestSimulator представляет службу на удаленном сервере. У него есть метод, который отвечает сообщением через заданный период времени. Мы можем представить, что это ожидание является симуляцией трудоемкого процесса в удаленной системе, что приводит к задержке ответа вызывающей службе:

class RemoteServiceTestSimulator {

private long wait;

RemoteServiceTestSimulator(long wait) throws InterruptedException {
this.wait = wait;
}

String execute() throws InterruptedException {
Thread.sleep(wait);
return "Success";
}
}

А вот наш образец клиента , который вызывает RemoteServiceTestSimulator .

Вызов службы изолирован и помещен в метод run() HystrixCommand. Именно эта упаковка обеспечивает устойчивость, о которой мы говорили выше:

class RemoteServiceTestCommand extends HystrixCommand<String> {

private RemoteServiceTestSimulator remoteService;

RemoteServiceTestCommand(Setter config, RemoteServiceTestSimulator remoteService) {
super(config);
this.remoteService = remoteService;
}

@Override
protected String run() throws Exception {
return remoteService.execute();
}
}

Вызов выполняется путем вызова метода execute() для экземпляра объекта RemoteServiceTestCommand .

Следующий тест демонстрирует, как это делается:

@Test
public void givenSvcTimeoutOf100AndDefaultSettings_whenRemoteSvcExecuted_thenReturnSuccess()
throws InterruptedException {

HystrixCommand.Setter config = HystrixCommand
.Setter
.withGroupKey(HystrixCommandGroupKey.Factory.asKey("RemoteServiceGroup2"));

assertThat(new RemoteServiceTestCommand(config, new RemoteServiceTestSimulator(100)).execute(),
equalTo("Success"));
}

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

5. Работа с удаленным сервисом и защитным программированием

5.1. Защитное программирование с тайм-аутом

Общей практикой программирования является установка тайм-аутов для вызовов удаленных служб.

Давайте начнем с того, как установить тайм-аут в HystrixCommand и как это помогает при коротком замыкании:

@Test
public void givenSvcTimeoutOf5000AndExecTimeoutOf10000_whenRemoteSvcExecuted_thenReturnSuccess()
throws InterruptedException {

HystrixCommand.Setter config = HystrixCommand
.Setter
.withGroupKey(HystrixCommandGroupKey.Factory.asKey("RemoteServiceGroupTest4"));

HystrixCommandProperties.Setter commandProperties = HystrixCommandProperties.Setter();
commandProperties.withExecutionTimeoutInMilliseconds(10_000);
config.andCommandPropertiesDefaults(commandProperties);

assertThat(new RemoteServiceTestCommand(config, new RemoteServiceTestSimulator(500)).execute(),
equalTo("Success"));
}

В приведенном выше тесте мы задержали ответ службы, установив тайм-аут на 500 мс. Мы также устанавливаем время ожидания выполнения в HystrixCommand равным 10 000 мс, что дает достаточно времени для ответа удаленной службы.

Теперь давайте посмотрим, что происходит, когда время ожидания выполнения меньше, чем время ожидания вызова службы:

@Test(expected = HystrixRuntimeException.class)
public void givenSvcTimeoutOf15000AndExecTimeoutOf5000_whenRemoteSvcExecuted_thenExpectHre()
throws InterruptedException {

HystrixCommand.Setter config = HystrixCommand
.Setter
.withGroupKey(HystrixCommandGroupKey.Factory.asKey("RemoteServiceGroupTest5"));

HystrixCommandProperties.Setter commandProperties = HystrixCommandProperties.Setter();
commandProperties.withExecutionTimeoutInMilliseconds(5_000);
config.andCommandPropertiesDefaults(commandProperties);

new RemoteServiceTestCommand(config, new RemoteServiceTestSimulator(15_000)).execute();
}

Обратите внимание, как мы понизили планку и установили время ожидания выполнения на 5000 мс.

Мы ожидаем, что служба ответит в течение 5 000 мс, тогда как мы настроили службу на ответ через 15 000 мс. Если вы заметите, что при выполнении теста тест завершится через 5000 мс вместо ожидания 15 000 мс и вызовет исключение HystrixRuntimeException.

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

В следующих разделах мы рассмотрим настройку размера пула потоков, который предотвращает исчерпание потоков, и обсудим его преимущества.

5.2. Защитное программирование с ограниченным пулом потоков

Установка тайм-аутов для вызова службы не решает всех проблем, связанных с удаленными службами.

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

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

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

Давайте посмотрим, как установить размер пула потоков в HystrixCommand :

@Test
public void givenSvcTimeoutOf500AndExecTimeoutOf10000AndThreadPool_whenRemoteSvcExecuted
_thenReturnSuccess() throws InterruptedException {

HystrixCommand.Setter config = HystrixCommand
.Setter
.withGroupKey(HystrixCommandGroupKey.Factory.asKey("RemoteServiceGroupThreadPool"));

HystrixCommandProperties.Setter commandProperties = HystrixCommandProperties.Setter();
commandProperties.withExecutionTimeoutInMilliseconds(10_000);
config.andCommandPropertiesDefaults(commandProperties);
config.andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter()
.withMaxQueueSize(10)
.withCoreSize(3)
.withQueueSizeRejectionThreshold(10));

assertThat(new RemoteServiceTestCommand(config, new RemoteServiceTestSimulator(500)).execute(),
equalTo("Success"));
}

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

Размер ядра — это количество потоков, которые всегда остаются активными в пуле потоков.

5.3. Защитное программирование с помощью шаблона короткого замыкания

Тем не менее, есть еще улучшения, которые мы можем внести в удаленные сервисные вызовы.

Давайте рассмотрим случай, когда удаленный сервис начал давать сбой.

Мы не хотим продолжать посылать ему запросы и тратить ресурсы. В идеале мы хотели бы прекратить делать запросы на определенное время, чтобы дать службе время восстановиться, прежде чем возобновлять запросы. Это так называемый паттерн короткого замыкания .

Давайте посмотрим, как Hystrix реализует этот шаблон:

@Test
public void givenCircuitBreakerSetup_whenRemoteSvcCmdExecuted_thenReturnSuccess()
throws InterruptedException {

HystrixCommand.Setter config = HystrixCommand
.Setter
.withGroupKey(HystrixCommandGroupKey.Factory.asKey("RemoteServiceGroupCircuitBreaker"));

HystrixCommandProperties.Setter properties = HystrixCommandProperties.Setter();
properties.withExecutionTimeoutInMilliseconds(1000);
properties.withCircuitBreakerSleepWindowInMilliseconds(4000);
properties.withExecutionIsolationStrategy
(HystrixCommandProperties.ExecutionIsolationStrategy.THREAD);
properties.withCircuitBreakerEnabled(true);
properties.withCircuitBreakerRequestVolumeThreshold(1);

config.andCommandPropertiesDefaults(properties);
config.andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter()
.withMaxQueueSize(1)
.withCoreSize(1)
.withQueueSizeRejectionThreshold(1));

assertThat(this.invokeRemoteService(config, 10_000), equalTo(null));
assertThat(this.invokeRemoteService(config, 10_000), equalTo(null));
assertThat(this.invokeRemoteService(config, 10_000), equalTo(null));

Thread.sleep(5000);

assertThat(new RemoteServiceTestCommand(config, new RemoteServiceTestSimulator(500)).execute(),
equalTo("Success"));

assertThat(new RemoteServiceTestCommand(config, new RemoteServiceTestSimulator(500)).execute(),
equalTo("Success"));

assertThat(new RemoteServiceTestCommand(config, new RemoteServiceTestSimulator(500)).execute(),
equalTo("Success"));
}
public String invokeRemoteService(HystrixCommand.Setter config, int timeout)
throws InterruptedException {

String response = null;

try {
response = new RemoteServiceTestCommand(config,
new RemoteServiceTestSimulator(timeout)).execute();
} catch (HystrixRuntimeException ex) {
System.out.println("ex = " + ex);
}

return response;
}

В приведенном выше тесте мы установили различные свойства автоматического выключателя. Наиболее важные из них:

  • CircuitBreakerSleepWindow , для которого установлено значение 4000 мс. Это настраивает окно прерывателя цепи и определяет временной интервал, по истечении которого запрос к удаленному сервису будет возобновлен.
  • CircuitBreakerRequestVolumeThreshold , который имеет значение 1 и определяет минимальное количество запросов, необходимых для того, чтобы частота отказов была учтена .

С указанными выше настройками наша команда HystrixCommand теперь будет открываться после двух неудачных запросов. Третий запрос даже не попадет в удаленную службу, несмотря на то, что мы установили задержку службы на 500 мс, Hystrix замкнется , и наш метод вернет null в качестве ответа.

Впоследствии мы добавим Thread.sleep(5000) , чтобы выйти за пределы установленного нами окна сна. Это приведет к тому, что Hystrix закроет цепь, и последующие запросы будут проходить успешно.

6. Заключение

Таким образом, Hystrix предназначен для:

  1. Обеспечьте защиту и контроль над сбоями и задержками от служб, обычно доступных по сети.
  2. Остановить каскадирование сбоев, возникающих из-за неработоспособности некоторых служб.
  3. Быстро ошибайтесь и быстро восстанавливайтесь
  4. Деградируйте изящно, где это возможно
  5. Мониторинг в режиме реального времени и оповещение командного центра о сбоях

В следующем посте мы увидим, как объединить преимущества Hystrix с фреймворком Spring.

Полный код проекта и все примеры можно найти в проекте github .