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

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

¿Cuál es la sobrecarga de crear y eliminar un HttpClient por solicitud, como en el siguiente ejemplo (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); } } 

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 en llamadas. Tener una nueva instancia de HttpClient requiere volver a configurar todo eso. 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 agregar HttpMessageHandlers a la canalización de solicitud / respuesta para aplicar problemas de corte transversal. Estos podrían ser para el registro, la auditoría, la regulación, el manejo de redireccionamiento, el manejo sin conexión, la captura de métricas. Todo tipo de cosas diferentes Si se crea un nuevo HttpClient en cada solicitud, todos estos manejadores de mensajes deben configurarse en cada solicitud y, de alguna manera, también se debe proporcionar cualquier estado de nivel de aplicación que se comparta entre las solicitudes de estos manejadores.

Cuanto más utilice las funciones de HttpClient , más verá que tiene sentido reutilizar una instancia existente.

Sin embargo, el mayor problema, en mi opinión, es que cuando se elimina una clase HttpClient , dispone de HttpClientHandler , que luego cierra a la fuerza la TCP/IP en el conjunto de conexiones administradas por ServicePointManager . Esto significa que cada solicitud con un nuevo HttpClient requiere el restablecimiento de una nueva TCP/IP .

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

En solicitudes que pasan por Internet, he visto una historia diferente. He visto un 40% de rendimiento 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 mantener una instancia de HttpClient durante toda la vida de su aplicación para cada API distinta a la que se conecte.

Si desea que su aplicación se amplíe, ¡la diferencia es GRANDE! Dependiendo de la carga, verá números de rendimiento muy diferentes. Como menciona Darrell Miller, el HttpClient fue diseñado para ser reutilizado en todas las solicitudes. Esto fue confirmado por los chicos del equipo de BCL que lo escribieron.

Un proyecto reciente que tuve fue para ayudar a un minorista de computadoras en línea muy grande y conocido a escalar el tráfico del Viernes Negro / vacaciones para algunos sistemas nuevos. Nos encontramos con algunos problemas de rendimiento relacionados con el uso de HttpClient. Como implementa IDisposable , los desarrolladores hicieron lo que normalmente harían al crear una instancia y colocarla dentro de una sentencia using() . Una vez que comenzamos con las pruebas de carga, la aplicación puso al servidor de rodillas, 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 al hecho de que está trabajando con recursos informáticos que abarcan varias capas de OSI , el cierre de los puertos de red puede llevar un tiempo. De hecho, el sistema operativo Windows puede demorar hasta 20 segundos para cerrar un puerto (por Microsoft). Estábamos abriendo puertos más rápido de lo que podrían estar cerrados: agotamiento del puerto del servidor que golpeó la CPU al 100%. Mi solución fue cambiar el HttpClient a una instancia estática que resolvió el problema. Sí, es un recurso desechable, pero cualquier sobrecarga está ampliamente compensada por la diferencia en el rendimiento. Te animo a que hagas algunas pruebas de carga para ver cómo se comporta tu aplicación.

También puede consultar la página de orientación de WebAPI para obtener documentación y ejemplos en https://www.asp.net/web-api/overview/advanced/calling-a-web-api-f-a-net-client

Preste especial atención a esta llamada:

HttpClient está destinado a ser instanciado una vez y reutilizado a lo largo de la vida de una aplicación. Especialmente en aplicaciones de servidor, la creación de una nueva instancia de HttpClient para cada solicitud agotará la cantidad de sockets disponibles bajo cargas pesadas. Esto dará como resultado errores SocketException.

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

Como las otras respuestas indican, HttpClient está diseñado para ser reutilizado. Sin embargo, reutilizar una única instancia de HttpClient en una aplicación de subprocesos múltiples significa que no puede cambiar los valores de sus propiedades de estado, como BaseAddress y DefaultRequestHeaders (por lo que solo puede usarlos si son constantes en su aplicación).

Un enfoque para evitar esta limitación es envolver HttpClient con una clase que duplica todos los métodos HttpClient que necesita ( GetAsync , PostAsync , etc.) y los delega en un HttpClient . Sin embargo, eso es bastante tedioso (también deberá ajustar los métodos de extensión ) y, afortunadamente, hay otra manera : seguir creando nuevas instancias de HttpClient , pero reutilizar el HttpClientHandler subyacente. Solo asegúrate de no tirar el 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) } }; } 

Relacionado con sitios web de gran 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://")); sp.ConnectionLeaseTimeout = DefaultConnectionLeaseTimeout; 

De 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á pensada para escenarios en los que las conexiones se deben descartar y restablecer periódicamente, como los escenarios de equilibrio de carga.

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

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

Cuando tiene servicios detrás de un CDN u otro punto final que desea conmutar por error, esta configuración ayuda a los llamantes a seguirlo hasta 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. Requiere que conozca sus servicios dependientes (los servicios que USTED llama) y sus puntos finales.

También es posible que desee consultar esta publicación 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 debajo de las cubiertas es reentrante) y seguro para hilos. En lugar de crear una nueva instancia de HttpClient para cada ejecución, debe compartir una única instancia de HttpClient durante toda la vida útil de la aplicación. Veamos por qué.