WebAPI istemcisinde arama başına yeni bir HttpClient oluşturmanın yükü nedir?


162

HttpClientWebAPI istemcisinin ömrü ne olmalıdır ? Birden fazla çağrı için
bir örneğine sahip olmak daha mı iyi HttpClient?

HttpClientAşağıdaki gibi bir istek başına bir oluşturma ve atma yükü nedir ( http://www.asp.net/web-api/overview/web-api-clients/calling-a-web-api-from- a-net-client ) ile ilişkili olan kısmını dışarı aktarmak suretiyle yedek oluşturmanız gerekir :

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);
    }
}

Emin değilim Stopwatch, ancak sınıf karşılaştırmak için kullanabilirsiniz . Benim tahminim HttpClient, tüm bu örneklerin aynı bağlamda kullanıldığı varsayılarak, tekli olmak daha mantıklı olacaktır .
Matthew

Yanıtlar:


215

HttpClient, birden fazla çağrı için yeniden kullanılmak üzere tasarlanmıştır . Birden çok iş parçacığında bile. HttpClientHandlerTekrar kullanılan çağrıları arasında olması amaçlanmıştır Kimlik ve Çerezler vardır. Yeni bir HttpClientörneğe sahip olmak, tüm bu şeylerin yeniden ayarlanmasını gerektirir. Ayrıca, DefaultRequestHeadersözellik birden çok çağrıya yönelik özellikler içerir. Her istekte bu değerlerin sıfırlanması zorunluluktur.

Diğer bir önemli yararı, kesişen endişeleri uygulamak için talep / yanıt boru hattına HttpClientekleme yeteneğidir HttpMessageHandlers. Bunlar günlüğe kaydetme, denetim, azaltma, yönlendirme yönetimi, çevrimdışı işleme, metrikleri yakalamak için olabilir. Her türlü farklı şey. Her istekte yeni bir HttpClient oluşturulduysa, bu ileti işleyicilerinin hepsinin her istekte ayarlanması ve bir şekilde bu işleyicilerin istekleri arasında paylaşılan herhangi bir uygulama düzeyi durumunun da sağlanması gerekir.

Özelliklerini ne kadar çok kullanırsanız, HttpClientmevcut bir örneği yeniden kullanmanın daha mantıklı olduğunu göreceksiniz.

Ancak, en büyük sorun, bence bir HttpClientsınıf atıldığında, yönetilen bağlantı havuzundaki bağlantıyı HttpClientHandlerzorla kapatacak şekilde atıyor . Bu, yeni olan her isteğin yeni bir bağlantının yeniden kurulmasını gerektirdiği anlamına gelir .TCP/IPServicePointManagerHttpClientTCP/IP

Testlerimden, bir LAN üzerinde düz HTTP kullanarak, performans isabeti oldukça ihmal edilebilir. Bunun nedeni, bağlantıyı HttpClientHandlerkapatmaya çalışsa bile bağlantıyı açık tutan altta yatan bir TCP tutma özelliği olduğundan şüpheleniyorum .

İnternet üzerinden yapılan taleplerde farklı bir hikaye gördüm. İsteği her seferinde yeniden açmak zorunda kaldığım için% 40 performans artışı gördüm.

Bir HTTPSbağlantı üzerindeki isabetin daha da kötü olacağını düşünüyorum.

Benim tavsiyem, bağlandığınız her farklı API için uygulamanızın ömrü boyunca bir HttpClient örneğini saklamaktır .


5
which then forcibly closes the TCP/IP connection in the pool of connections that is managed by ServicePointManagerBu ifadeden emin misin? Buna inanmak zor. HttpClientbana sık sık örneklenmesi gereken bir iş birimi gibi geliyor.
usr

2
@vkelman Evet, yeni bir HttpClientHandler ile oluşturmuş olsanız bile HttpClient'in bir örneğini yeniden kullanabilirsiniz. Ayrıca, HttpClient için bir HttpClientHandler'ı yeniden kullanmanıza ve bağlantıyı öldürmeden HttpClient'i atmanıza izin veren özel bir yapıcı olduğuna dikkat edin.
Darrel Miller

