No artigo anterior apresentamos o padrão de resiliência Retry, mas o que acontece se o servidor de destino está falhando devido a uma sobrecarga? Fazer novas tentativas sobrecarregará ainda mais o servidor, e é neste cenário que o padrão Circuit Breaker pode nos ajudar.
Em circuitos elétricos, um Circuit Breaker (disjuntor) é um dispositivo que protege uma instalação elétrica de sobrecargas de corrente, bem como curto-circuitos. Em sistemas distribuídos, a implementação do padrão Circuit Breaker ajudará a proteger o serviço de destino.
Esse padrão é mais complexo que o Retry, pois se baseia numa máquina de estado, como mostra a figura a seguir:

Estados:
- Closed/Fechado: em circuitos elétricos, quando um disjuntor está fechado, indica que está passando corrente. No padrão de resiliência podemos dizer que está passando “requisições”.
- Open/Aberto: neste estado, o circuito não passa as requisições ao destino – apenas retorna ao requisitante que o serviço está fora, evitando assim de sobrecarregar o destino com novas requisições, além de fornecer mais rapidamente um feedback, sem precisar aguardar o termino de uma requisição que provavelmente falhará. O circuito vai para o estado aberto após atingir um limite pré definido de falhas e permanecerá aberto também por um tempo definido.
- Meio aberto/Half-Open: esse é um estado de checagem. Após esgotar o tempo no estado aberto, o sistema vai para esse estado e a próxima requisição será encaminhada ao destino. Se o destino retornar falha, o circuito volta ao estado aberto, e se retornar com sucesso, vai para o estado fechado.
A animação a seguir ilustra o Circuit Breaker em ação com um limite (threshold) de duas falhas consecutivas para fazer a transição para o estado de aberto:
Circuit Breaker com Polly
Novamente, o Polly vem para facilitar a vida dos desenvolvedores na implementação do padrão Circuit Breaker no código. Veja como é simples:
public class ProductService
{
private readonly HttpClient _httpClient;
private readonly AsyncCircuitBreakerPolicy _circuitBreakerPolicy;
public ProductService()
{
_httpClient = new HttpClient();
_httpClient.BaseAddress = new Uri("http://localhost:5000/");
_circuitBreakerPolicy = Policy.Handle<HttpRequestException>()
.CircuitBreakerAsync(exceptionsAllowedBeforeBreaking: 2,
durationOfBreak: TimeSpan.FromMinutes(1));
}
public async Task<string> GetSample()
{
return await _circuitBreakerPolicy.ExecuteAsync<string>(async () =>
{
return await _httpClient.GetStringAsync("api/business");
});
}
}
No código estamos configurando uma política de Circuit Breaker para que quando haja duas exceções (falhas) HttpRequestException consecutivas, o circuito pare de enviar requisições ao destino por um minuto. Durante esse período, o circuito retornaria uma BrokenCircuitException.
Observe no código que a política precisa ser a mesma instância entre as chamadas – isso porque é necessário manter a instância da maquina de estado.
Veremos o código de um cliente consumindo esse serviço:
public class Client
{
private readonly ProductService _productService;
public Client()
{
_productService = new ProductService();
}
private string Call()
{
try
{
return _productService.GetSample().Result;
}
catch (AggregateException ex)
{
return ex.InnerException.GetType().FullName;
}
}
public void Test_5_Calls()
{
for (int i = 1; i <= 5; i++)
{
Console.WriteLine($"Call {i}: {Call()}");
}
}
}
Temos a seguinte saída para o método Test_5_Calls:
Call 1: System.Net.Http.HttpRequestException
Call 2: System.Net.Http.HttpRequestException
Call 3: Polly.CircuitBreaker.BrokenCircuitException
Call 4: Polly.CircuitBreaker.BrokenCircuitException
Call 5: Polly.CircuitBreaker.BrokenCircuitException
Vejam que após a segunda chamada consecutiva com falha (HttpRequestException), o serviço passa a falhar rápido, retornando uma BrokenCircuitException.
Configuração avançada do Circuit Breaker
Imagine um cenário onde o serviço de destino está sendo balanceado e um dos servidores está sobrecarregado. Neste cenário, é provável que não tenhamos uma quantidade seguida de falhas para ativar o Circuit Breaker.
O framework Polly disponibiliza a política AdvancedCircuitBreaker, que permitirá a criação de uma configuração mais robusta, na qual será analisada uma amostragem de requisições durante um período e abrirá o circuito, caso a quantidade de falhas atinja um certo percentual. Vale a pena explorar um pouco mais essa política.
Conclusão
A política de Circuit Breaker é muito importante para proteger o servidor de destino, bem como favorecer a experiência do usuário com uma falha rápida. A implementação dessa política com o Polly é relativamente simples.
Espero que tenham gostado. Deixem seus feedbacks e continuem acompanhando!