Circuit breaker e retry patterns: resiliência em chamadas entre serviços
Introdução
Em um cenário cada vez mais dinâmico e interconectado, onde serviços são consumidos por outros serviços, a resiliência das aplicações tornou-se uma preocupação fundamental no desenvolvimento de software. A capacidade de lidar com falhas e erros é crucial para garantir que as aplicações permaneçam disponíveis e funcionem corretamente.
A chamada entre serviços (Service-to-Service Calls) é um padrão comum em sistemas distribuídos, onde uma aplicação pode interagir com outras aplicações ou serviços remotos. No entanto, essas interações podem ser afetadas por diversos fatores que levam a falhas e erros, como problemas de rede, tempo de resposta lento, ou até mesmo desejos intencionais (ataques DDoS) destinados a sobrecarregar os sistemas.
Neste contexto, é crucial implementar padrões de design que promovam a resiliência nas chamadas entre serviços. Neste artigo, exploraremos dois conceitos essenciais para aumentar a robustez das interações entre serviços: Circuit Breaker e Retry Patterns. Ao final do artigo, você estará familiarizado com as melhorias que essas técnicas podem trazer à resiliência das suas aplicações.
O que é e por que importa
Um Circuit Breaker é um padrão de design de software que visa detectar e lidar com falhas em chamadas entre serviços, previnindo a cascata de erros que pode ocorrer quando uma falha afeta várias interações simultâneas. Ele funciona como um interruptor elétrico, cortando o circuito e evitando que o problema se propague.
O Circuit Breaker é essencial em sistemas distribuídos, pois ajuda a prevenir a sobrecarga de recursos, minimizar o impacto das falhas e melhorar a disponibilidade geral do sistema. Ao detectar uma falha recorrente, ele pode desativar temporariamente a chamada ao serviço afetado, evitando que outras partes do sistema sejam afetadas.
A motivação por trás do Circuit Breaker é evitar que as falhas sejam propagadas e causem danos adicionais. Quando uma chamada ao serviço falha, o Circuit Breaker pode reverter a operação para um estado anterior, minimizando as consequências da falha. Além disso, ele fornece informações sobre a causa da falha, permitindo que os administradores tomem medidas corretivas e resolvam o problema.
O Circuit Breaker resolve problemas como:
- Falhas recorrentes em chamadas ao serviço
- Problemas de rede ou tempo de resposta lento
- Ataques DDoS destinados a sobrecarregar os sistemas
Ao implementar um Circuit Breaker, as aplicações podem se tornar mais robustas e resilientes, reduzindo o risco de falhas e melhorando a experiência do usuário.
Como funciona na prática
Um Circuit Breaker consiste em três estados principais: FECHADO, ABERTO e HALF-OPEN.
FECHADO
Nesse estado, as chamadas ao serviço são permitidas normalmente. Se uma chamada falhar, o Circuit Breaker altera para o estado ABERTO.
ABERTO
Quando o Circuit Breaker está no estado ABERTO, as chamadas ao serviço são bloqueadas temporariamente. Esse estado é mantido por um tempo determinado, que pode ser configurado para evitar que a cascata de erros se propague.
HALF-OPEN
Nesse estado, o Circuit Breaker permite uma chamada ao serviço para verificar se a falha foi resolvida. Se a chamada for bem-sucedida, o Circuit Breaker volta para o estado FECHADO, permitindo as chamadas normalmente novamente.
As etapas do funcionamento de um Circuit Breaker são:
- Monitorar as chamadas ao serviço e detectar falhas
- Alterar para o estado ABERTO se uma chamada falhar
- Bloquear as chamadas ao serviço em estado ABERTO
- Permanecer no estado ABERTO por um tempo determinado
- Voltar para o estado FECHADO se uma chamada for bem-sucedida em estado HALF-OPEN
- Retornar a normalidade permitindo as chamadas ao serviço
Alím disso, o Circuit Breaker pode ser configurado para lidar com diferentes tipos de falhas e comportamentos. Por exemplo:
- Tempo limite para bloqueio
- Número de tentativas antes de bloquear
- Período de tempo para verificar se a falha foi resolvida
Essas configurações permitem que o Circuit Breaker seja adaptado às necessidades especÃficas da aplicacão.
Exemplo real
Um exemplo prático de como utilizar Circuit Breaker e padrões de retry em uma aplicação é através da implementação de um serviço que faz chamadas a outros serviços externos.
Suponha que temos uma aplicação com dois serviços: ServicoA e ServicoB. O serviço ServicoA precisa chamar o serviço ServicoB para realizar uma operação crítica. No entanto, às vezes o serviço ServicoB fica indisponível ou demora muito tempo a responder.
// Configuração do Circuit Breaker
@Configuration
public class CircuitBreakerConfig {
@Bean
public RetryTemplate retryTemplate(RetryPolicy retryPolicy) {
return new RetryTemplate(retryPolicy);
}
}
// Implementação do Circuit Breaker
@Service
public class ServicoA {
private final RestTemplate restTemplate;
private final CircuitBreakerFactory circuitBreakerFactory;
@Autowired
public ServicoA(RestTemplate restTemplate, CircuitBreakerFactory circuitBreakerFactory) {
this.restTemplate = restTemplate;
this.circuitBreakerFactory = circuitBreakerFactory;
}
@Async
public void fazerChamadaAServicoB() {
// Configuração do Circuit Breaker para o serviço ServicoB
CircuitBreaker circuitBreaker = circuitBreakerFactory.create("ServicoB");
try {
// Tenta realizar a chamada ao serviço ServicoB com retry configurado
RestResponse resposta = restTemplate.getForObject("http://servicob:8080/resource", RestResponse.class);
// Se a resposta for bem-sucedida, retorna-a
return resposta;
} catch (RestClientException e) {
// Se ocorrer uma falha, o Circuit Breaker altera para estado ABERTO
circuitBreaker.open();
throw new RuntimeException("Falha ao chamar serviço ServicoB", e);
}
}
}
Nesse exemplo, quando o serviço ServicoA tenta chamar o serviço ServicoB, ele utiliza um Circuit Breaker configurado com padrões de retry para lidar com possíveis falhas. Se a chamada ao serviço ServicoB for bem-sucedida, o Circuit Breaker volta para estado FECHADO e as chamadas são permitidas normalmente novamente. Se ocorrer uma falha, o Circuit Breaker altera para estado ABERTO e bloqueia as chamadas temporariamente, evitando que a cascata de erros se propague.
Boas práticas
Utilize Circuit Breakers para proteger chamadas entre serviços que tenham impacto significativo na cascata de erros.
Implemente políticas de retry para lidar com falhas transitórias em serviços externos, evitando bloqueios indesejados.
Configure o Circuit Breaker com timeouts e limites de tentativas apropriados, considerando a latência e a disponibilidade do serviço.
Utilize métricas e monitoramento para avaliar o desempenho do Circuit Breaker e ajustar as configurações se necessário.
Armadilhas comuns
Risco de sobrecarga: Se o retry for configurado com tentativas muito altas, pode levar a uma sobrecarga excessiva no serviço externo.
Falso positivo: O estado ABERTO do Circuit Breaker pode ser gerado por um problema transitório no serviço, levando a bloqueios desnecessários.
Falha em detectar mudanças de disponibilidade: Se o serviço externo não estiver configurado corretamente para informar alterações na sua disponibilidade, o Circuit Breaker pode não estar ciente dessas mudanças.
Conclusão
Ao adotar circuit breakers e padrões de retry, é possível aumentar a resiliência das chamadas entre serviços e evitar cascata de erros. No entanto, é crucial configurar corretamente essas ferramentas para evitar sobrecarga excessiva no serviço externo e falsos positivos.
Para avançar nesse caminho, é recomendável:
- Realizar um mapeamento dos serviços críticos da aplicação e implementar circuit breakers apenas onde necessário.
- Estabelecer políticas de retry e timeouts apropriados para cada serviço, considerando a latência e disponibilidade do mesmo.
- Utilizar monitoramento contínuo para avaliar o desempenho do circuit breaker e ajustar as configurações se necessário.
Referências
- Fowler, M. Circuit Breakers. Disponível em: https://martinfowler.com/bliki/CircuitBreaker.html. Acesso: 2024.
- Newmann, D. The Circuit Breaker Pattern. Disponível em: https://www.thoughtworks.com/insights/blog/circuit-breaker-pattern. Acesso: 2024.
- Nygard, M. Release It!. Pragmatic Bookshelf, 2007. p. 217–223.
- Netflix. Circuit Breaker and Load Shedding in Resilience Design. Disponível em: https://netflix-engineeringblog.github.io/circuit-breaker-load-shedding/. Acesso: 2024.
- OWASP. Fault Tolerance Patterns. Disponível em: https://owasp.org/www-project-fault-tolerance-patterns/. Acesso: 2024.