Каковы накладные расходы на создание нового HttpClient на один вызов в клиенте WebAPI?

Каким должен быть время жизни HttpClient для клиента WebAPI?
 Лучше ли иметь один экземпляр HttpClient для нескольких вызовов?

Какие накладные расходы на создание и удаление 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);
    }
}

Ответ 1

HttpClient был предназначен для повторного использования для нескольких вызовов. Даже через несколько потоков. HttpClientHandler имеет учетные данные и файлы cookie, которые предназначены для повторного использования через вызовы. Наличие нового экземпляра HttpClient требует повторной настройки всего этого материала. Кроме того, свойство DefaultRequestHeaders содержит свойства, предназначенные для нескольких вызовов. При использовании reset эти значения для каждого запроса обходят точку.

Другим важным преимуществом HttpClient является возможность добавить HttpMessageHandlers в конвейер запроса/ответа для применения проблем с перекрестными разрезами. Это может быть регистрация, аудит, дросселирование, переадресация, автономная обработка, захват показателей. Всевозможные вещи. Если для каждого запроса создается новый HttpClient, тогда все эти обработчики сообщений должны быть настроены для каждого запроса, и каким-то образом должно быть предусмотрено состояние любого уровня приложения, которое совместно используется между запросами этих обработчиков.

Чем больше вы используете функции HttpClient, тем больше вы увидите, что повторное использование существующего экземпляра имеет смысл.

Однако самая большая проблема, на мой взгляд, заключается в том, что когда класс HttpClient размещен, он располагает HttpClientHandler, который затем принудительно закрывает соединение TCP/IP в пуле соединений, которым управляет ServicePointManager, Это означает, что каждый запрос с новым HttpClient требует повторного установления нового соединения TCP/IP.

Из моих тестов, используя простой HTTP в локальной сети, поражение производительности довольно незначительно. Я подозреваю, что это связано с тем, что существует основной протокол TCP keepalive, который поддерживает соединение, даже когда HttpClientHandler пытается его закрыть.

По просьбам, которые проходят через Интернет, я видел другую историю. Я видел 40% -ный рост производительности из-за необходимости повторно открывать запрос каждый раз.

Я подозреваю, что удар по соединению HTTPS будет еще хуже.

Мой совет: сохранить экземпляр HttpClient для срока службы вашего приложения для каждого отдельного API, к которому вы подключаетесь.

Ответ 2

Если вы хотите, чтобы ваше приложение масштабировалось, разница ОГРОМНАЯ! В зависимости от нагрузки вы увидите очень разные показатели производительности. Как упоминает Даррел Миллер, HttpClient был разработан для повторного использования в разных запросах. Это подтвердили ребята из команды BCL, которые ее написали.

Недавний проект, который я имел, заключался в том, чтобы помочь очень крупному и известному онлайн-магазину компьютеров в масштабе черных для Black Friday/holiday traffic для некоторых новых систем. Мы столкнулись с некоторыми проблемами производительности, связанными с использованием HttpClient. Поскольку он реализует IDisposable, разработчики выполнили то, что вы обычно делали, создав экземпляр и разместив его внутри оператора using(). Как только мы начали тестирование нагрузки, приложение поставило сервер на колени - да, сервер не просто приложение. Причина в том, что каждый экземпляр HttpClient открывает порт на сервере. Из-за недетерминированной доработки GC и того факта, что вы работаете с компьютерными ресурсами, которые охватывают несколько слоев https://www.asp.net/web-api/overview/advanced/calling-a-web-api-from-a-net-client

Обратите особое внимание на этот призыв:

HttpClient предназначен для создания экземпляра один раз и повторного использования на протяжении всего срока службы приложения. Особенно в серверных приложениях создание нового экземпляра HttpClient для каждого запроса приведет к исчерпанию количества сокетов, доступных при тяжелых нагрузках. Это приведет к ошибкам SocketException.

Если вы обнаружите, что вам нужно использовать статический HttpClient с разными заголовками, базовым адресом и т.д., что вам нужно будет сделать, так это создать HttpRequestMessage вручную и установить эти значения в HttpRequestMessage. Затем используйте HttpClient:SendAsync(HttpRequestMessage requestMessage, ...)

Ответ 3

Как утверждают другие ответы, HttpClient предназначен для повторного использования. Однако повторное использование одного экземпляра HttpClient в многопоточном приложении означает, что вы не можете изменять значения его свойств с состоянием, например BaseAddress и DefaultRequestHeaders (поэтому вы можете использовать их только в том случае, если они постоянны в приложении).

Один из способов обойти это ограничение - обернуть HttpClient классом, который дублирует все необходимые методы HttpClient (GetAsync, PostAsync и т.д.) и делегирует их в singleton HttpClient. Однако это довольно утомительно (вам также придется обернуть существует другой способ - продолжать создавать новые экземпляры 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) 
       } 
    };
}

Ответ 4

Связано с большими объемами веб-сайтов, но не напрямую с 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=true

"Вы можете использовать это свойство, чтобы гарантировать, что активные соединения объекта ServicePoint не остаются неопределенными. Это свойство предназначено для сценариев, в которых соединения должны периодически удаляться и восстанавливаться, например сценарии балансировки нагрузки.

По умолчанию, когда KeepAlive является истинным для запроса, свойство MaxIdleTime устанавливает тайм-аут для закрытия соединений ServicePoint из-за неактивности. Если ServicePoint имеет активные соединения, MaxIdleTime не действует, и соединения остаются открытыми неограниченное время.

Если для свойства ConnectionLeaseTimeout установлено значение, отличное от -1, и по истечении указанного времени, активное соединение ServicePoint закрывается после обслуживания запроса путем установки KeepAlive в false в этом запросе. Установка этого значения влияет на все подключения, управляемые объектом ServicePoint. "

Когда у вас есть службы за CDN или другой конечной точкой, которую вы хотите переустановить, этот параметр помогает абонентам следовать за вами в ваш новый пункт назначения. В этом примере через 60 секунд после перехода на другой ресурс все вызывающие абоненты должны повторно подключиться к новой конечной точке. Это требует, чтобы вы знали свои зависимые сервисы (те услуги, которые вы звоните) и их конечные точки.

Ответ 5

Вы также можете обратиться к этому сообщению в блоге Саймоном Тимсом: https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/

Но HttpClient отличается. Хотя он реализует интерфейс IDisposable, это фактически общий объект. Это означает, что под крышками он является реентерабельным) и потокобезопасным. Вместо создания нового экземпляра HttpClient для каждого исполнения вы должны поделиться одним экземпляром HttpClient для всего жизненного цикла приложения. Давайте посмотрим, почему.