Padrão de disjuntor - AWS Orientação prescritiva

As traduções são geradas por tradução automática. Em caso de conflito entre o conteúdo da tradução e da versão original em inglês, a versão em inglês prevalecerá.

Padrão de disjuntor

Intenção

O padrão do disjuntor pode impedir que um serviço de chamada repita uma chamada para outro serviço (receptor) quando a chamada já gerou repetidos tempos limite ou falhas. O padrão também é usado para detectar quando o serviço do receptor está funcional novamente.

Motivação

Quando vários microsserviços colaboram para lidar com solicitações, um ou mais serviços podem ficar indisponíveis ou apresentar alta latência. Quando aplicações complexas usam microsserviços, uma interrupção em um microsserviço pode levar à falha da aplicação. Os microsserviços se comunicam por meio de chamadas de procedimentos remotos, e erros transitórios podem ocorrer na conectividade da rede, causando falhas. (Os erros transitórios podem ser tratados usando o padrão de repetição com recuo.) Durante a execução síncrona, a cascata de tempos limite ou falhas pode causar uma experiência ruim para o usuário.

No entanto, em algumas situações, as falhas podem levar mais tempo para serem resolvidas, por exemplo, quando o serviço do receptor está inativo ou uma contenção no banco de dados resulta em tempos limite. Nesses casos, se o serviço de chamada repetir as chamadas repetidamente, essas novas tentativas poderão resultar em contenção na rede e no consumo do grupo de threads do banco de dados. Além disso, se vários usuários tentarem a aplicação novamente e de forma repetida, isso piorará o problema e poderá causar degradação da performance de toda a aplicação.

O padrão do disjuntor foi popularizado por Michael Nygard em seu livro Release It (Nygard 2018). Esse padrão de design pode impedir que um serviço de chamada repita uma chamada de serviço que já tenha gerado repetidos tempos limite ou falhas. Ele também pode detectar quando o serviço do receptor está funcional novamente.

Os objetos do disjuntor funcionam como disjuntores elétricos que interrompem automaticamente a corrente quando há uma anormalidade no circuito. Os disjuntores elétricos desligam ou interrompem o fluxo da corrente quando há uma falha. Da mesma forma, o objeto do disjuntor está situado entre o chamador e o serviço do receptor e dispara se o receptor não estiver disponível.

As falácias da computação distribuída são um conjunto de afirmações feitas por Peter Deutsch e outros da Sun Microsystems. Eles dizem que programadores que são novos em aplicações distribuídas invariavelmente fazem suposições falsas. A confiabilidade da rede, as expectativas de latência zero e as limitações de largura de banda resultam em aplicações de software escritas com o mínimo de tratamento de erros de rede.

Durante uma interrupção na rede, as aplicações podem esperar por uma resposta indefinidamente e consumir continuamente os recursos da aplicação. Deixar de repetir as operações quando a rede estiver disponível também pode levar à degradação da aplicação. Se as chamadas de API para um banco de dados ou um serviço externo expirarem devido a problemas de rede, chamadas repetidas sem disjuntor podem afetar o custo e a performance.

Aplicabilidade

Use esse padrão quando:

  • O serviço de chamadas faz uma chamada que provavelmente falhará.

  • A alta latência exibida pelo serviço do receptor (por exemplo, quando as conexões do banco de dados são lentas) gera tempos limite no serviço do chamador.

  • O serviço do chamador faz uma chamada síncrona, mas o serviço do receptor não está disponível ou apresenta alta latência.

Problemas e considerações

  • Implementação independente de serviço: para evitar o excesso de código, recomendamos que você implemente o objeto disjuntor de forma independente de microsserviços e orientada por API.

  • Fechamento do circuito pelo receptor: quando o receptor se recupera de um problema ou falha de performance, ele pode atualizar o status do circuito para CLOSED. Esta é uma extensão do padrão do disjuntor e poderá ser implementada se seu objetivo de tempo de recuperação (RTO) exigir.

  • Chamadas multithreaded: o valor do tempo limite de expiração é definido como o período de tempo em que o circuito permanece desligado antes que as chamadas sejam roteadas novamente para verificar a disponibilidade do serviço. Quando o serviço do receptor é chamado em vários threads, a primeira chamada que falhou define o valor do tempo limite de expiração. Sua implementação deve garantir que as chamadas subsequentes não alterem o tempo limite de expiração indefinidamente.

  • Forçar a abertura ou o fechamento do circuito: os administradores do sistema devem ter a capacidade de abrir ou fechar um circuito. Isso pode ser feito atualizando o valor do tempo limite de expiração na tabela do banco de dados.

  • Observabilidade: a aplicação deve ter o registro em log configurado para identificar as chamadas que falham quando o disjuntor está aberto.

