Desenvolvimento

7 mai, 2019

Criando aplicações resilientes com Circuit Breaker

Publicidade

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!