Spring Boot e Spring Cloud
Spring Cloud para microsserviços: Config Server, discovery, gateway e resiliência
Avançado9 min de leituraAtualizado
Esta página foi traduzida do inglês com a ajuda de IA e conferida com o original. Ler a versão em inglês
Você vai saber explicar quais problemas de sistemas distribuídos o Spring Cloud resolve e quais projetos cuidam deles hoje: rodar um Config Server e atualizar seus clientes, registrar serviços no Eureka e chamá-los pelo nome com um RestClient com load balancing, rotear o tráfego externo pelo Spring Cloud Gateway e proteger as chamadas com circuit breakers do Resilience4j, tudo no Spring Boot 3 com os release trains 2023 e 2024.
O que você vai aprender
- Relacionar os problemas recorrentes de uma arquitetura de microsserviços aos projetos do Spring Cloud que tratam deles
- Configurar o Spring Cloud Config Server com backend Git e atualizar a configuração dos clientes em tempo de execução
- Registrar serviços no Eureka e chamá-los pelo nome lógico com um RestClient com load balancing ou uma HTTP interface
- Definir rotas, predicates e filters no Spring Cloud Gateway
- Envolver chamadas remotas em circuit breakers, retries e time limiters do Resilience4j e explicar quando cada um ajuda
- Identificar os componentes aposentados do Spring Cloud Netflix e seus substitutos atuais
Dividir uma aplicação em uma dúzia de serviços troca problemas que você já tinha resolvido (uma chamada de método, um arquivo de configuração compartilhado, um log único) por problemas que ainda não resolveu: onde está o outro serviço, como ele obtém sua configuração, o que acontece quando ele fica lento e como o tráfego de fora chega ao serviço certo. O Spring Cloud é o guarda-chuva dos projetos Spring que tratam desses problemas em cima do Spring Boot. Ele mudou muito desde a primeira geração baseada na Netflix, então esta página descreve o cenário atual para o Spring Boot 3 e mostra o essencial de cada peça: configuração, discovery, load balancing, gateway e resiliência.
Release trains e o conjunto atual de projetos
O Spring Cloud não é uma única biblioteca, e sim um conjunto coordenado de projetos lançados juntos como um release train, cuja versão é importada como BOM:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2024.0.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
Cada train é voltado para uma linha minor do Spring Boot; combinar um train com a versão errada do Boot gera falhas obscuras na inicialização. Os projetos que você mais vai encontrar:
| Problema | Projeto | Observações |
|---|---|---|
| Configuração centralizada e versionada | Spring Cloud Config | Backends Git, Vault, JDBC e de arquivos |
| Registro de serviços | Spring Cloud Netflix Eureka, Consul, Zookeeper, Kubernetes | O Eureka é o único componente Netflix que restou |
| Load balancing no cliente | Spring Cloud LoadBalancer | Substituiu o Ribbon |
| Clientes HTTP declarativos | Spring Cloud OpenFeign (manutenção), HTTP interfaces do Spring Framework | Ambos se integram ao LoadBalancer |
| API gateway | Spring Cloud Gateway | Substituiu o Zuul; núcleo reativo, com uma variante servlet |
| Circuit breaking | Spring Cloud Circuit Breaker com Resilience4j | Substituiu o Hystrix |
| Mensageria orientada a eventos | Spring Cloud Stream | Binders para Kafka, RabbitMQ e outros |
| Propagação de configuração | Spring Cloud Bus | Propaga eventos de refresh por meio de um broker |
| Contratos orientados ao consumidor | Spring Cloud Contract | Stubs gerados a partir dos contratos |
O tracing distribuído saiu do Spring Cloud quando o Sleuth foi aposentado; agora ele é feito pelo Micrometer Tracing dentro do próprio Boot, como mostra o guia de observabilidade.
Configuração centralizada
Doze serviços com doze arquivos application.yml, todos com o mesmo host de banco de dados e o mesmo endereço do broker de mensagens, divergem em poucas semanas. O Spring Cloud Config Server serve a configuração a partir de um backend versionado, na maioria das vezes um repositório Git, indexada por nome da aplicação, profile e label (branch ou tag):
@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication { /* main */ }
server:
port: 8888
spring:
cloud:
config:
server:
git:
uri: https://git.example.com/platform/config.git
default-label: main
search-paths: '{application}' # one folder per service
clone-on-start: true
O repositório contém application.yml (compartilhado por todos), orders.yml, orders-prod.yml e assim por diante; GET /orders/prod/main retorna as property sources mescladas do serviço orders no profile prod. Valores criptografados podem ser armazenados como {cipher}... e descriptografados pelo servidor com uma chave simétrica ou um par de chaves, o que evita deixar segredos em texto plano no Git sem exigir um cofre separado, embora o Spring Cloud Vault ou um gerenciador de segredos da nuvem seja a opção mais robusta.
Os clientes adicionam spring-cloud-starter-config e apontam para o servidor pelo mecanismo de config import, disponível a partir do Boot 2.4:
spring:
application:
name: orders # becomes {application} in the request
config:
import: "optional:configserver:http://config:8888"
cloud:
config:
fail-fast: true # refuse to start without configuration in production
retry:
max-attempts: 6
A antiga abordagem com bootstrap.yml ainda existe por trás do spring-cloud-starter-bootstrap, mas é legado; spring.config.import é o modelo atual e se integra à ordem de precedência das propriedades descrita no guia de configuração.
Alterar um valor no Git não muda um serviço em execução até que ele releia a configuração. Exponha o endpoint refresh do Actuator e faça um POST nele: o Boot refaz o binding dos beans @ConfigurationProperties e recria os beans anotados com @RefreshScope:
@RefreshScope
@Service
class PricingService {
private final BigDecimal surcharge;
PricingService(@Value("${pricing.surcharge}") BigDecimal surcharge) {
this.surcharge = surcharge; // a new instance is created on the next call after a refresh
}
}
Atualizar cada instância na mão não escala; o Spring Cloud Bus conecta os serviços ao Kafka ou ao RabbitMQ para que um único POST /actuator/busrefresh em qualquer instância (ou um webhook do servidor Git para o endpoint /monitor do Config Server) se espalhe para todas. @RefreshScope coloca o bean atrás de um proxy, o que custa um pouco a cada chamada e combina mal com @Scheduled e com beans que mantêm conexões; para a maioria das configurações atualizáveis, prefira @ConfigurationProperties, cujo binding é refeito no próprio lugar.
Service discovery e load balancing
Deixar http://orders-2.internal:8080 fixo no código quebra na primeira vez que uma instância é substituída. Um registro de serviços permite que cada instância registre seu nome e endereço ao subir, envie heartbeats e cancele o registro ao desligar; os clientes pedem ao registro as instâncias atuais de orders e escolhem uma.
O Eureka é o registro que a maior parte do material sobre Spring Cloud descreve. O servidor é uma aplicação Boot com spring-cloud-starter-netflix-eureka-server e @EnableEurekaServer; cada serviço adiciona spring-cloud-starter-netflix-eureka-client e se registra com seu spring.application.name:
eureka:
client:
service-url:
defaultZone: http://eureka-1:8761/eureka/,http://eureka-2:8761/eureka/
instance:
prefer-ip-address: true
lease-renewal-interval-in-seconds: 10
O Eureka privilegia a disponibilidade em vez da consistência: as instâncias continuam listadas por um tempo depois de morrerem, e um servidor que perde contato com os pares continua servindo o último registro conhecido (modo self-preservation). Por isso os clientes precisam de retries e timeouts de qualquer forma: o registro é um cache, não a verdade.
No lado do cliente fica o Spring Cloud LoadBalancer, que resolve um nome de host lógico para uma instância e aplica uma política round-robin (padrão) ou aleatória. Serviços com Boot 3.2+ devem usar um RestClient com load balancing:
@Configuration
class ClientConfig {
@Bean
@LoadBalanced
RestClient.Builder loadBalancedRestClientBuilder() {
return RestClient.builder();
}
@Bean
InventoryApi inventoryApi(@LoadBalanced RestClient.Builder builder) {
RestClient client = builder.baseUrl("http://inventory").build(); // "inventory" is a service name
return HttpServiceProxyFactory.builderFor(RestClientAdapter.create(client))
.build().createClient(InventoryApi.class);
}
}
@LoadBalanced marca o builder para que um interceptor troque inventory por um host e uma porta reais a cada chamada. O Spring Cloud OpenFeign oferece o mesmo por meio de interfaces @FeignClient(name = "inventory"), com um conjunto mais rico de anotações de contrato e encoders; ele continua com suporte, mas código novo deve preferir as HTTP interfaces do framework, que não exigem nenhum projeto extra e funcionam com o mesmo load balancer.
No Kubernetes, tudo isso é opcional. Um Service chamado inventory já resolve por DNS para um IP virtual com load balancing, então http://inventory funciona com um RestClient comum e sem registro. O Spring Cloud Kubernetes existe para aplicações que querem que a abstração DiscoveryClient leia os endpoints do Kubernetes, principalmente em deploys híbridos.
O gateway
Clientes externos não devem conhecer a topologia dos seus serviços. Um API gateway é o ponto de entrada único que roteia as requisições para os serviços, termina o TLS, aplica autenticação e rate limiting e reescreve caminhos. O Spring Cloud Gateway é construído sobre o Spring WebFlux e o Project Reactor (existe uma variante baseada em servlet, o Spring Cloud Gateway Server MVC, para times que querem uma stack bloqueante), e as rotas são declaradas na configuração ou em Java:
spring:
cloud:
gateway:
routes:
- id: orders
uri: lb://orders # lb:// resolves through the load balancer and registry
predicates:
- Path=/api/orders/**
filters:
- StripPrefix=1 # /api/orders/42 -> /orders/42 on the service
- name: CircuitBreaker
args:
name: ordersBreaker
fallbackUri: forward:/fallback/orders
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 50
redis-rate-limiter.burstCapacity: 100
- id: catalogue
uri: http://catalogue:8080
predicates:
- Path=/api/catalogue/**
- Method=GET
filters:
- AddResponseHeader=Cache-Control, public, max-age=60
Um predicate decide se uma rota corresponde à requisição (path, método, header, host, query, horário do dia); um filter transforma a requisição ou a resposta (remover ou reescrever paths, adicionar headers, retry, rate limiting, circuit breaking, repassar um token OAuth 2.0). Global filters se aplicam a todas as rotas e costumam ser o lugar de correlation ids, logging de requisições e autenticação. Com o suporte a resource server OAuth 2.0 do Spring Security, o gateway valida os access tokens uma única vez e os repassa, de modo que os serviços downstream podem confiar no header Authorization; o filter TokenRelay cuida do repasse.
No release train 2025.0, os artefatos do gateway foram renomeados para deixar explícitas as duas stacks (spring-cloud-starter-gateway-server-webflux e spring-cloud-starter-gateway-server-webmvc, com prefixos de propriedades sob spring.cloud.gateway.server.*); os nomes antigos continuam disponíveis por um período de transição. Consulte a documentação de referência do train que você usa antes de copiar nomes de propriedades.
Resiliência: circuit breakers, retries e limites de tempo
Uma dependência lenta é mais perigosa do que uma fora do ar: as threads se acumulam esperando, e quem chama fica lento para todo mundo. O Resilience4j fornece os padrões, e o Spring Cloud Circuit Breaker os encapsula atrás de uma factory neutra em relação ao fornecedor:
@Service
class RecommendationService {
private final CircuitBreakerFactory<?, ?> breakers;
private final RecommendationApi api;
RecommendationService(CircuitBreakerFactory<?, ?> breakers, RecommendationApi api) {
this.breakers = breakers;
this.api = api;
}
List<Product> forCustomer(long customerId) {
return breakers.create("recommendations")
.run(() -> api.recommend(customerId),
throwable -> List.of()); // fallback: an empty list, not an error page
}
}
resilience4j:
circuitbreaker:
instances:
recommendations:
sliding-window-size: 20
failure-rate-threshold: 50 # open after 50 % failures in the window
wait-duration-in-open-state: 30s
permitted-number-of-calls-in-half-open-state: 5
timelimiter:
instances:
recommendations:
timeout-duration: 800ms
Um circuit breaker conta as falhas em uma janela deslizante; passado o limite, ele abre e faz as chamadas falharem imediatamente, sem tocar na dependência; depois de uma espera, deixa passar algumas chamadas de teste (half-open) e volta a fechar se elas derem certo. Um time limiter limita quanto tempo uma chamada pode levar. Um retry repete uma chamada que falhou, com backoff, o que ajuda em erros transitórios e piora uma sobrecarga; por isso, combine-o com o circuit breaker e mantenha poucas tentativas. Um bulkhead limita as chamadas concorrentes a uma dependência para que uma dependência lenta não consuma todas as threads. O starter resilience4j-spring-boot3 também oferece anotações (@CircuitBreaker(name = "recommendations", fallbackMethod = "empty")) aplicadas via AOP, e publica estado e métricas no Actuator e no Micrometer.
Os fallbacks precisam fazer sentido: uma lista de recomendações vazia, um preço em cache, uma resposta de "tente novamente mais tarde" com o header Retry-After. Um fallback que devolve silenciosamente dados desatualizados ou inventados é um bug de efeito retardado.
Juntando tudo
Uma topologia mínima, mas realista, em máquinas virtuais: duas instâncias de Config Server apoiadas no mesmo repositório Git; duas instâncias de Eureka que se registram uma na outra; um gateway que roteia /api/** para os serviços pelo nome, com um circuit breaker por rota; cada serviço busca a configuração ao subir, se registra no Eureka, chama os pares por HTTP interfaces com load balancing protegidas pelo Resilience4j e reporta health, métricas e traces pelo Actuator. No Kubernetes, remova o Eureka e possivelmente o Config Server (ConfigMaps e Secrets com spring.config.import=kubernetes: ou config trees montados), mantenha o gateway ou troque-o por um ingress mais segurança em cada serviço, e mantenha a camada de resiliência, que nenhuma plataforma oferece para você.
Erros comuns
Misturar release trains e versões do Boot. O BOM precisa corresponder à versão minor do Boot; os sintomas vão de um NoSuchMethodError na inicialização a beans que somem sem aviso.
Copiar exemplos de Hystrix, Ribbon ou Zuul. Eles não compilam com os trains atuais. Traduza-os para Resilience4j, Spring Cloud LoadBalancer e Spring Cloud Gateway.
Tratar o registro como fonte de verdade. Instâncias que caíram continuam listadas até o lease expirar. Todo cliente precisa de timeouts e retries com backoff, independentemente do discovery.
Atualizar com @RefreshScope beans que guardam estado ou recursos. O proxy recria o bean e descarta caches, pools de conexões e tarefas agendadas. Em vez disso, refaça o binding de @ConfigurationProperties e reserve @RefreshScope para beans baratos de reconstruir.
Uma única instância de Config Server ou de registro. Ambos ficam no caminho crítico da inicialização; rode pelo menos duas e configure fail-fast de forma consciente, conforme um serviço possa ou não subir com valores padrão locais desatualizados.
Repetir chamadas não idempotentes. Um POST /payments repetido cobra duas vezes. Faça retry apenas de operações idempotentes, ou torne a operação idempotente com uma chave gerada pelo cliente.
Colocar regra de negócio nos filters do gateway. O gateway deve rotear, proteger, limitar e observar. Transformação de payloads e orquestração pertencem aos serviços, onde podem ser testadas e versionadas junto com o domínio.
Principais pontos
- O Spring Cloud é um conjunto de projetos coordenados lançados como trains; importe o BOM que corresponde à versão minor do seu Spring Boot e nunca os misture.
- O Config Server serve configuração versionada a partir do Git por aplicação, profile e label; os clientes a importam com
spring.config.importe a atualizam pelo Actuator ou pelo Spring Cloud Bus. - O discovery (Eureka, Consul, Kubernetes) junto com o Spring Cloud LoadBalancer permite que os serviços se chamem pelo nome com um
RestClient@LoadBalancedou uma HTTP interface; no Kubernetes, o DNS muitas vezes torna o registro desnecessário. - O Spring Cloud Gateway roteia o tráfego externo com predicates e filters, e é nele que devem ficar a autenticação, o rate limiting e os circuit breakers por rota.
- Circuit breakers, time limiters, retries e bulkheads do Resilience4j impedem que uma única dependência lenta derrube quem a chama; os fallbacks precisam devolver algo que o negócio possa aceitar.
- Hystrix, Ribbon e Zuul acabaram; o Sleuth virou Micrometer Tracing; o OpenFeign está em modo de manutenção em favor das HTTP interfaces do Spring.
Fontes e leituras complementares
Perguntas frequentes
- Qual versão do Spring Cloud funciona com a minha versão do Spring Boot?
- O Spring Cloud é lançado em release trains, que recebiam nomes de estações do metrô de Londres e, desde 2020, são nomeados pelo ano. O train 2023.0.x (Leyton) é voltado para o Spring Boot 3.2 e 3.3; o 2024.0.x (Moorgate), para o Boot 3.4; o 2025.0.x (Northfields), para o Boot 3.5. Importe o BOM spring-cloud-dependencies do train correspondente e deixe que ele gerencie a versão de cada spring-cloud-starter; a tabela de compatibilidade na página do projeto Spring Cloud é a referência definitiva.
- Ainda preciso do Eureka no Kubernetes?
- Geralmente não. Os Services do Kubernetes já oferecem discovery baseado em DNS e load balancing, e um Spring Cloud Gateway ou um ingress controller consegue rotear pelo nome do serviço. O Eureka ainda faz sentido em máquinas virtuais, em plataformas sem uma abstração de serviço ou quando você precisa de decisões de load balancing no cliente que o Kubernetes não oferece. O Spring Cloud Kubernetes faz a ponte entre os dois quando a mesma base de código precisa rodar em ambos.
- O Spring Cloud OpenFeign está depreciado?
- Ele está em modo de manutenção: recebe correções de bugs e continua compatível com os novos release trains, mas o time do Spring recomenda, para código novo, os clientes de HTTP interface embutidos no Spring Framework 6. Os clientes Feign existentes continuam funcionando; a migração pode ser gradual, interface por interface, porque em ambos os casos são interfaces declarativas apoiadas por um cliente com load balancing.
- O que aconteceu com Hystrix, Ribbon e Zuul?
- A Netflix parou de desenvolvê-los, e o Spring Cloud removeu as integrações no release train 2020.0 (Ilford). Os substitutos são o Spring Cloud Circuit Breaker com Resilience4j no lugar do Hystrix, o Spring Cloud LoadBalancer no lugar do Ribbon e o Spring Cloud Gateway no lugar do Zuul. O Eureka é o único componente da Netflix que continua no Spring Cloud Netflix.