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


что должно быть HttpClient срок службы клиентской части веб-API?
Лучше ли иметь один экземпляр 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}tt{2}", product.Name, product.Price, product.Category);
    }
}
5 123

5 ответов:

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

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

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

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

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

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

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

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

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

недавний проект, который у меня был, должен был помочь очень большому и известному онлайн-ритейлеру компьютеров масштабироваться для трафика Черной пятницы/праздника для некоторых новых систем. Мы столкнулись с некоторыми проблемами производительности вокруг использования HttpClient. Так как он реализует IDisposable, разработчики сделали то, что вы обычно делаете, создавая экземпляр и помещая его внутрь using() заявление. Как только мы начали нагрузочное тестирование, приложение поставило сервер на колени - да, сервер не только приложение. Причина в том, что каждый экземпляр HttpClient открывает порт на сервере. Из-за недетерминированного завершения GC и того факта, что вы работаете с компьютерными ресурсами, которые охватывают несколько слои OSI, закрытия сетевых портов может занять некоторое время. На самом деле ОС Windows может занять до 20 секунд, чтобы закрыть порт (на Microsoft). Мы открывали порты быстрее, чем они могли быть закрыты - истощение портов сервера, которое забило процессор до 100%. Мое исправление состояло в том, чтобы изменить HttpClient на статический экземпляр, который решил проблему. Да, это одноразовый ресурс, но любые накладные расходы значительно перевешиваются разницей в производительности. Я призываю вас сделать некоторые нагрузочное тестирование, чтобы увидеть, как ваше приложение ведет себя.

вы также можете проверить страницу руководства WebAPI для документации и примера на 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, ...)

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

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

относится к большим объемам веб-сайтов, но не непосредственно к 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;

From 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 имеет значение true для запроса, свойство MaxIdleTime задает время ожидания для закрытия соединений ServicePoint из-за бездействия. Если точка обслуживания имеет активные соединения, MaxIdleTime не имеет никакого эффекта, и соединения остаются открытыми бесконечно.

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

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

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

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