Я не могу придумать лучшего способа описать конкретную функцию библиотеки Netflix Hystrix, чем цитируя ее домашнюю страницу:
Задержка и отказоустойчивость:
прекратите каскадные сбои. Резервы и грациозная деградация. Сбой быстрого и быстрого выздоровления.
Резьба и семафорная изоляция с выключателями.
Я видел пример, продемонстрированный Джошом Лонгом ( @starbuxman ), который использует Hystrix, интегрированную со Spring — конкретный код здесь . В примере используются аннотации для включения в сервисный класс Hystrix.
Моя цель здесь — воссоздать аналогичную настройку в режиме меньшего модульного тестирования. Имея это в виду, рассмотрим следующий интерфейс, который станет отказоустойчивым с помощью библиотеки Hystrix:
package hystrixtest;
public interface RemoteCallService {
String call(String request) throws Exception;
}
И фиктивная реализация для этого. Фиктивная реализация делегирует фиктивную реализацию, которая, в свою очередь, дает сбой первые два раза, когда она вызывается, и успешно завершается с третьим вызовом:
package hystrixtest;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import static org.mockito.Mockito.*;
public class DummyRemoteCallService implements RemoteCallService {
private RemoteCallService mockedDelegate;
public DummyRemoteCallService() {
try {
mockedDelegate = mock(RemoteCallService.class);
when(mockedDelegate.call(anyString()))
.thenThrow(new RuntimeException("Deliberately throwing an exception 1"))
.thenThrow(new RuntimeException("Deliberately throwing an exception 2"))
.thenAnswer(new Answer<String>() {
@Override
public String answer(InvocationOnMock invocationOnMock) throws Throwable {
return (String) invocationOnMock.getArguments()[0];
}
});
}catch(Exception e) {
throw new IllegalStateException(e);
}
}
@Override
@HystrixCommand(fallbackMethod = "fallBackCall")
public String call(String request) throws Exception {
return this.mockedDelegate.call(request);
}
public String fallBackCall(String request) {
return "FALLBACK: " + request;
}
}
Удаленный вызов был аннотирован аннотацией @Hystrixcommand с базовой конфигурацией для возврата к методу «fallBackCall» в случае неудачного удаленного вызова.
Теперь, как вы можете себе представить, в библиотеке Hystrix должно быть что-то, что должно перехватывать вызовы с аннотацией @HystrixCommand и делать ее отказоустойчивой. Это рабочий тест, который объединяет необходимую инфраструктуру — по сути, библиотека Hystrix предоставляет сопутствующую библиотеку на основе AOP, которая перехватывает вызовы. Здесь я использовал поддержку тестирования Spring для начальной загрузки инфраструктуры AOP, для создания HystrixCommandAspect в качестве bean-компонента, вызов переходит к «fallBackCall» для первых двух неудачных вызовов и выполняется в третий раз:
package hystrixtest;
import com.netflix.hystrix.contrib.javanica.aop.aspectj.HystrixCommandAspect;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class TestRemoteCallServiceHystrix {
@Autowired
private RemoteCallService remoteCallService ;
@Test
public void testRemoteCall() throws Exception{
assertThat(this.remoteCallService.call("test"), is("FALLBACK: test"));
assertThat(this.remoteCallService.call("test"), is("FALLBACK: test"));
assertThat(this.remoteCallService.call("test"), is("test"));
}
@Configuration
@EnableAspectJAutoProxy
public static class SpringConfig {
@Bean
public HystrixCommandAspect hystrixCommandAspect() {
return new HystrixCommandAspect();
}
@Bean
public RemoteCallService remoteCallService() {
return new DummyRemoteCallService();
}
}
}
Spring-Cloud предоставляет более простой способ настройки библиотек Netflix для проектов, основанных на Spring-Boot, и если бы я использовал эту библиотеку, тест трансформировался бы в это, теперь куча конфигурации закомментирована с помощью Spring-Boot:
package hystrixtest;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration
public class TestRemoteCallServiceHystrix {
@Autowired
private RemoteCallService remoteCallService;
@Test
public void testRemoteCall() throws Exception {
assertThat(this.remoteCallService.call("test"), is("FALLBACK: test"));
assertThat(this.remoteCallService.call("test"), is("FALLBACK: test"));
assertThat(this.remoteCallService.call("test"), is("test"));
}
@Configuration
@EnableAutoConfiguration
// @EnableAspectJAutoProxy
@EnableHystrix
public static class SpringConfig {
// @Bean
// public HystrixCommandAspect hystrixCommandAspect() {
// return new HystrixCommandAspect();
// }
@Bean
public RemoteCallService remoteCallService() {
return new DummyRemoteCallService();
}
}
}
Если вы заинтересованы в изучении этого примера дальше,
здесь есть
GitHub репо с рабочими испытаниями.