Resiliência de Microsserviços com Spring Cloud Hystrix: Fallback, Isolamento e Circuit Breaker

Em uma arquitetura de microsserviços, as aplicações são divididas em pequenos serviços independentes que se comunicam por meio de chamadas remotas. Como essas chamadas trafegam pela rede, qualquer instabilidade no provedor — latência elevada, indisponibilidade temporária ou falhas transitórias — pode se propagar para o consumidor, acumulando requisições pendentes, esgotando threads e provocando uma falha em cascata no sistema.

Para evitar esse cenário, o Netflix criou o Hystrix, uma biblioteca de tolerância a falhas que adiciona camadas de proteção ao redor das dependências remotas. O Spring Cloud Hystrix integra essa biblioteca ao ecossistema Spring, oferecendo recursos como fallback, isolamento por threads e circuit breaker.

  1. Configuração inicial do projeto

Comece criando um consumidor Spring Boot chamado loja-virtual-gateway. Inclua as dependências do Eureka Client, Ribbon e Hystrix no arquivo pom.xml ou no build.gradle:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>

Na classe principal, habilite o Hystrix com a anotação composta @SpringCloudApplication, que equivale a usar @SpringBootApplication, @EnableDiscoveryClient e @EnableCircuitBreaker simultaneamente:

package com.exemplo.loja;

import org.springframework.boot.SpringApplication;
import org.springframework.cloud.client.SpringCloudApplication;

@SpringCloudApplication
public class LojaVirtualGatewayApplication {

    public static void main(String[] args) {
        SpringApplication.run(LojaVirtualGatewayApplication.class, args);
    }
}

Configure o registro no Eureka e a porta do serviço no arquivo bootstrap.yml:

spring:
  application:
    name: loja-virtual-gateway

server:
  port: 8082

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/
  1. Expondo um RestTemplate balanceado por Ribbon

Registre um RestTemplate anotado com @LoadBalanced para que o Ribbon resolva o destino pelo nome registrado no Eureka:

package com.exemplo.loja.config;

import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class ClienteHttpConfig {

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}
  1. Implementando fallback com @HystrixCommand

O fallback é executado quando a chamada remota excede o tempo limite, lança exceção ou retorna erro. Crie uma classe de serviço que consome o endpoint /produtos de outro microsserviço e define um método alternativo:

package com.exemplo.loja.service;

import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

@Service
public class CatalogoProdutosService {

    private final RestTemplate restTemplate;

    public CatalogoProdutosService(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }

    @HystrixCommand(fallbackMethod = "respostaPadrao")
    public String listarProdutos() {
        return restTemplate.getForObject(
                "http://catalogo-produtos/produtos",
                String.class);
    }

    public String respostaPadrao() {
        return "[{\"id\":0,\"nome\":\"Produto temporariamente indisponível\",\"preco\":0.0}]";
    }
}

Em seguida, exponha o serviço por meio de um controller:

package com.exemplo.loja.web;

import com.exemplo.loja.service.CatalogoProdutosService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class LojaController {

    private final CatalogoProdutosService catalogoService;

    public LojaController(CatalogoProdutosService catalogoService) {
        this.catalogoService = catalogoService;
    }

    @GetMapping("/loja/produtos")
    public String produtos() {
        return catalogoService.listarProdutos();
    }
}
  1. Simulando uma falha para testar o fallback

No microsserviço provedor catalogo-produtos, adicione uma latência artificial no endpoint para forçar o timeout do Hystrix:

package com.exemplo.catalogo.web;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Collections;
import java.util.Map;

@RestController
public class ProdutosController {

    @GetMapping("/produtos")
    public Map<String, Object> listar() throws InterruptedException {
        Thread.sleep(6000);
        return Collections.singletonMap("produtos", "lista completa");
    }
}

Com o timeout padrão de um segundo, a requisição para http://localhost:8082/loja/produtos falhará no consumidor e o Hystrix retornará o JSON padrão definido no método respostaPadrao, preservando a experiência do cliente mesmo com o catálogo lento.

  1. Isolamento de dependências

Quando um método é anotado com @HystrixCommand, o Hystrix envolve a chamada em um comando independente, alocando um conjunto separado de threads para cada dependência externa. Isso impede que uma dependência lenta consuma todos os recursos do consumidor.

É possível personalizar o tamanho do pool de threads e o timeout do comando:

@HystrixCommand(
    fallbackMethod = "respostaPadrao",
    commandProperties = {
        @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1500")
    },
    threadPoolProperties = {
        @HystrixProperty(name = "coreSize", value = "8"),
        @HystrixProperty(name = "maxQueueSize", value = "20")
    }
)
public String listarProdutos() {
    return restTemplate.getForObject("http://catalogo-produtos/produtos", String.class);
}
  1. Funcionamento do circuit breaker

O circuit breaker monitora as execuções de cada comando dentro de uma janela de tempo. Quando a taxa de falhas ultrapassa um limite configurado, o disjuntor abre e passa a rejeitar novas chamadas ao serviço problemático, executando imediatamente o fallback. Após um intervalo de descanso, ele entra em estado semiaberto e permite uma requisição de teste. Se essa requisição for bem-sucedida, o circuito volta ao estado fechado; caso contrário, permanece aberto.

Os três parâmetros principais são:

  • Janela de estatísticas: intervalo em que as falhas são contadas. Padrão de 10 segundos.
  • Volume mínimo de requisições: quantidade mínima de chamadas necessárias para que o circuit breaker avalie a abertura. Padrão de 20.
  • Limite percentual de erro: porcentagem de falhas que dispara a abertura do circuito. Padrão de 50%.

Configure esses valores diretamente na anotação:

@HystrixCommand(
    fallbackMethod = "respostaPadrao",
    commandProperties = {
        @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "15"),
        @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "4000"),
        @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "40")
    }
)
public String listarProdutos() {
    return restTemplate.getForObject("http://catalogo-produtos/produtos", String.class);
}
  1. Por que fallback e circuit breaker funcionam juntos

O fallback sozinho resolve falhas individuais, mas ainda assim cada requisição aguarda o timeout antes de retornar a resposta alternativa. Sob alta carga, esse tempo de espera acumualdo pode saturar o consumidor. O circuit breaker elimina essa espera ao bloquear imediatamente as chamadas para a dependência com falha, reduzindo a latência e permitindo a recuperação automática quando o serviço voltar a responder.

Tags: Spring Cloud Hystrix Netflix Hystrix Resilience4j alternative Circuit Breaker Pattern

Publicado em 6-17 16:35