Dev (Back & Front)ARTIGO

Criando aplicações resilientes com Circuit Breaker

Neste artigo, Maicon Pereira mostra como criar aplicações resilientes com Circuit Breaker e explica o que acontece quando o servidor de destino está falhando devido a uma sobrecarga.

Criando aplicações resilientes com Circuit Breaker
Imagem: Maicon Carlos Pereira

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:

referência: Polly

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!

Graduado em Ciência da Computação e MBA em Gerenciamento de Projetos, Scrum Master Professional. Com mais de 12 anos no mundo de desenvolvimento de software, atualmente atuando como especialista em engenharia de software na Ti da Patrus Transportes.

Ver perfil