Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Dica
Esse conteúdo é um trecho do eBook, arquitetura de microsserviços do .NET para aplicativos .NET em contêineres, disponível em do .NET Docs ou como um PDF para download gratuito que pode ser lido offline.
Conforme observado anteriormente, você deve lidar com falhas que podem levar um tempo variável para se recuperar, como pode acontecer quando você tenta se conectar a um serviço ou recurso remoto. Lidar com esse tipo de falha pode melhorar a estabilidade e a resiliência de um aplicativo.
Em um ambiente distribuído, as chamadas para recursos e serviços remotos podem falhar devido a falhas transitórias, como conexões de rede lentas e tempos limite, ou se os recursos estiverem respondendo lentamente ou estiverem temporariamente indisponíveis. Essas falhas normalmente se corrigem após um curto período de tempo e um aplicativo de nuvem robusto deve estar preparado para lidar com elas usando uma estratégia como o "padrão de repetição".
No entanto, também pode haver situações em que as falhas são devido a eventos imprevistos que podem levar muito mais tempo para serem corrigidos. Essas falhas podem variar de gravidade de uma perda parcial de conectividade até a falha completa de um serviço. Nessas situações, pode ser inútil para um aplicativo repetir continuamente uma operação que é improvável de ter êxito.
Em vez disso, o aplicativo deve ser codificado para aceitar que a operação falhou e lidar com a falha adequadamente.
O uso de retries HTTP de forma descuidada pode resultar na criação de um ataque de Negação de Serviço (DoS) no seu próprio software. Como um microsserviço falha ou é executado lentamente, vários clientes podem repetir repetidamente solicitações com falha. Isso cria um risco perigoso de aumentar exponencialmente o tráfego direcionado ao serviço com falha.
Portanto, você precisa de algum tipo de barreira de defesa para que as solicitações excessivas parem quando não vale a pena continuar tentando. Essa barreira de defesa é precisamente o disjuntor.
O padrão de disjuntor tem uma finalidade diferente do "padrão de repetição". O "padrão de repetição" permite que um aplicativo repita uma operação na expectativa de que a operação seja bem-sucedida. O padrão disjuntor impede que um aplicativo execute uma operação que provavelmente falhará. Um aplicativo pode combinar esses dois padrões. No entanto, a lógica de repetição deve ser sensível a qualquer exceção retornada pelo disjuntor e deve abandonar as tentativas de repetição se o disjuntor indicar que uma falha não é transitória.
Implementar o padrão de Disjuntor com IHttpClientFactory
e Polly
Como durante a implementação de repetições, a abordagem recomendada para disjuntores é aproveitar as comprovadas bibliotecas do .NET, como a Polly e sua integração nativa com o IHttpClientFactory
.
Adicionar uma política de disjuntor no pipeline do middleware de saída do IHttpClientFactory
é tão simples quanto adicionar uma única parte incremental de código ao que você já tem ao usar o IHttpClientFactory
.
A única adição aqui ao código usado para tentativas de chamadas HTTP é o código em que você adiciona a política de Circuit Breaker à lista de políticas a serem aplicadas, conforme mostrado no código incremental a seguir.
// Program.cs
var retryPolicy = GetRetryPolicy();
var circuitBreakerPolicy = GetCircuitBreakerPolicy();
builder.Services.AddHttpClient<IBasketService, BasketService>()
.SetHandlerLifetime(TimeSpan.FromMinutes(5)) // Sample: default lifetime is 2 minutes
.AddHttpMessageHandler<HttpClientAuthorizationDelegatingHandler>()
.AddPolicyHandler(retryPolicy)
.AddPolicyHandler(circuitBreakerPolicy);
O AddPolicyHandler()
método é o que adiciona políticas aos HttpClient
objetos que você usará. Nesse caso, ele está adicionando uma política da Polly a um disjuntor.
Para ter uma abordagem mais modular, a Política de Disjuntor é definida em um método separado chamado GetCircuitBreakerPolicy()
, conforme mostrado no seguinte código:
// also in Program.cs
static IAsyncPolicy<HttpResponseMessage> GetCircuitBreakerPolicy()
{
return HttpPolicyExtensions
.HandleTransientHttpError()
.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30));
}
No exemplo de código acima, a política de disjuntor é configurada para que o circuito seja interrompido ou aberto quando houver cinco falhas consecutivas ao tentar novamente as requisições HTTP. Quando isso acontece, o circuito será interrompido por 30 segundos: nesse período, chamadas falharão imediatamente pelo disjuntor em vez de serem colocadas. A política interpreta automaticamente exceções relevantes e códigos de status HTTP como falhas.
Os disjuntores também devem ser usados para redirecionar solicitações para uma infraestrutura de fallback se você tiver problemas em um recurso específico implantado em um ambiente diferente do aplicativo ou serviço cliente que está executando a chamada HTTP. Dessa forma, se houver uma interrupção no datacenter que afeta apenas seus microsserviços de back-end, mas não seus aplicativos cliente, os aplicativos cliente poderão redirecionar para os serviços de fallback. O Polly está planejando uma nova política para automatizar esse cenário de política de failover.
Todos esses recursos são para casos em que você está gerenciando o failover de dentro do código do .NET, em vez de deixar que o Azure o gerencie automaticamente com transparência de local.
Do ponto de vista de uso, ao usar HttpClient, não é necessário adicionar nada novo aqui porque o código é o mesmo que quando usado HttpClient
com IHttpClientFactory
, conforme mostrado nas seções anteriores.
Testar repetições de HTTP e disjuntores no eShopOnContainers
Sempre que você iniciar a solução eShopOnContainers em um host do Docker, ela precisará iniciar vários contêineres. Alguns dos contêineres são mais lentos para iniciar e inicializar, como o contêiner do SQL Server. Isso é especialmente verdadeiro na primeira vez que você implanta o aplicativo eShopOnContainers no Docker porque ele precisa configurar as imagens e o banco de dados. O fato de alguns contêineres começarem mais lentos do que outros pode fazer com que o restante dos serviços inicialmente gere exceções HTTP, mesmo se você definir dependências entre contêineres no nível do docker-compose, conforme explicado nas seções anteriores. Essas dependências de docker-compose entre contêineres estão apenas no nível do processo. O processo de ponto de entrada do contêiner pode ser iniciado, mas o SQL Server pode não estar pronto para consultas. O resultado pode ser uma cascata de erros e o aplicativo pode obter uma exceção ao tentar consumir esse contêiner específico.
Você também pode ver esse tipo de erro na inicialização quando o aplicativo está sendo implantado na nuvem. Nesse caso, os orquestradores poderão estar movendo contêineres de um nó ou VM para outro (ou seja, começando novas instâncias) ao equilibrar o número de contêineres entre nós do cluster.
A maneira como 'eShopOnContainers' resolve esses problemas ao iniciar todos os contêineres é usando o padrão de repetição ilustrado anteriormente.
Testar o disjuntor no eShopOnContainers
Há algumas maneiras de interromper/abrir o circuito e testá-lo com eShopOnContainers.
Uma opção é reduzir o número permitido de repetições para 1 na política de disjuntor e reimplantar toda a solução no Docker. Com uma única repetição, há uma boa chance de uma solicitação HTTP falhar durante a implantação, o disjuntor será aberto e você receberá um erro.
Outra opção é usar o middleware personalizado implementado no microsserviço Basket . Quando esse middleware está habilitado, ele captura todas as solicitações HTTP e retorna o código de status 500. Você pode habilitar o middleware fazendo uma solicitação GET para o URI com falha, como o seguinte:
GET http://localhost:5103/failing
Essa solicitação retorna o estado atual do middleware. Se o middleware estiver habilitado, a solicitação retornará o código de status 500. Se o middleware estiver desabilitado, não haverá resposta.GET http://localhost:5103/failing?enable
Essa solicitação habilita o middleware.GET http://localhost:5103/failing?disable
Essa solicitação desabilita o middleware.
Por exemplo, depois que o aplicativo estiver em execução, você poderá habilitar o middleware fazendo uma solicitação usando o URI a seguir em qualquer navegador. Observe que o microsserviço de ordenação usa a porta 5103.
http://localhost:5103/failing?enable
Em seguida, você pode verificar o status usando o URI http://localhost:5103/failing
, conforme mostrado na Figura 8-5.
Figura 8-5. Verificando o estado do middleware ASP.NET com “Falha”. Neste caso, desabilitado.
Neste ponto, o microsserviço Basket responde com o código de status 500 sempre que você o chama.
Depois que o middleware estiver em execução, você poderá tentar fazer um pedido do aplicativo Web MVC. Como as solicitações falham, o circuito é aberto.
No exemplo a seguir, você pode observar que o aplicativo web MVC tem um bloco de captura na lógica de realização de pedido. Se o código captura uma exceção de circuito aberto, ele mostra ao usuário uma mensagem amigável dizendo-lhe para esperar.
public class CartController : Controller
{
//…
public async Task<IActionResult> Index()
{
try
{
var user = _appUserParser.Parse(HttpContext.User);
//Http requests using the Typed Client (Service Agent)
var vm = await _basketSvc.GetBasket(user);
return View(vm);
}
catch (BrokenCircuitException)
{
// Catches error when Basket.api is in circuit-opened mode
HandleBrokenCircuitException();
}
return View();
}
private void HandleBrokenCircuitException()
{
TempData["BasketInoperativeMsg"] = "Basket Service is inoperative, please try later on. (Business message due to Circuit-Breaker)";
}
}
Aqui está um resumo. A política de repetição tenta várias vezes realizar a solicitação HTTP e encontra erros HTTP. Quando o número de repetições atinge o número máximo definido para a política de Disjuntor (nesse caso, 5), o aplicativo gera uma BrokenCircuitException. O resultado é uma mensagem amigável, conforme mostrado na Figura 8-6.
Figura 8-6. Disjuntor retornando um erro na interface do usuário
Você pode implementar uma lógica diferente para quando abrir/quebrar o circuito. Ou você poderá tentar uma solicitação HTTP para um microsserviço de back-end diferente se houver um datacenter de fallback ou um sistema de back-end redundante.
Por fim, outra possibilidade para o CircuitBreakerPolicy
é usar Isolate
(que força e mantém o circuito aberto) e Reset
(que o fecha novamente). Isso pode ser usado para criar um ponto de extremidade HTTP de utilitário que invoque Isolar e Reiniciar diretamente na política. Esse endpoint HTTP também pode ser usado, adequadamente protegido, em produção para isolar temporariamente um sistema localizado posteriormente, como quando é necessário atualizá-lo. Ou poderia desarmar o circuito manualmente para proteger o sistema a downstream que você suspeita ter falha.
Recursos adicionais
-
Padrão de disjuntor
https://learn.microsoft.com/azure/architecture/patterns/circuit-breaker