Каковы затраты на создание нового 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);
}
}
6 ответов
HttpClient
был разработан для многократного использования. Даже через несколько потоков. HttpClientHandler
имеет учетные данные и файлы cookie, которые предназначены для повторного использования в звонках. Имея новый HttpClient
экземпляр требует переустановить все эти вещи. Так же DefaultRequestHeaders
Свойство содержит свойства, которые предназначены для нескольких вызовов. Необходимость сбрасывать эти значения в каждом запросе побеждает точку.
Еще одно важное преимущество HttpClient
это возможность добавить HttpMessageHandlers
в конвейер запроса / ответа, чтобы применить сквозные проблемы. Это могут быть записи, аудит, регулирование, обработка перенаправления, автономная обработка, захват метрик. Все виды разных вещей. Если новый HttpClient создается для каждого запроса, то все эти обработчики сообщений должны быть настроены для каждого запроса, и каким-либо образом должно быть предоставлено любое состояние уровня приложения, которое совместно используется запросами для этих обработчиков.
Чем больше вы используете функции HttpClient
Чем больше вы увидите, что повторное использование существующего экземпляра имеет смысл.
Однако самой большой проблемой, на мой взгляд, является то, что когда HttpClient
класс расположен, он располагает HttpClientHandler
, который затем принудительно закрывает TCP/IP
соединение в пуле соединений, которым управляет ServicePointManager
, Это означает, что каждый запрос с новым HttpClient
требует восстановления нового TCP/IP
подключение.
Из моих тестов, использующих простой HTTP в локальной сети, снижение производительности довольно незначительно. Я подозреваю, что это потому, что есть основной протокол активности TCP, который держит соединение открытым, даже когда 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
и т. д.) и делегирует их одному 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;
"Это свойство можно использовать, чтобы активные соединения объекта ServicePoint не оставались открытыми бесконечно. Это свойство предназначено для сценариев, в которых следует периодически прерывать и восстанавливать подключения, например, для сценариев балансировки нагрузки.
По умолчанию, когда KeepAlive имеет значение true для запроса, свойство MaxIdleTime устанавливает время ожидания для закрытия подключений ServicePoint из-за неактивности. Если у ServicePoint есть активные соединения, MaxIdleTime не имеет никакого эффекта, и соединения остаются открытыми в течение неопределенного времени.
Если для свойства ConnectionLeaseTimeout установлено значение, отличное от -1, и по истечении указанного времени активное соединение ServicePoint закрывается после обслуживания запроса, установив для KeepAlive значение false в этом запросе. Установка этого значения влияет на все соединения, управляемые объектом ServicePoint."
Если у вас есть службы за CDN или другой конечной точкой, которые вы хотите переключать при сбое, тогда этот параметр помогает вызывающим абонентам следовать за вами к вашему новому месту назначения. В этом примере через 60 секунд после аварийного переключения все абоненты должны повторно подключиться к новой конечной точке. Это требует, чтобы вы знали ваши зависимые сервисы (те сервисы, которые вы вызываете) и их конечные точки.
Стоит отметить, что ни одно из примечаний блога "не использовать, используя" это то, что вам нужно учитывать не только BaseAddress и DefaultHeader. Как только вы сделаете HttpClient статическим, существуют внутренние состояния, которые будут передаваться через запросы. Пример: вы проходите проверку подлинности для третьей стороны с помощью HttpClient для получения токена FedAuth (игнорируйте, почему не используете OAuth/OWIN/ и т. Д.), В этом ответном сообщении есть заголовок Set-Cookie для FedAuth, он добавляется в состояние HttpClient. Следующий пользователь, который войдет в ваш API, будет отправлять последний файл cookie FedAuth, если вы не управляете этими файлами cookie при каждом запросе.
Во-первых, хотя этот класс одноразовый, его можно использовать с using
заявление - не лучший выбор, потому что даже когда вы избавляетесь HttpClient
объект, базовый сокет не освобождается немедленно и может вызвать серьезную проблему под названием "исчерпание сокетов".
Но есть вторая проблема с HttpClient
которые вы можете получить, когда используете его как одиночный или статический объект. В этом случае одноэлементный или статическийHttpClient
не уважает DNS
изменения.
в ядре.net вы можете сделать то же самое с HttpClientFactory примерно так:
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"]);
});
документация и пример здесь
Вы также можете обратиться к этому сообщению в блоге Саймона Тиммса: https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/
Но
HttpClient
это отличается. Хотя это реализуетIDisposable
Интерфейс это на самом деле общий объект. Это означает, что под крышками это повторно входящий) и потокобезопасный. Вместо создания нового экземпляраHttpClient
для каждого выполнения вы должны разделить один экземплярHttpClient
за весь срок службы приложения. Давайте посмотрим на почему.