Padrões de resiliência de aplicativos
Gorjeta
Este conteúdo é um excerto do eBook, Architecting Cloud Native .NET Applications for Azure, disponível no .NET Docs ou como um PDF transferível gratuito que pode ser lido offline.
A primeira linha de defesa é a resiliência da aplicação.
Embora você possa investir tempo considerável escrevendo sua própria estrutura de resiliência, esses produtos já existem. Polly é uma biblioteca abrangente de resiliência .NET e tratamento de falhas transitórias que permite que os desenvolvedores expressem políticas de resiliência de maneira fluente e segura para threads. O Polly destina-se a aplicativos criados com o .NET Framework ou o .NET 7. A tabela a seguir descreve os recursos de resiliência, chamados policies
, disponíveis na Biblioteca Polly. Podem ser aplicados individualmente ou agrupados.
Política | Experiência |
---|---|
Tentar novamente | Configura operações de repetição em operações designadas. |
Disjuntor Automático | Bloqueia operações solicitadas por um período predefinido quando as falhas excedem um limite configurado |
Limite de tempo excedido | Coloca limite na duração pela qual um chamador pode esperar por uma resposta. |
Bulkhead | Restringe as ações ao pool de recursos de tamanho fixo para evitar que chamadas com falha sobrecarreguem um recurso. |
Cache | Armazena respostas automaticamente. |
Fallback | Define o comportamento estruturado em caso de falha. |
Observe como na figura anterior as políticas de resiliência se aplicam a mensagens de solicitação, sejam elas provenientes de um cliente externo ou de um serviço back-end. O objetivo é compensar a solicitação de um serviço que pode estar momentaneamente indisponível. Essas interrupções de curta duração normalmente se manifestam com os códigos de status HTTP mostrados na tabela a seguir.
Código de status HTTP | Motivo |
---|---|
404 | Não Encontrado |
408 | Tempo limite do pedido |
429 | Demasiados pedidos (provavelmente foi limitado) |
502 | Gateway ruim |
503 | Serviço indisponível |
504 | Tempo limite do gateway |
Pergunta: Você tentaria novamente um código de status HTTP de 403 - Proibido? N.º Aqui, o sistema está funcionando corretamente, mas informando ao chamador que ele não está autorizado a executar a operação solicitada. Deve-se ter o cuidado de repetir apenas as operações causadas por falhas.
Conforme recomendado no Capítulo 1, os desenvolvedores da Microsoft que criam aplicativos nativos da nuvem devem ter como alvo a plataforma .NET. A versão 2.1 introduziu a biblioteca HTTPClientFactory para criar instâncias de cliente HTTP para interagir com recursos baseados em URL. Substituindo a classe HTTPClient original, a classe factory suporta muitos recursos aprimorados, um dos quais é a integração total com a biblioteca de resiliência Polly. Com ele, você pode facilmente definir políticas de resiliência na classe de inicialização do aplicativo para lidar com falhas parciais e problemas de conectividade.
Em seguida, vamos expandir os padrões de repetição e disjuntor.
Padrão de Repetição
Em um ambiente distribuído nativo da nuvem, as chamadas para serviços e recursos de nuvem podem falhar devido a falhas transitórias (de curta duração), que normalmente se corrigem após um breve período de tempo. A implementação de uma estratégia de repetição ajuda um serviço nativo da nuvem a mitigar esses cenários.
O padrão Retry permite que um serviço tente novamente uma operação de solicitação com falha um número (configurável) de vezes com um tempo de espera exponencialmente crescente. A Figura 6-2 mostra uma nova tentativa em ação.
Figura 6-2. Repetir padrão em ação
Na figura anterior, um padrão de repetição foi implementado para uma operação de solicitação. Ele é configurado para permitir até quatro tentativas antes de falhar com um intervalo de backoff (tempo de espera) a partir de dois segundos, que dobra exponencialmente para cada tentativa subsequente.
- A primeira chamada falha e retorna um código de status HTTP de 500. O aplicativo aguarda por dois segundos e tenta novamente a chamada.
- A segunda invocação também falha e retorna um código de status HTTP de 500. O aplicativo agora dobra o intervalo de backoff para quatro segundos e tenta novamente a chamada.
- Finalmente, a terceira chamada é bem-sucedida.
- Nesse cenário, a operação de repetição teria tentado até quatro novas tentativas, dobrando a duração do backoff antes de falhar a chamada.
- Se a 4ª tentativa falhasse, uma política de fallback seria invocada para lidar graciosamente com o problema.
É importante aumentar o período de recuo antes de tentar novamente a chamada para permitir que o tempo de serviço se autocorrija. É uma boa prática implementar um backoff exponencialmente crescente (dobrando o período em cada nova tentativa) para permitir um tempo de correção adequado.
Padrão do disjuntor
Embora o padrão de repetição possa ajudar a salvar uma solicitação emaranhada em uma falha parcial, há situações em que as falhas podem ser causadas por eventos imprevistos que exigirão longos períodos de tempo para serem resolvidos. Estas falhas podem variar em termos de gravidade, de uma perda parcial de conectividade à falha total de um serviço. Nessas situações, é inútil para um aplicativo repetir continuamente uma operação que provavelmente não terá êxito.
Para piorar as coisas, a execução de operações de repetição contínua em um serviço não responsivo pode levá-lo a um cenário de negação de serviço autoimposto, onde você inunda seu serviço com chamadas contínuas esgotando recursos como memória, threads e conexões de banco de dados, causando falha em partes não relacionadas do sistema que usam os mesmos recursos.
Nessas situações, seria preferível que a operação falhasse imediatamente e só tentasse invocar o serviço se for provável que ele seja bem-sucedido.
O padrão de disjuntor pode impedir que um aplicativo tente executar repetidamente uma operação que provavelmente falhará. Após um número predefinido de chamadas falhadas, bloqueia todo o tráfego para o serviço. Periodicamente, ele permitirá uma chamada de avaliação para determinar se a falha foi resolvida. A Figura 6-3 mostra o padrão do disjuntor em ação.
Figura 6-3. Padrão do disjuntor em ação
Na figura anterior, um padrão de disjuntor foi adicionado ao padrão de repetição original. Observe como após 100 solicitações com falha, os disjuntores abrem e não permitem mais chamadas para o serviço. O valor CheckCircuit, definido em 30 segundos, especifica a frequência com que a biblioteca permite que uma solicitação prossiga para o serviço. Se essa chamada for bem-sucedida, o circuito fecha e o serviço fica novamente disponível para o tráfego.
Tenha em mente que a intenção do padrão Disjuntor é diferente da do padrão Retry. O padrão Retry permite que um aplicativo tente novamente uma operação na expectativa de que ela seja bem-sucedida. O padrão de disjuntor impede que um aplicativo faça uma operação que provavelmente falhará. Normalmente, um aplicativo combinará esses dois padrões usando o padrão Retry para invocar uma operação por meio de um disjuntor.
Testar a resiliência
O teste de resiliência nem sempre pode ser feito da mesma forma que você testa a funcionalidade do aplicativo (executando testes de unidade, testes de integração e assim por diante). Em vez disso, você deve testar o desempenho da carga de trabalho de ponta a ponta em condições de falha, que ocorrem apenas intermitentemente. Por exemplo: injetar falhas ao travar processos, certificados expirados, tornar serviços dependentes indisponíveis etc. Estruturas como o macaco do caos podem ser usadas para esses testes de caos.
A resiliência do aplicativo é essencial para lidar com operações solicitadas problemáticas. Mas, é apenas metade da história. Em seguida, abordamos os recursos de resiliência disponíveis na nuvem do Azure.