Введение в 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 предназначен для:
-
Обеспечить защиту и контроль от сбоев и задержек от служб, к которым обычно обращаются по сети
-
Прекратить каскадирование сбоев, связанных с отключением некоторых служб
-
Быстро проваливаются и быстро восстанавливаются
-
Изящно деградировать, где это возможно
-
Мониторинг в режиме реального времени и оповещение командного центра о сбоях
В следующем посте мы увидим, как объединить преимущества Hystrix с платформой Spring.
Полный код проекта и все примеры можно найти наgithub project.