¿Cuál es la sobrecarga de crear un nuevo HttpClient por llamada en un cliente WebAPI?


¿Cuál debería ser la vida útil HttpClient de un cliente WebAPI?
¿Es mejor tener una instancia del HttpClient para múltiples llamadas?

¿Cuál es la sobrecarga de crear y disponer de un HttpClient por solicitud, como en el ejemplo a continuación (tomado de http://www.asp.net/web-api/overview/web-api-clients/calling-a-web-api-from-a-net-client):

using (var client = new HttpClient())
{
    client.BaseAddress = new Uri("http://localhost:9000/");
    client.DefaultRequestHeaders.Accept.Clear();
    client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

    // New code:
    HttpResponseMessage response = await client.GetAsync("api/products/1");
    if (response.IsSuccessStatusCode)
    {
        Product product = await response.Content.ReadAsAsync>Product>();
        Console.WriteLine("{0}\t${1}\t{2}", product.Name, product.Price, product.Category);
    }
}
Author: Henk Holterman, 2014-03-21

5 answers

HttpClient ha sido diseñado para ser reutilizado para múltiples llamadas. Incluso a través de múltiples hilos. HttpClientHandler tiene Credenciales y cookies que están destinadas a ser reutilizadas entre llamadas. Tener una nueva instancia HttpClient requiere volver a configurar todas esas cosas. Además, la propiedad DefaultRequestHeaders contiene propiedades destinadas a múltiples llamadas. Tener que restablecer esos valores en cada solicitud derrota el punto.

Otro beneficio importante de HttpClient es la capacidad de añadir HttpMessageHandlers en el canalización de solicitud / respuesta para aplicar preocupaciones transversales. Estos podrían ser para el registro, la auditoría, la limitación, el manejo de redirecciones, el manejo fuera de línea, la captura de métricas. Todo tipo de cosas diferentes. Si se crea un nuevo HttpClient en cada solicitud, entonces todos estos controladores de mensajes deben configurarse en cada solicitud y de alguna manera cualquier estado de nivel de aplicación que se comparta entre las solicitudes de estos controladores también debe proporcionarse.

Cuanto más se utilizan las características de HttpClient, más verá que reutilizar una instancia existente tiene sentido.

Sin embargo, el mayor problema, en mi opinión, es que cuando una clase HttpClient está dispuesta, se deshace de HttpClientHandler, que luego cierra por la fuerza la conexión TCP/IP en el grupo de conexiones que es administrado por ServicePointManager. Esto significa que cada solicitud con un nuevo HttpClient requiere restablecer una nueva conexión TCP/IP.

De mis pruebas, usando HTTP simple en una LAN, el impacto en el rendimiento es bastante insignificante. Sospecho que esto es porque hay un TCP keepalive subyacente que mantiene abierta la conexión incluso cuando HttpClientHandler intenta cerrarla.

En las solicitudes que pasan por Internet, he visto una historia diferente. He visto un golpe de rendimiento del 40% debido a tener que volver a abrir la solicitud cada vez.

Sospecho que el golpe en una conexión HTTPS sería aún peor.

Mi consejo es que mantenga una instancia de HttpClient durante la vida útil de su aplicación para cada API distinta que conéctate.

 166
Author: Darrel Miller,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-07-06 03:04:50

Si quieres que tu aplicación se amplíe, ¡la diferencia es ENORME! Dependiendo de la carga, verá números de rendimiento muy diferentes. Como menciona Darrel Miller, el HttpClient fue diseñado para ser reutilizado a través de solicitudes. Esto fue confirmado por los chicos del equipo de BCL que lo escribieron.

Un proyecto reciente que tuve fue ayudar a un minorista de computadoras en línea muy grande y conocido a escalar el tráfico del Viernes Negro/feriado para algunos sistemas nuevos. Nos encontramos con algunos problemas de rendimiento en torno a la uso de HttpClient. Dado que implementa IDisposable, los desarrolladores hicieron lo que normalmente haría al crear una instancia y colocarla dentro de una instrucción using(). Una vez que comenzamos a probar la carga, la aplicación puso de rodillas al servidor, sí, el servidor no solo la aplicación. La razón es que cada instancia de HttpClient abre un puerto en el servidor. Debido a la finalización no determinista de GC y el hecho de que está trabajando con recursos informáticos que abarcan múltiples capas OSI, cerrar los puertos de red puede llevar un tiempo. De hecho, el propio sistema operativo Windows puede tardar hasta 20 segundos en cerrar un puerto (por Microsoft). Estábamos abriendo puertos más rápido de lo que podían ser cerrados - agotamiento del puerto del servidor que martilló la CPU al 100%. Mi solución fue cambiar el HttpClient a una instancia estática que resolvía el problema. Sí, es un recurso desechable, pero cualquier sobrecarga es ampliamente compensada por la diferencia en el rendimiento. Te animo a hacer algunas pruebas de carga para ver cómo tu aplicación comportar.

También puede consultar la página de guía de WebAPI para la documentación y el ejemplo en https://www.asp.net/web-api/overview/advanced/calling-a-web-api-from-a-net-client

Presta especial atención a esta llamada:

HttpClient está destinado a ser instanciado una vez y reutilizado durante toda la vida de una aplicación. Especialmente en aplicaciones de servidor, la creación de una nueva instancia HttpClient para cada solicitud agotará el número de sockets disponibles bajo cargas pesadas. Esto dará lugar a errores SocketException.

Si encuentra que necesita usar un HttpClient estático con diferentes encabezados, dirección base, etc. lo que tendrá que hacer es crear el HttpRequestMessage manualmente y establecer esos valores en el HttpRequestMessage. A continuación, utilice el HttpClient:SendAsync(HttpRequestMessage requestMessage, ...)

 57
Author: Dave Black,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-10-10 19:28:41

Como dicen las otras respuestas, HttpClient está destinado a ser reutilizado. Sin embargo, reutilizar una única instancia HttpClient en una aplicación multihilo significa que no puede cambiar los valores de sus propiedades con estado, como BaseAddress y DefaultRequestHeaders (por lo que solo puede usarlos si son constantes en toda la aplicación).

Un enfoque para sortear esta limitación es envolver HttpClient con una clase que duplique todos los métodos HttpClient que necesita (GetAsync, PostAsync etc) y los delega a un singleton HttpClient. Sin embargo, eso es bastante tedioso (necesitará envolver los métodos de extensión también), y afortunadamente hay otra forma: seguir creando nuevas instancias HttpClient, pero reutilizar el HttpClientHandler subyacente. Solo asegúrese de no deshacerse del controlador:

HttpClientHandler _sharedHandler = new HttpClientHandler(); //never dispose this
HttpClient GetClient(string token)
{
    //client code can dispose these HttpClient instances
    return new HttpClient(_sharedHandler, disposeHandler: false)         
    {
       DefaultRequestHeaders = 
       {
            Authorization = new AuthenticationHeaderValue("Bearer", token) 
       } 
    };
}
 6
Author: Ohad Schneider,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-07-08 17:42:33

Relacionado con sitios web de alto volumen pero no directamente con HttpClient. Tenemos el fragmento de código a continuación en todos nuestros servicios.

// number of milliseconds after which an active System.Net.ServicePoint connection is closed.
const int DefaultConnectionLeaseTimeout = 60000;

ServicePoint sp = ServicePointManager.FindServicePoint(new Uri("http://<yourServiceUrlHere>"));
sp.ConnectionLeaseTimeout = DefaultConnectionLeaseTimeout;

Desde https://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k(System.Net.ServicePoint.ConnectionLeaseTimeout);k(TargetFrameworkMoniker-.NETFramework,Version%3Dv4.5.2);k(DevLang-csharp)&rd=true

"Puede usar esta propiedad para asegurarse de que las conexiones activas de un objeto ServicePoint no permanezcan abiertas indefinidamente. Esta propiedad está diseñada para escenarios en los que las conexiones se deben eliminar y restablecer periódicamente, como escenarios de equilibrio de carga.

De forma predeterminada, cuando KeepAlive es true para una solicitud, la propiedad maxIdleTime establece el tiempo de espera para cerrar conexiones de ServicePoint debido a la inactividad. Si el ServicePoint tiene conexiones activas, maxIdleTime no tiene efecto y las conexiones permanecen abiertas indefinidamente.

Cuando la propiedad ConnectionLeaseTimeout se establece en un valor distinto de -1, y después de que transcurra el tiempo especificado, una conexión de ServicePoint activa se cierra después de atender una solicitud configurando KeepAlive en false en esa solicitud. Establecer este valor afecta a todas las conexiones administradas por el objeto ServicePoint."

Cuando tiene servicios detrás de una CDN u otro endpoint que desea conmutar por error, esta configuración ayuda a las personas que llaman a seguirle a su nuevo destino. En este ejemplo, 60 segundos después de una conmutación por error, todas las personas que llaman deben volver a conectarse al nuevo punto final. Se requiere que conozca sus servicios dependientes (aquellos servicios a los que llama) y sus puntos finales.

 3
Author: No Refunds No Returns,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-07-13 16:35:06

Es posible que también desee hacer referencia a esta entrada de blog de Simon Timms: https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong /

Pero HttpClient es diferente. Aunque implementa la interfaz IDisposable en realidad es un objeto compartido. Esto significa que bajo las cubiertas es reentrante) y hilo seguro. En lugar de crear una nueva instancia de HttpClient para cada ejecución, debe compartir una sola instancia de HttpClient durante toda la vida útil de la aplicación. Veamos por qué.

 0
Author: SvenAelterman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-06-20 17:41:06