Implementação

Arquitetura de alto nível

No exemplo a seguir, o chamador é o serviço de pedidos e o receptor é o serviço de pagamento.

Quando não há falhas, o serviço de pedidos roteia todas as chamadas para o serviço de pagamento pelo disjuntor, conforme mostra o diagrama a seguir.

Padrão de disjuntor sem falhas.

Se o serviço de pagamento atingir o tempo limite, o disjuntor poderá detectar o tempo limite e rastrear a falha.

Disjuntor com falha no serviço de pagamento.

Se os tempos limite excederem um limite especificado, a aplicação abrirá o circuito. Quando o circuito está aberto, o objeto do disjuntor não roteia as chamadas para o serviço de pagamento. Ele retorna uma falha imediata quando o serviço de pedidos chama o serviço de pagamento.

O disjuntor interrompe o roteamento para o serviço de pagamento.

O objeto do disjuntor tenta periodicamente verificar se as chamadas para o serviço de pagamento foram bem-sucedidas.

O disjuntor repete periodicamente o serviço de pagamento.

Quando a chamada para o serviço de pagamento é bem-sucedida, o circuito é fechado e todas as chamadas adicionais são roteadas para o serviço de pagamento novamente.

Disjuntor com serviço de pagamento em funcionamento.

Implementação usando serviços AWS

A solução de amostra usa fluxos de trabalho expressos no AWS Step Functions para implementar o padrão do disjuntor. A máquina de estado do Step Functions permite que você configure os recursos de repetição e o fluxo de controle baseado em decisões necessários para a implementação do padrão.

A solução também usa uma tabela do Amazon DynamoDB como armazenamento de dados para rastrear o status do circuito. Isso pode ser substituído por um datastore na memória, como o Amazon ElastiCache (Redis OSS), para melhorar a performance.

Quando um serviço deseja chamar outro serviço, ele inicia o fluxo de trabalho com o nome do serviço do receptor. O fluxo de trabalho obtém o status do disjuntor na tabela CircuitStatus do DynamoDB, que armazena os serviços atualmente degradados. Se CircuitStatus contiver um registro não expirado para o receptor, o circuito estará aberto. O fluxo de trabalho do Step Functions retorna uma falha imediata e tem como saída um estado FAIL.

Se a tabela CircuitStatus não contiver um registro para o receptor ou contiver um registro expirado, o serviço estará operacional. A etapa ExecuteLambda na definição da máquina de estado chama a função do Lambda que é enviada por meio de um valor de parâmetro. Se a chamada for bem-sucedida, o fluxo de trabalho do Step Functions será encerrado com um estado SUCCESS.

Implementação do disjuntor com o AWS Step Functions e o DynamoDB.

Se a chamada de serviço falhar ou ocorrer um tempo limite, a aplicação tentará novamente com um recuo exponencial por um número definido de vezes. Se a chamada de serviço falhar após as novas tentativas, o fluxo de trabalho vai inserir um registro na tabela CircuitStatus do serviço com um ExpiryTimeStamp, e o fluxo de trabalho sairá com um estado FAIL. As chamadas subsequentes para o mesmo serviço retornarão uma falha imediata, desde que o disjuntor esteja aberto. A etapa Get Circuit Status na definição da máquina de estado verifica a disponibilidade do serviço com base no valor ExpiryTimeStamp. Os itens expirados são excluídos da tabela CircuitStatus usando o recurso de tempo de vida (TTL) do DynamoDB.

Código de exemplo

O código a seguir usa a função GetCircuitStatus do Lambda para verificar o status do disjuntor.

var serviceDetails = _dbContext.QueryAsync<CircuitBreaker>(serviceName, QueryOperator.GreaterThan, new List<object> {currentTimeStamp}).GetRemainingAsync(); if (serviceDetails.Result.Count > 0) { functionData.CircuitStatus = serviceDetails.Result[0].CircuitStatus; } else { functionData.CircuitStatus = ""; }

O código a seguir mostra as instruções do Amazon States Language no fluxo de trabalho do Step Functions.

"Is Circuit Closed": { "Type": "Choice", "Choices": [ { "Variable": "$.CircuitStatus", "StringEquals": "OPEN", "Next": "Circuit Open" }, { "Variable": "$.CircuitStatus", "StringEquals": "", "Next": "Execute Lambda" } ] }, "Circuit Open": { "Type": "Fail" }

Repositório GitHub

Para uma implementação completa da arquitetura de amostra desse padrão, consulte o repositório do GitHub em https://github.com/aws-samples/circuit-breaker-netcore-blog.

Referências do blog

Conteúdo relacionado