2
@vkelman HttpClient'i çevresinde tutmayı tercih ederim, ancak HttpClientHandler'ı saklamayı tercih ederseniz, ikinci parametre yanlış olduğunda bağlantıyı açık tutacaktır.
Darrel Miller

2
@DarrelMiller Yani bağlantı HttpClientHandler'a bağlı gibi görünüyor. Ben bir HttpClientHandler çevresinde tutmak ve tüm o HttpClient örnekleri oluşturmak VEYA statik bir HttpClient örneği oluşturmak gerekir bu yüzden bağlantıyı yok etmek istemiyorum biliyorum. Ancak, CookieContainer HttpClientHandler'a bağlıysa ve çerezlerimin istek başına farklı olması gerekiyorsa, ne önerirsiniz? Her istek için kendi CookieContainer değiştirerek statik bir HttpClientHandler iş parçacığı eşitleme önlemek istiyorum.
Dave Black

2
@ Sana.91 Yapabilirsin. Hizmet koleksiyonuna singleton olarak kaydetmek ve bu şekilde erişmek daha iyi olur.
Darrel Miller

69

Uygulamanızın ölçeklendirilmesini istiyorsanız, fark BÜYÜK! Yüke bağlı olarak, çok farklı performans numaraları göreceksiniz. Darrel Miller'in belirttiği gibi, HttpClient, istekler arasında yeniden kullanılmak üzere tasarlanmıştır. Bu, bunu yazan BCL ekibindeki çocuklar tarafından doğrulandı.

Son zamanlarda yaptığım bir proje, bazı yeni sistemler için Kara Cuma / tatil trafiği için çok büyük ve iyi bilinen bir çevrimiçi bilgisayar perakendecisinin ölçeklendirilmesine yardımcı olmaktı. HttpClient kullanımı ile ilgili bazı performans sorunlarıyla karşılaştık. Uygulama IDisposablegeliştirdiklerinden, geliştiriciler normalde bir örnek oluşturarak ve bunu bir using()ifadenin içine yerleştirerek yaptıklarınızı yaptılar . Yük testine başladığımızda, uygulama sunucuyu dizlerine getirdi - evet, sunucu sadece uygulamayı değil. Bunun nedeni, her HttpClient örneğinin sunucuda bir bağlantı noktası açmasıdır. GC'nin deterministik olmayan sonlandırılması ve birden fazla OSI katmanına yayılan bilgisayar kaynaklarıyla çalıştığınız için , ağ bağlantı noktalarını kapatmak biraz zaman alabilir. Aslında Windows işletim sisteminin kendisibir bağlantı noktasını kapatmak 20 saniyeye kadar sürebilir (her Microsoft için). Portları kapatılabileceğinden daha hızlı açıyorduk - CPU'yu% 100'e çeken sunucu portu tükenmesi. Benim düzeltme HttpClient sorunu çözen statik bir örneğe değiştirmek oldu. Evet, tek kullanımlık bir kaynaktır, ancak herhangi bir genel gider, performanstaki farktan büyük ölçüde ağır basar. Uygulamanızın nasıl davrandığını görmek için bazı yük testleri yapmanızı öneririz.

Ayrıca https://www.asp.net/web-api/overview/advanced/calling-a-web-api-from-a-net-client adresinden WebAPI Kılavuzu sayfasına da göz atabilirsiniz.

Bu çağrıya özellikle dikkat edin:

HttpClient'in bir kez başlatılması ve bir uygulamanın ömrü boyunca yeniden kullanılması amaçlanmıştır. Özellikle sunucu uygulamalarında, her istek için yeni bir HttpClient örneği oluşturmak, ağır yükler altında kullanılabilen soket sayısını tüketir. Bu SocketException hatalarına neden olur.

