it-roy-ru.com

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

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

Каковы затраты на создание и удаление HttpClient для запроса, как в примере ниже (взято с http://www.asp.net/web-api/overview/web-api-clients/calling-a-web-api -от сети-клиента ): 

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);
    }
}
133
Bruno Pessanha

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

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

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

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

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

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

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

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

182
Darrel Miller

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

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

Вы также можете проверить страницу руководства WebAPI для получения документации и примера по адресу https://www.asp.net/web-api/overview/advanced/calling-a-web-api-from-a-net -клиент

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

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

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

60
Dave Black

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

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

Связанный с большими объемами веб-сайтов, но не напрямую с 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 ); k (DevLang-csharp) & rd = true

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

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

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

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

4
No Refunds No Returns

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

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

1
SvenAelterman

Стоит отметить, что ни одно из примечаний блога «не использовать, используя» заключается в том, что нужно учитывать не только BaseAddress и DefaultHeader. Как только вы сделаете HttpClient статическим, существуют внутренние состояния, которые будут передаваться по запросам. Пример: вы проходите проверку подлинности для третьей стороны с помощью HttpClient для получения токена FedAuth (игнорируйте, почему не используете OAuth/OWIN/и т.д.), В этом ответном сообщении есть заголовок Set-Cookie для FedAuth, он добавляется в ваше состояние HttpClient. Следующий пользователь, который войдет в ваш API, будет отправлять последний файл cookie FedAuth, если вы не управляете этими файлами cookie при каждом запросе.

0
escapismc