Um guia para o Spring Cloud Netflix - Hystrix
1. Visão geral
Neste tutorial, vamos cobrirSpring Cloud Netflix Hystrix - a biblioteca de tolerância a falhas. Usaremos a biblioteca e implementaremos o padrão empresarialCircuit Breaker, que descreve uma estratégia contra falhas em cascata em diferentes níveis de um aplicativo.
O princípio é análogo à eletrônica:Hystrix está observando métodos para chamadas com falha para serviços relacionados. Se houver tal falha, ele abrirá o circuito e encaminhará a chamada para um método de fallback.
A biblioteca tolerará falhas até um limite. Além disso, deixa o circuito aberto. O que significa que ele encaminhará todas as chamadas subsequentes para o método de fallback, para evitar falhas futuras. This creates a time buffer for the related service to recover from its failing state.
2. Produtor REST
Para criar um cenário, que demonstra o padrãoCircuit Breaker, precisamos primeiro de um serviço. Vamos chamá-lo de ‘REST Produtor’, porque ele fornece dados para ‘REST Consumer’ habilitado para Hystrix - que criaremos na próxima etapa.
Vamos criar um novo projetoMaven usando a dependênciaspring-boot-starter-web:
org.springframework.boot
spring-boot-starter-web
1.4.0.RELEASE
O projeto em si é intencionalmente mantido simples. Consiste em uma interface de controlador com um método GET anotado de@RequestMapping retornando simplesmente umString, a@RestController implementando esta interface e um@SpringBootApplication.
Vamos começar com a interface:
public interface GreetingController {
@GetMapping("/greeting/{username}")
String greeting(@PathVariable("username") String username);
}
E a implementação:
@RestController
public class GreetingControllerImpl implements GreetingController {
@Override
public String greeting(@PathVariable("username") String username) {
return String.format("Hello %s!\n", username);
}
}
Em seguida, escreveremos a classe principal do aplicativo:
@SpringBootApplication
public class RestProducerApplication {
public static void main(String[] args) {
SpringApplication.run(RestProducerApplication.class, args);
}
}
Para completar esta seção, a única coisa que falta fazer é configurar uma porta de aplicativo na qual estaremos escutando. Não usaremos a porta padrão 8080 porque a porta deve permanecer reservada para o aplicativo descrito na próxima etapa.
Além disso, estamos definindo um nome de aplicativo para poder consultar nosso ‘REST Producer’ no aplicativo cliente que apresentaremos mais tarde. Vamos criar umapplication.properties com o seguinte conteúdo:
server.port=9090
spring.application.name=rest-producer
Agora podemos testar nosso ‘REST Producer’ usando curl:
$> curl http://localhost:9090/greeting/Cid
Hello Cid!
3. Consumidor REST comHystrix
Para nosso cenário de demonstração, implementaremos um aplicativo da web, que está consumindo o serviçoREST da etapa anterior usandoRestTemplateeHystrix. Por uma questão de simplicidade, vamos chamá-lo de ‘REST Consumer’.
Consequentemente, criamos um novo projeto Maven comhttps://search.maven.org/classic/#search%7Cgav%7C1%7Cg%3A%22org.springframework.cloud%22%20AND%20a%3A%22spring-cloud-starter-hystrix%22, spring-boot-starter-webe_spring-boot-starter-thymeleaf como dependências:
org.springframework.cloud
spring-cloud-starter-hystrix
1.1.5.RELEASE
org.springframework.boot
spring-boot-starter-web
1.4.0.RELEASE
org.springframework.boot
spring-boot-starter-thymeleaf
1.4.0.RELEASE
Para queCircuit Breaker funcione,Hystix varrerá as classes anotadas de@Component or @Service em busca de métodos anotados de@HystixCommand, implementará um proxy para ele e monitorará suas chamadas.
Vamos criar uma classe@Service primeiro, que será injetada em@Controller. Como estamos construindo umweb-application using Thymeleaf,, também precisamos de um modelo HTML para servir como uma visualização.
Este será nosso@Service injetável implementando um@HystrixCommand com um método de fallback associado. Esse fallback precisa usar a mesma assinatura que o "original":
@Service
public class GreetingService {
@HystrixCommand(fallbackMethod = "defaultGreeting")
public String getGreeting(String username) {
return new RestTemplate()
.getForObject("http://localhost:9090/greeting/{username}",
String.class, username);
}
private String defaultGreeting(String username) {
return "Hello User!";
}
}
RestConsumerApplication será nossa principal classe de aplicativo. A anotação@EnableCircuitBreaker varrerá o caminho de classe para qualquer implementaçãoCircuit Breaker compatível.
Para usarHystrix explicitamente, você deve anotar esta classe com@EnableHystrix:
@SpringBootApplication
@EnableCircuitBreaker
public class RestConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(RestConsumerApplication.class, args);
}
}
Vamos configurar o controlador usando nossoGreetingService:
@Controller
public class GreetingController {
@Autowired
private GreetingService greetingService;
@GetMapping("/get-greeting/{username}")
public String getGreeting(Model model, @PathVariable("username") String username) {
model.addAttribute("greeting", greetingService.getGreeting(username));
return "greeting-view";
}
}
E aqui está o modelo HTML:
Greetings from Hystrix
Para garantir que o aplicativo esteja escutando em uma porta definida, colocamos o seguinte em um arquivoapplication.properties:
server.port=8080
Para ver um disjuntor Hystix em ação, estamos iniciando nosso ‘REST Consumer’ e apontando nosso navegador parahttp://localhost:8080/get-greeting/Cid. Em circunstâncias normais, o seguinte será mostrado:
Hello Cid!
Para simular uma falha de nosso ‘REST Producer’, vamos simplesmente pará-lo e, depois de terminar de atualizar o navegador, devemos ver uma mensagem genérica, retornada do método de fallback em nosso@Service:
Hello User!
4. Consumidor REST comHystrix eFeign
Agora, vamos modificar o projeto da etapa anterior para usarSpring Netflix Feign como cliente REST declarativo, em vez de SpringRestTemplate.
A vantagem é que mais tarde podemos refatorar facilmente nossa interface Feign Client para usarSpring Netflix Eureka para descoberta de serviço.
Para iniciar o novo projeto, faremos uma cópia de nosso ‘REST Consumer’ e adicionaremos ‘Produtor REST’ espring-cloud-starter-feign como dependências:
com.example.spring.cloud
spring-cloud-hystrix-rest-producer
1.0.0-SNAPSHOT
org.springframework.cloud
spring-cloud-starter-feign
1.1.5.RELEASE
Agora, podemos usar nossoGreetingController para estender um cliente Feign. ImplementaremosHystrix fallback como uma classe interna estática anotada com@Component.
Alternativamente, poderíamos definir um método anotado@Bean retornando uma instância desta classe de fallback.
A propriedade name de@FeignClient é obrigatória. É usado para pesquisar o aplicativo por descoberta de serviço por meio deEureka Client ou por meio de URL, se essa propriedade for fornecida. Para mais informações sobre como usar Spring Netflix Eureka para descoberta de serviço, dê uma olhada emat this article:
@FeignClient(
name = "rest-producer"
url = "http://localhost:9090",
fallback = GreetingClient.GreetingClientFallback.class
)
public interface GreetingClient extends GreetingController {
@Component
public static class GreetingClientFallback implements GreetingController {
@Override
public String greeting(@PathVariable("username") String username) {
return "Hello User!";
}
}
}
EmRestConsumerFeignApplication, colocaremos uma anotação adicional para permitir a integração deFeign, na verdade,@EnableFeignClients, para a classe de aplicativo principal:
@SpringBootApplication
@EnableCircuitBreaker
@EnableFeignClients
public class RestConsumerFeignApplication {
public static void main(String[] args) {
SpringApplication.run(RestConsumerFeignApplication.class, args);
}
}
Vamos modificar o controlador para usar um Feign Client com fiação automática, em vez do@Service injetado anteriormente, para recuperar nossa saudação:
@Controller
public class GreetingController {
@Autowired
private GreetingClient greetingClient;
@GetMapping("/get-greeting/{username}")
public String getGreeting(Model model, @PathVariable("username") String username) {
model.addAttribute("greeting", greetingClient.greeting(username));
return "greeting-view";
}
}
Para distinguir este exemplo do anterior, vamos alterar a porta de escuta do aplicativo emapplication.properties:
server.port=8082
Finalmente, testaremos esteFeign habilitado ‘REST Consumer’ como o da seção anterior. O resultado esperado deve ser o mesmo.
5. Fallback de cache comHystrix
Agora, vamos adicionar Hystrix ao nosso projetoSpring Cloud. Neste projeto de nuvem, temos um serviço de classificação que conversa com o banco de dados e obtém classificações de livros.
Suponhamos que nosso banco de dados seja um recurso sob demanda e sua latência de resposta pode variar com o tempo ou pode não estar disponível com o tempo. Vamos lidar com este cenário comHystrixCircuit-Breaker voltando para um cache para os dados.
5.1. Instalação e configuração
Vamos adicionar a dependênciaspring-cloud-starter-hystrix ao nosso módulo de classificação:
org.springframework.cloud
spring-cloud-starter-hystrix
Quando as classificações são inseridas / atualizadas / excluídas no banco de dados, iremos replicar as mesmas para o cache do Redis com umRepository. Se você quiser saber mais sobre Redis, verifiquethis article.
Vamos atualizar oRatingService para envolver os métodos de consulta do banco de dados em um comando Hystrix com@HystrixCommande configurá-lo com um fallback para leitura do Redis:
@HystrixCommand(
commandKey = "ratingsByIdFromDB",
fallbackMethod = "findCachedRatingById",
ignoreExceptions = { RatingNotFoundException.class })
public Rating findRatingById(Long ratingId) {
return Optional.ofNullable(ratingRepository.findOne(ratingId))
.orElseThrow(() ->
new RatingNotFoundException("Rating not found. ID: " + ratingId));
}
public Rating findCachedRatingById(Long ratingId) {
return cacheRepository.findCachedRatingById(ratingId);
}
Observe que o método fallback deve ter a mesma assinatura de um método agrupado e deve residir na mesma classe. Agora, quandofindRatingById falha ou fica atrasado mais do que um determinado limite,Hystrix retrocede parafindCachedRatingById.
Como os recursos deHystrix são injetados de forma transparente como conselhoAOP, temos que ajustar a ordem em que o conselho é empilhado, caso tenhamos outro conselho como o conselho transacional de Spring. Aqui, ajustamos o conselho de AOP de transação do Spring para ter precedência inferior ao conselho deHystrix AOP:
@EnableHystrix
@EnableTransactionManagement(
order=Ordered.LOWEST_PRECEDENCE,
mode=AdviceMode.ASPECTJ)
public class RatingServiceApplication {
@Bean
@Primary
@Order(value=Ordered.HIGHEST_PRECEDENCE)
public HystrixCommandAspect hystrixAspect() {
return new HystrixCommandAspect();
}
// other Beans, Configurations
}
Aqui, ajustamos o conselho deSpring’s transaçãoAOP para ter precedência menor do que o conselho deHystrix AOP.
5.2. Testando o Hystrix Fallback
Agora que configuramos o circuito, podemos testá-lo desativando o banco de dadosH2 com o qual nosso repositório interage. Mas, primeiro, vamos executar a instânciaH2 como processos externos em vez de executá-la como um banco de dados embutido.
Vamos copiar oH2 library (h2-1.4.193.jar) para um diretório conhecido e iniciar o servidor H2:
>java -cp h2-1.4.193.jar org.h2.tools.Server -tcp
TCP server running at tcp://192.168.99.1:9092 (only local connections)
Vamos atualizar o URL da fonte de dados do nosso módulo emrating-service.properties para apontar para este servidor H2:
spring.datasource.url = jdbc:h2:tcp://localhost/~/ratings
Podemos iniciar nossos serviços conforme fornecido em nossoarticle anterior da série Spring-Cloud e testar as classificações de cada livro desativando a instância H2 externa que estamos executando.
Podemos ver que quando o banco de dadosH2 não está acessível,Hystrix volta automaticamente paraRedis para ler as avaliações de cada livro. O código-fonte que demonstra esse caso de uso pode ser encontradohere.
6. Usando escopos
Normalmente, um método anotado@HytrixCommand é executado em um contexto de pool de threads. Mas às vezes ele precisa ser executado em um escopo local, por exemplo, a@SessionScope ou@RequestScope. Isso pode ser feito fornecendo argumentos para a anotação de comando:
@HystrixCommand(fallbackMethod = "getSomeDefault", commandProperties = {
@HystrixProperty(name = "execution.isolation.strategy", value = "SEMAPHORE")
})
7. O painelHystrix
Um bom recurso opcional deHystrix é a capacidade de monitorar seu status em um painel.
Para habilitá-lo, colocaremosspring-cloud-starter-hystrix-dashboard espring-boot-starter-actuator no pom.xml do nosso ‘REST Consumer’:
org.springframework.cloud
spring-cloud-starter-hystrix-dashboard
1.1.5.RELEASE
org.springframework.boot
spring-boot-starter-actuator
1.4.0.RELEASE
O primeiro precisa ser habilitado anotando@Configuration com@EnableHystrixDashboarde o último habilita automaticamente as métricas necessárias em nosso aplicativo da web.
Depois de reiniciar o aplicativo, apontaremos um navegador parahttp://localhost:8080/hystrix, inseriremos o URL das métricas de ‘hystrix.stream’ e iniciaremos o monitoramento.
Finalmente, devemos ver algo como isto:
Monitorar um ‘hystrix.stream’ é algo bom, mas se você tiver que assistir a vários aplicativos habilitados paraHystrix, isso se tornará inconveniente. Para este propósito,Spring Cloud fornece uma ferramenta chamadaTurbine, que pode agregar fluxos para apresentar em umHystrix Dashboard.
ConfigurarTurbine está além do escopo deste artigo, mas a possibilidade deve ser mencionada aqui. Portanto, também é possível coletar esses fluxos por meio de mensagens, usandoTurbine Stream.
8. Conclusão
Como vimos até agora, agora podemos implementar o padrãoCircuit Breaker usandoSpring Netflix Hystrix junto comSpring RestTemplate ouSpring Netflix Feign.
Isso significa que somos capazes de consumir serviços com fallback incluído usando dados "estáticos" ou "padrão" e podemos monitorar o uso desses dados.
Como de costume, você encontrará as fontes emGitHub.