HttpClientFarklı başlıklara, temel adreslere vb. Sahip bir statik kullanmanız gerektiğini fark ederseniz, yapmanız gereken HttpRequestMessageelle oluşturmak ve bu değerleri HttpRequestMessage. Ardından,HttpClient:SendAsync(HttpRequestMessage requestMessage, ...)

.NET Core için UPDATE : Örnekler IHttpClientFactoryoluşturmak için via Dependency Injection uygulamasını kullanmalısınız HttpClient. Kullanım ömrünü sizin için yönetir ve açıkça atmanıza gerek yoktur. Bkz . ASP.NET Core'da IHttpClientFactory kullanarak HTTP istekleri yapma


1
Bu yazı stres testi yapacak olanlar için yararlı bilgiler içeriyor ..!
Sana.91

9

Diğer cevapların belirttiği gibi, HttpClientyeniden kullanım içindir. Ancak, HttpClientçok iş parçacıklı bir uygulamada tek bir örneği yeniden kullanmak , BaseAddressve gibi durumsal özelliklerinin değerlerini değiştiremeyeceğiniz anlamına gelir.DefaultRequestHeaders (böylece bunları yalnızca uygulamanızda sabit kalıyorsa kullanabilirsiniz).

Bu sınırlamayı ulaşım için bir yaklaşım sarma olan HttpClientbir bütün kopyalar sınıfla HttpClientsen gerekmez yöntemleri ( GetAsync, PostAsyncbir tekil vb) ve delege onları HttpClient. Bununla birlikte, bu oldukça sıkıcıdır ( uzantı yöntemlerini de sarmanız gerekir ) ve neyse ki başka bir yol var - yeni HttpClientörnekler oluşturmaya devam edin , ancak altta yatanları yeniden kullanın HttpClientHandler. Sadece işleyiciyi atmadığınızdan emin olun:

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) 
       } 
    };
}

2
Gitmenin en iyi yolu bir HttpClient örneğini tutmak ve sonra kendi yerel HttpRequestMessage örneklerinizi oluşturmak ve sonra da HttpClient üzerinde .SendAsync () yöntemini kullanmaktır. Bu şekilde diş açmaya devam edecektir. Her HttpRequestMessage kendi Kimlik Doğrulama / URL değerlerine sahip olacaktır.
Tim

@TimP. neden daha iyi? SendAsyncçok daha az elverişli gibi özel yöntemlerle daha PutAsync, PostAsJsonAsyncvb
Ohad Schneider

2
SendAsync, URL'yi ve başlıklar gibi diğer özellikleri değiştirmenize ve yine de iş parçacığı açısından güvenli olmanıza izin verir.
Tim P.

2
Evet, işleyici anahtardır. HttpClient örnekleri arasında paylaşıldığı sürece iyi durumdasınız. Önceki yorumunuzu yanlış okudum.
Dave Black

1
Paylaşılan bir işleyici tutarsak, eski DNS sorunuyla ilgilenmemize gerek var mı?
shanti

5

Yüksek hacimli web siteleriyle ilgilidir, ancak doğrudan HttpClient ile ilgili değildir. Tüm hizmetlerimizde aşağıdaki kod pasajına sahibiz.

        // 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;

itibaren ) 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

"Bu özelliği, bir ServicePoint nesnesinin etkin bağlantılarının süresiz olarak açık kalmamasını sağlamak için kullanabilirsiniz. Bu özellik, yük dengeleme senaryoları gibi düzenli aralıklarla bağlantıların kesilmesi ve yeniden kurulması gereken senaryolar için tasarlanmıştır.

Varsayılan olarak, KeepAlive bir istek için doğru olduğunda, MaxIdleTime özelliği, hareketsizlik nedeniyle ServicePoint bağlantılarını kapatma zaman aşımını ayarlar. ServicePoint'in etkin bağlantıları varsa, MaxIdleTime'ın bir etkisi yoktur ve bağlantılar süresiz olarak açık kalır.

