Neste post, mostramos a abordagem correta para o uso da classe HttpClient. Além disso, destacamos como o uso incorreto dessa feature pode comprometer a performance de um sistema.
Um grande e-commerce, em um passado não muito distante, passava por momentos de instabilidade que ocorriam de forma intermitente.
A solução de APM já monitorava, de maneira satisfatória, o nível de operação do GC e o volume de alocação das CPUs. Entretanto, não percebíamos nada de estranho nesses indicadores. Nossas suspeitas recaíram sobre a rede.
Utilizando o perfmon, constatamos forte crescimento no número de Conexões estabelecidas e de Conexões ativas (ambos recursos do sistema operacional). Na época, pareceu sensato começar as investigações em pontos do sistema que “batiam” em serviços externos.
Examinando o código, sem muitas surpresas, identificamos utilização ineficiente da classe HttpClient.
Como NÃO usar HttpClient
Embora não recomendada, a utilização de HttpClient como indicado no código abaixo é bastante comum:
public class Versao1 { private readonly string _baseUrl; public Versao1(string baseUrl) { _baseUrl = baseUrl; } public string Obter(string query) { string retorno; using (HttpClient client = new HttpClient()) { client.BaseAddress = new Uri(_baseUrl); . . . } return retorno; } }
No código acima, toda vez que o método Obter()
é chamado uma instância de HttpClient é criada. Por estar em um bloco using, esta instância é descartada (pelo menos é o que esperamos).
O problema é que essa abordagem contraria a recomendação de uso da própria Microsoft.
HttpClient destina-se a ser instanciado uma vez e reutilizadas em toda a vida útil de um aplicativo. Criando uma instância de uma classe HttpClient para cada solicitação irá esgotar o número de soquetes disponíveis com cargas pesadas. Isso resultará em erros SocketException.
Isso acontece porque, embora HttpClient implemente a interface IDisposable (por favor, leia esse post para entender mais sobre a tratativa de recursos não gerenciados), “por baixo do capô”, ele mantem estado compartilhado e thread safe entre diversas instâncias.
O vídeo abaixo mostra o impacto dessa abordagem inicando número de conexões realizadas.
Como usar HttpClient
É boa prática, mais do que recomendada, indicada pela da Microsoft, manter uma única de HttpClient uma instância, em uma variável estática, para toda a aplicação. Modernamente, recomenda-se a utilização de IHttpClientFactory.
public class Versao2 { private static readonly HttpClient HttpClient = new HttpClient(); public async string Obter(string url) { var resultado = await HttpClient.GetAsync(url); if (resultado.StatusCode != System.Net.HttpStatusCode.OK) throw new HttpRequestException($"{resultado.StatusCode}-{resultado.RequestMessage}"); var retorno = await resultado.Content.ReadAsStringAsync(); return retorno; } }
Como a Microsoft descata, não preciamos nos preocupar com requisições de conexões concorrentes.
Cada instância de HttpClient seu próprio pool de conexões, isolando suas requisições.
No vídeo que segue, vemos o impacto dessa mudança simples no consumo de recursos não gerenciados do sistema operacional.
Concluindo
Há três ofensores comuns para performance: Rede, Disco e GC. Entretanto, frequentemente, problemas ocorrem devido a esgotamento de outros recursos do sistema operacional.
Nesse post, mostramos que mudanças simples do código permitem grande melhoria para as aplicações e podem diminuir instabilidade em nossas aplicações.
O erro que apresentamos aqui é resultado de uma má prática, extremamente comum, mesmo claramente contraindicada na documentação. Isso só reforça a nossa recomendação para que todos se aprofundem um pouco sobre o funcionamento das features que utilizam ANTES de colocá-las em produção.
E você, já teve problemas com HttpClient?
Ops acho que usei da forma errada muitas vezes =/
Já vou alterar o meu, obrigado pela excelente dica
Passamos pelo mesmo problema aqui, identificamos centenas de conexões aberta com status Wait usando o nettop (mesmo sem nenhum acesso nas aplicações).
Após alterar para utilizar IHttpClientFactory ou HttpClient estático diminui e muito!
Só há um detalhe sobre usar um singleton do Httpclient, ele não respeita o ttl do dns, esse foi um dos motivos da equipe do aspnetcore ter implementado o IHttpClientFactory
como gerenciar alteração de headers em comunicações concorrentes nesse cenários?
Tenho utilizado o Refit (https://github.com/reactiveui/refit) para as requisições HttpClient e está funcionando bem.
legal, esse eu não conhecia. como está a utilização dele?
Ponto de atenção com operação bloqueante:
HttpClient.GetAsync(url).Result;
Eu discordo do uso de .Result. Pode causar deadlock na operação.
Uma lib que eu tenho usado, inclusive ajuda muito em testes unitários é o https://flurl.dev/
Você está certo. A versão desse post em Espanhol já resolve isso.
Anderson,
Tudo certo? Desculpe a demora pra responder.
Você tem toda razão!
Foi um descuido da minha parte!!! Fiz a correção no POST.
; )
Caramba, usei errado também, obrigado pelo excelente artigo!