Введение в Hystrix

Введение в Hystrix

1. обзор

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

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

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

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

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

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

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

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

class CommandHelloWorld extends HystrixCommand {

    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. Maven Setup

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


    com.netflix.hystrix
    hystrix-core
    1.5.4

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


    com.netflix.rxjava
    rxjava-core
    0.20.7

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

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

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

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

class RemoteServiceTestSimulator {

    private long wait;

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

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

And here is our sample client, вызывающийRemoteServiceTestSimulator.

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

class RemoteServiceTestCommand extends HystrixCommand {

    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 мс.

Мы ожидаем, что служба ответит в течение 5000 мс, тогда как мы настроили службу для ответа через 15 000 мс. Если вы заметили, когда выполняете тест, тест завершится через 5000 мс вместо ожидания 15000 мс и выдаст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. Защитное программирование с шаблоном выключателя короткого замыкания

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

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

Мы не хотим постоянно запускать запросы и тратить ресурсы. В идеале мы хотели бы прекратить делать запросы на определенное количество времени, чтобы дать сервису время на восстановление, а затем возобновить запросы. Это то, что называется шаблономShort Circuit Breaker.

Давайте посмотрим, как 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 project.