ConnectionLeaseTimeout özelliği -1 dışında bir değere ayarlandığında ve belirtilen süre geçtikten sonra, isteğe bağlı olarak KeepAlive öğesi false olarak ayarlanarak etkin bir ServicePoint bağlantısı kapatılır. Bu değerin ayarlanması, ServicePoint nesnesi tarafından yönetilen tüm bağlantıları etkiler. "

Bir CDN'nin veya yük devretmek istediğiniz başka bir uç noktanın arkasında hizmetleriniz varsa, bu ayar arayanların sizi yeni hedefinize takip etmelerine yardımcı olur. Bu örnekte, bir yük devretme işleminden 60 saniye sonra tüm arayanlar yeni bitiş noktasına yeniden bağlanmalıdır. Bağımlı hizmetlerinizi (SİZİN çağırdığınız hizmetler) ve bunların uç noktalarını bilmenizi gerektirir.


Bağlantıları açıp kapatarak sunucuya hala büyük miktarda yük yüklemiş olursunuz. Örnek tabanlı HttpClientHandlers ile örnek tabanlı HttpClients kullanıyorsanız, dikkatli olmazsanız yine de bağlantı noktası tükenmeye başlarsınız.
Dave Black

Katılmıyorum. Her şey bir ödünleşmedir. Bizim için yeniden yönlendirilen bir CDN veya DNS'yi takip etmek bankadaki paraya karşılık gelir kaybıdır.
İade Yok İade Yok

1

Simon Timms'in bu blog gönderisine de başvurabilirsiniz: https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/

Ama HttpClientfarklı. Hayata geçirdiği rağmen IDisposablearayüz aslında paylaşılan bir nesnedir. Bu, kapakların altında yeniden girildiği anlamına gelir) ve iplik güvenli. HttpClientHer yürütme için yeni bir örneği oluşturmak yerine HttpClientuygulamanın tüm ömrü boyunca tek bir örneğini paylaşmanız gerekir . Neden olduğuna bakalım.


1

Dikkat edilmesi gereken bir nokta, "kullanma" bloglarının notunun hiçbirinin dikkate almanız gereken sadece BaseAddress ve DefaultHeader olmadığıdır. HttpClient'i statik hale getirdiğinizde, istekler arasında taşınacak iç durumlar vardır. Bir örnek: Bir FedAuth jetonu almak için HttpClient ile 3. bir tarafa kimlik doğrulaması yapıyorsunuz (neden OAuth / OWIN / vb. Her bir istekte bu çerezleri yönetmediğiniz sürece API'nize giriş yapacak bir sonraki kullanıcı, son kişinin FedAuth çerezini gönderecektir.


0

İlk sorun olarak, bu sınıf tek kullanımlık olsa da, usingdeyimle birlikte kullanmak en iyi seçim değildir çünkü HttpClientnesneyi elden çıkarsanız bile , temeldeki yuva hemen serbest bırakılmaz ve 'soketlerin tükenmesi adı verilen ciddi bir soruna neden olabilir.

Ancak HttpClient, singleton veya statik nesne olarak kullandığınızda karşılaşabileceğiniz ikinci bir sorun var. Bu durumda, tek veya statik bir değişikliklere HttpClientsaygı göstermez DNS.

içinde .net çekirdek sen de aynısını yapabilirsiniz HttpClientFactory böyle bir şey:

public interface IBuyService
{
    Task<Buy> GetBuyItems();
}
public class BuyService: IBuyService
{
    private readonly HttpClient _httpClient;

    public BuyService(HttpClient httpClient)
    {
        _httpClient = httpClient;
    }

    public async Task<Buy> GetBuyItems()
    {
        var uri = "Uri";

        var responseString = await _httpClient.GetStringAsync(uri);

        var buy = JsonConvert.DeserializeObject<Buy>(responseString);
        return buy;
    }
}

ConfigureServices

services.AddHttpClient<IBuyService, BuyService>(client =>
{
     client.BaseAddress = new Uri(Configuration["BaseUrl"]);
});

burada dokümantasyon ve örnek

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.