在WebAPI客户端中每次调用创build一个新的HttpClient的开销是多less?
什么应该是WebAPI客户端的HttpClient
生存期?
有一个HttpClient
实例多个呼叫是更好吗?
什么是创build和处置HttpClient
每个请求的开销,如下面的例子(取自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
已被devise为重复使用多个电话 。 即使跨多个线程。 HttpClientHandler
具有证书和Cookie,旨在跨通话重复使用。 拥有一个新的HttpClient
实例需要重新设置所有这些东西。 此外, DefaultRequestHeaders
属性包含用于多个调用的属性。 不得不在每个请求上重置这些值都会使这一点失败。
HttpClient
另一个主要优点是可以将HttpMessageHandlers
添加到请求/响应pipe道中,以应用交叉切割问题。 这些可以用于logging,审计,限制,redirect处理,离线处理,捕获指标。 各种不同的东西。 如果每个请求都创build一个新的HttpClient,则需要在每个请求上设置所有这些消息处理程序,并且还需要提供在这些处理程序的请求之间共享的任何应用程序级别状态。
你使用HttpClient
的function越多,你会看到重复使用现有的实例就越有意义。
然而,我认为最大的问题是,当处理一个HttpClient
类时,它会configurationHttpClientHandler
,然后强制closures由ServicePointManager
pipe理的连接池中的TCP/IP
连接。 这意味着每个带有新的HttpClient
请求都需要重新build立一个新的TCP/IP
连接。
从我的testing中,在局域网上使用普通的HTTP,性能命中率是微不足道的。 我怀疑这是因为有一个底层的TCP keepalive即使HttpClientHandler
试图closures时仍保持连接打开。
通过互联网的请求,我看到了一个不同的故事。 由于每次都不得不重新打开请求,所以我看到了40%的性能问题。
我怀疑HTTPS
连接的打击会更糟糕。
我的build议是保持你的应用程序的生命周期HttpClient的实例为每个不同的API连接到。
如果你想要你的应用程序的规模,差异是巨大的! 根据负载的不同,您将看到非常不同的性能数字。 正如Darrel Miller提到的那样,HttpClient被devise为跨请求重用。 BCL团队的编写人员证实了这一点。
我最近的一个项目是帮助一个非常大的知名在线电脑零售商扩大黑色星期五/假日交通的一些新系统。 我们遇到了一些使用HttpClient的性能问题。 由于它实现了IDisposable
,因此开发人员通过创build一个实例并将其放在using()
语句中,来完成通常要做的事情。 一旦我们开始加载testing的应用程序带来了服务器的膝盖 – 是的,服务器不只是应用程序。 原因是HttpClient的每个实例都在服务器上打开一个端口。 由于GC的非确定性终结以及您正在使用跨越多个OSI层的计算机资源的事实,closuresnetworking端口可能需要一段时间。 实际上Windows操作系统本身可能需要长达20秒才能closures一个端口(每个微软)。 我们打开端口的速度比它们可能被closures的速度快 – 服务器端口耗尽,导致CPU达到100%。 我的修复是将HttpClient更改为解决问题的静态实例。 是的,这是可以随意使用的资源,但任何开销都远远超过了性能的差异。 我鼓励你做一些负载testing,看看你的应用程序的行为。
您也可以通过https://www.asp.net/web-api/overview/advanced/calling-a-web-api-from-a-net-client查看WebAPI指导页面的文档和示例;
特别注意这个呼吁:
HttpClient旨在实例化一次,并在应用程序的整个生命周期中重新使用。 特别是在服务器应用程序中,为每个请求创build一个新的HttpClient实例将耗尽大负载下可用的套接字数量。 这将导致SocketException错误。
如果您发现需要使用具有不同头文件,基址等的静态HttpClient
,则需要手动创buildHttpRequestMessage
并在HttpRequestMessage
上设置这些值。 然后,使用HttpClient:SendAsync(HttpRequestMessage requestMessage, ...)
与大量网站相关,但不直接与HttpClient相关。 我们所有的服务都有以下代码片段。
// 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;
从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 =真
“您可以使用此属性来确保ServicePoint对象的活动连接不会无限期地保持打开状态。此属性适用于定期重新build立连接(如负载平衡scheme)的情况。
默认情况下,当请求的KeepAlive为true时,MaxIdleTime属性将设置由于不活动而closuresServicePoint连接的超时。 如果ServicePoint具有活动的连接,则MaxIdleTime不起作用,连接将无限期地保持打开状态。
如果ConnectionLeaseTimeout属性设置为-1以外的值,并且经过指定的时间,则通过在该请求中将KeepAlive设置为false来为请求提供服务后,将closures活动的ServicePoint连接。 设置此值将影响由ServicePoint对象pipe理的所有连接。“
当您在CDN或其他要故障转移的端点后面有服务时,此设置可帮助呼叫者跟随您到达新的目的地。 在故障转移后的60秒内,所有呼叫者都应该重新连接到新端点。 它确实需要你知道你的依赖服务(你称之为那些服务)和他们的端点。
你也可以参考Simon Timms的博客文章: https : //aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/
但是
HttpClient
是不同的。 虽然它实现了IDisposable
接口,但它实际上是一个共享对象。 这意味着在它下面是可重入的)和线程安全的。 不要为每次执行创build一个新的HttpClient
实例,而应该在应用程序的整个生命周期中共享一个HttpClient
实例。 我们来看看为什么。
正如其他答案的状态, HttpClient
是为了重用。 但是,在multithreading应用程序中重复使用单个HttpClient
实例意味着您不能更改其有状态属性的值,如BaseAddress
和DefaultRequestHeaders
(因此,如果它们在应用程序中保持不变,则只能使用它们)。
解决这个限制的一种方法是用一个类来复制HttpClient
,这个类复制了你所需要的所有HttpClient
方法( GetAsync
, PostAsync
等),并把它们委托给一个单独的HttpClient
。 然而,这是非常乏味的(你也需要包装扩展方法 ),幸运的是还有另一种方法 – 不断创build新的HttpClient
实例,但重用底层的HttpClientHandler
。 只要确保你不处理处理程序:
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) } }; }