En esta publicación, mostramos el enfoque correcto para usar la clase HttpClient. Además, destacamos cómo el mal uso de esta función puede comprometer el rendimiento de un sistema.
Un gran sitio de comercio electrónico, en un pasado no muy lejano, estaba experimentando momentos intermitentes de inestabilidad.
Teníamos una solución APM que monitoreaba el nivel operativo de GC y el volumen de asignación de CPU, pero no notamos nada extraño sobre estos indicadores. Entonces sospechamos problemas de red
Usando perfmon, hemos visto un fuerte crecimiento en la cantidad de Conexiones realizadas y Conexiones activas. En ese momento, parecía prudente comenzar las investigaciones en puntos del sistema que “aprovechaban” los servicios externos.
Examinando el código, como era de esperar, encontramos un uso ineficiente de la clase HttpClient.
Cómo NO usar HttpClient
Aunque no se recomienda, usar HttpClient como se indica en el siguiente código es bastante común:
public class Version1 { private readonly string _baseUrl; public Version1(string baseUrl) { _baseUrl = baseUrl; } public string GetData(string query) { string result; using (HttpClient client = new HttpClient()) { client.BaseAddress = new Uri(_baseUrl); . . . } return result; } }
En el código anterior, cada vez que se llama al método GetData()
se crea una instancia de HttpClient. Debido a que está en un bloque using, esta instancia es siempre descartada correctamente (al menos eso es lo que esperamos).
El problema es que este enfoque va en contra de las propias recomendaciones de uso de Microsoft.
HttpClient está diseñado para instanciarse una vez y reutilizarse durante toda la vida útil de una aplicación. Crear una instancia de una clase HttpClient para cada request reducirá el número de sockets disponibles con cargas pesadas. Esto provocará errores de SocketException.
Cómo usar HttpClient
Es una buena práctica, indicada por Microsoft, mantener una única instancia de HttpClient en una variable estática para toda la aplicación. Modernamente, se recomienda usar IHttpClientFactory.
public class Version2 { private static readonly HttpClient HttpClient = new HttpClient(); public async string GetData(string url) { var req = await HttpClient.GetAsync(url); if (req.StatusCode != System.Net.HttpStatusCode.OK) throw new HttpRequestException($"{req.StatusCode}-{req.RequestMessage}"); var result = await req.Content.ReadAsStringAsync(); return retorno; } }
Como señala Microsoft, no tenemos que preocuparnos por las requests concurrentes.
Cada instancia de HttpClient tiene su propio grupo de conexiones, aislando sus requests.
Conclusiones
Hay tres causas comunes para el bajo rendimiento de la aplicación: red, disco y GC. Sin embargo, también puede haber problemas debido al agotamiento de otros recursos del sistema operativo.
En esta publicación, demostramos que los cambios de código simples permiten mejoras importantes para las aplicaciones y pueden disminuir la inestabilidad.
El error que presentamos aquí es el resultado de una mala práctica , extremadamente común, incluso claramente contraindicado en la documentación. Esto solo refuerza nuestra recomendación de que todos estudien los recursos utilizados un poco más antes de ponerlos en producción.