Runtime: Singleton HttpClient не учитывает изменения DNS

Созданный на 29 авг. 2016  ·  77Комментарии  ·  Источник: dotnet/runtime

Как описано в следующем сообщении: http://byterot.blogspot.co.uk/2016/07/singleton-httpclient-dns.html , как только вы начнете использовать общий экземпляр HttpClient для повышения производительности, столкнется с проблемой, когда клиент не будет учитывать обновления записей DNS в случае отказоустойчивости.

Основная проблема заключается в том, что значение по умолчанию ConnectionLeaseTimeout установлено равным -1 , бесконечно. Он закроется только при удалении клиента, что очень неэффективно.

Исправление заключается в обновлении значения точки обслуживания следующим образом:

var sp = ServicePointManager.FindServicePoint(new Uri("http://foo.bar/baz/123?a=ab"));
sp.ConnectionLeaseTimeout = 60*1000; // 1 minute

К сожалению, сегодня нет возможности сделать это с .NET Core.

Либо ServicePointManager должен быть перенесен в .NET Core, либо аналогичная эквивалентная функциональность должна быть включена другим способом.

area-System.Net.Http enhancement

Самый полезный комментарий

Это абсолютно проблема. Предложенное @darrelmiller решение, похоже, демонстрирует непонимание

Мы используем диспетчеры трафика Azure для оценки работоспособности экземпляра. Они работают поверх DNS с короткими TTL. Хост волшебным образом не узнает, что он неисправен, и не начнет выдавать заголовки C

Что касается netcore 1.1, я сейчас ищу решение именно этой проблемы. Я не вижу ни одного, нативно во всяком случае.

Все 77 Комментарий

По умолчанию 60 секунд, и вы можете установить его вручную на все, что захотите. https://github.com/dotnet/corefx/blob/d0dc5fc099946adc1035b34a8b1f6042eddb0c75/src/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpHandler.cs#L76

Я не думаю, что это одно и то же свойство. Это время ожидания соединения, а не ConnectionLeaseTimeout.

Как указано в https://msdn.microsoft.com/en-us/library/system.net.servicepoint.connectionleasetimeout.aspx , в некоторых сценариях его следует периодически удалять. Но нет очевидного способа изменить поведение в .NET Core.

@onovotny Да, это странная вещь, которая, кажется, делает явное соединение: закрывает через определенный промежуток времени. Нет причин, по которым нельзя было бы реализовать промежуточное ПО, если бы вы действительно этого захотели. Это просто HTTP-заголовок.

@darrelmiller, так что это выходит за

@onovotny , в .NET Core нет класса ServicePointManager . Это похоже на проблему с .NET Framework (Desktop). Пожалуйста, откройте вопрос в UserVoice или Connect.

@onovotny Первоначальная проблема, насколько я понимаю, заключается в том, что на стороне сервера кто-то хочет обновить запись DNS, чтобы все будущие запросы к хосту A направлялись на новый IP-адрес. Лучшее решение, на мой взгляд, состоит в том, что при изменении этой DNS-записи приложение на хосте А должно возвращать клиенту заголовок C соединение: закрытие, когда истекает тайм-аут «аренды».

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

Это абсолютно проблема. Предложенное @darrelmiller решение, похоже, демонстрирует непонимание

Мы используем диспетчеры трафика Azure для оценки работоспособности экземпляра. Они работают поверх DNS с короткими TTL. Хост волшебным образом не узнает, что он неисправен, и не начнет выдавать заголовки C

Что касается netcore 1.1, я сейчас ищу решение именно этой проблемы. Я не вижу ни одного, нативно во всяком случае.

@ kudoz83 Вы правы, хост волшебным образом не знает, когда диспетчер трафика настроил разрешение DNS, поэтому я сказал

когда эта запись DNS изменяется, приложение на хосте A должно быть сообщено

Если вы не считаете целесообразным уведомлять исходный сервер о том, что он должен возвращать заголовки

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

И что касается меня, не понимающего варианта использования. Первоначальная проблема была основана на сообщении в блоге, где вариант использования был связан с переключением между производственными и промежуточными слотами и не имел ничего общего с мониторингом работоспособности. Хотя оба сценария могут выиграть от возможности получать уведомления о переключении DNS/слотов.

Насколько я знаю, нет способа уведомить неисправный хост о том, что он неисправен? Как правило, это означает, что он находится в каком-то состоянии ошибки. В исходном сообщении говорится о сценариях отработки отказа.

Диспетчер трафика Azure объясняется здесь https://docs.microsoft.com/en-us/azure/traffic-manager/traffic-manager-monitoring.

Он работает на уровне DNS и прозрачен для клиентов и хостов — так задумано. Экземпляры Azure, взаимодействующие друг с другом через диспетчер трафика для разрешения DNS, находятся в затруднительном положении, не имея возможности установить срок жизни при обновлении DNS для HttpClient.

То, как в настоящее время работает HttpClient, похоже, противоречит собственным предложениям службы Azure от Microsoft — в этом вся суть. Не говоря уже о том, что любой подобный сервис (например, Amazon Route 53) работает точно так же и имеет точно такую ​​же короткую зависимость DNS TTL.

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

Насколько я знаю, нет способа уведомить неисправный хост о том, что он неисправен?

Если «сбойный» хост не может выполнить какой-либо код, нам не понадобится код состояния HTTP 503, потому что исходный сервер никогда не сможет его вернуть. Существуют сценарии, в которых неисправный узел может быть не в состоянии обработать уведомление, но в этих случаях он, вероятно, не будет удерживать активное соединение TCP/IP открытым слишком долго.

Когда Али первоначально поднял эту тему в Твиттере, прежде чем он написал сообщение в блоге, он был согласен с тем фактом, что клиент будет продолжать делать запросы и получать ответы от сервера, который был заменен.

Я понимаю, что у вас другая ситуация, и вы не можете полагаться на то, что исходный сервер сможет надежно закрыть соединение. Я не уверен, что понимаю, почему в вашей ситуации вы не можете использовать HttpMessageHandler для обнаружения ответов 5XX, закрыть соединение и повторить запрос. Или, что еще проще, просто убедите свой веб-сервер возвращать соединение:закрыть всякий раз, когда он возвращает код состояния 5XX.

Также стоит отметить, что поведение, которое вас беспокоит, отсутствует в HttpClient. Он находится в базовом HttpHandler или даже ниже его, и используется ряд различных реализаций HttpHandler. На мой взгляд, текущее поведение соответствует тому, как HTTP спроектирован для работы. Я согласен, что есть проблема, когда настройки DNS обновляются при открытых соединениях, но это не проблема .net. Это проблема архитектуры HTTP. Это не означает, что .net не может сделать что-то, чтобы уменьшить эту проблему.

Мне любопытно, каким, по вашему мнению, должно быть решение. Как вы думаете, следует ли повторно реализовать что-то вроде ConnectionLeaseTimeout, которое просто периодически разрывает соединение, независимо от того, был ли аварийный переход или нет? Или у вас есть лучшее решение?

Прости,

Я не должен был звучать так воинственно, как вчера. Был плохой день.

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

@ kudoz83 Не беспокойтесь, у всех нас бывают такие дни :-)

Насколько я понимаю, ConnectionLeaseTimeout — это буквально таймер, который периодически закрывает бездействующее соединение со стороны клиента. Он реализован в ServicePointManager, которого нет в ядре. Это отличается от времени ожидания «неактивного соединения», которое закрывает соединение только после того, как оно не использовалось в течение определенного периода времени.

Вероятно, есть вызов Win32, который позволит очистить кеш DNS-клиента. Это гарантирует, что новые подключения будут выполнять поиск DNS. Я быстро поискал его, но не нашел, но я уверен, что он где-то есть.

В .net core в Windows управление HTTP-соединениями фактически возложено на ОС. Самое близкое, что вы получаете, это вызов Win32 API WinHttpOpen https://github.com/dotnet/corefx/blob/master/src/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpHandler.cs# L718 Я не могу найти общедоступных API, которые могли бы что-то сделать с _sessionHandle. Чтобы получить то изменение поведения, которое вы ищете, потребуется, чтобы люди из ОС внесли изменения. По крайней мере, я так это понимаю.

Не проще ли заставить диспетчер трафика Azure установить очень низкий TTL для записей DNS? Если вас не беспокоят долгоживущие соединения, то низкий TTL должен свести к минимуму вероятность установки новых соединений с мертвым сервером.

Преимущество того, как построен WinHttpHandler, заключается в том, что мы получаем такие вещи, как HTTP/2, бесплатно с обновлением ОС. Недостатком является то, что мы не получаем полного контроля в управляемом коде.

@даррелмиллер

[ConnectionLeaseTimeout] реализован в ServicePointManager, которого нет в ядре.

Кажется, что ServicePoint.ConnectionLeaseTimeout возвращается в .Net Standard 2.0/.Net Core 1.2. (Хотя я понятия не имею, будут ли к нему привязаны, например, соединения WinHttpHandler.)

Я думаю, что здесь есть некоторое недопонимание: DNS TTL совершенно не связаны со временем жизни соединения HTTP/TCP.

Новые HTTP-соединения выполняют поиск DNS и подключаются к возвращенному адресу хоста. Несколько HTTP-запросов могут быть отправлены через одно и то же соединение, но после открытия HTTP-соединения (и базового TCP-соединения) повторный поиск DNS не выполняется.

Это правильное поведение, поэтому даже если вы делаете 1000 запросов через соединение, которое длится несколько дней, разрешение DNS не имеет значения, поскольку соединение не изменилось. Если вы сделаете новый HTTP-запрос через новое HTTP-соединение, вы увидите поиск DNS для нового соединения.

Это обновление DNS не имеет ничего общего с HttpClient , который просто использует HttpClientHandler по умолчанию (который сам использует стек сокетов HttpWebRequest ), и любое разрешение DNS обрабатывается этим стеком. ServicePointManager управляет объектами ServicePoint в этом стеке, которые являются подключениями к определенному хосту. ServicePointManager есть некоторые минимальные кэширование на стороне клиента на DNS - запросов , которые вы можете настроить.

Настройки по умолчанию предназначены для того, чтобы соединения оставались открытыми на неопределенный срок для повышения эффективности. Если подключенный хост становится недоступным, соединение будет автоматически разорвано, что вызовет новое соединение с новым поиском DNS, однако DNS TTL не вызовет никаких новых подключений. Если вам это нужно, вам придется закодировать это в своем приложении.

Если вы хотите, чтобы был выполнен еще один поиск DNS, вам необходимо сбросить соединение на клиенте. Вы можете установить максимальное время жизни TCP-соединения, отправить заголовок connection: close или просто удалить HttpClient или базовый ServicePoint . Вы также можете выполнить разрешение DNS с помощью Dns.GetHostAddresses и вместо этого использовать IP-адрес для своих HTTP-запросов.

Надеюсь, что это поможет, если я не в этом, пожалуйста, дайте мне знать.

@manigandham

Я думаю, что здесь есть некоторое недопонимание: DNS TTL совершенно не связаны со временем жизни соединения HTTP/TCP.

Да, DNS TTL довольно несвязаны, но не полностью.

Новые HTTP-соединения выполняют поиск DNS и подключаются к возвращенному адресу хоста.

Да это верно. Но в Windows есть локальный кеш DNS, который учитывает TTL. Если значение TTL велико, а DNS-сервер изменил запись DNS, новое соединение будет открыто со старым IP-адресом из-за кэширования записи DNS клиентом. Очистка кеша DNS клиента — единственный известный мне обходной путь.

Несколько HTTP-запросов могут быть отправлены через одно и то же соединение, но после открытия HTTP-соединения (и базового TCP-соединения) повторный поиск DNS не выполняется.

Еще раз правильно. В производственном/промежуточном обмене, когда замененный сервер все еще отвечает, это приведет к тому, что многие будущие запросы будут направляться на старый сервер, что плохо. Однако самый последний разговор был о сценарии, когда сервер выходит из строя и диспетчер трафика переключается на новый сервер. Что происходит с существующим подключением к вышедшему из строя серверу, неизвестно. Если это серьезный аппаратный сбой, есть большая вероятность, что соединение будет прервано. Это заставит клиента повторно установить соединение, и именно здесь полезен низкий TTL. Однако, если сбой сервера не нарушает работу сервера и он просто возвращает ответы 5XX, серверу было бы полезно вернуть соединение: закрытие, чтобы гарантировать, что будущие запросы будут выполняться по новому соединению, надеюсь, к новому рабочему соединению. сервер

Это обновление DNS не имеет ничего общего с HttpClient, который просто использует HttpClientHandler по умолчанию (который сам использует стек сокетов HttpWebRequest).

И да и нет. В ядре .Net HttpClientHandler был переписан и больше не использует HttpWebRequest и, следовательно, не использует ServicePointManager. Отсюда и причина этой проблемы.

Я внимательно изучил IIS и, похоже, не нашел способа получить ответы 5XX с заголовками c

К вашему сведению, вот связанный пост, посвященный этой проблеме, и временное решение. Остерегайтесь HttpClient

Либо ServicePointManager следует перенести в .NET Core.

Хотя мы добавили ServicePointManager и HttpWebRequest для паритета платформ, они не поддерживают большую часть функций .Net Core, поскольку базовый стек разрешается либо в WinHTTP, либо в Curl, которые не предоставляют дополнительные ручки управления.

Использование ServicePointManager для управления HttpClient на самом деле является побочным эффектом использования HttpWebRequest и управляемого стека HTTP в .Net Framework. Поскольку WinHttpHandler/CurlHandler используются в .Net Core, SPM не контролирует экземпляры HttpClient.

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

Вот ответ команды WinHTTP:

  1. Из соображений безопасности, пока клиент продолжает отправлять запросы и _существуют активные подключения_, WinHTTP будет продолжать использовать IP-адрес назначения. Невозможно ограничить время, в течение которого эти соединения остаются активными.
  2. Если все подключения к удаленной конечной точке были закрыты сервером, будут установлены _новые_ подключения с запросом DNS и, следовательно, к новому IP-адресу.

Чтобы заставить клиентов перейти на следующий IP-адрес, единственное жизнеспособное решение — гарантировать, что сервер отклонит новые подключения и закроет существующие (уже упоминалось в этой теме).
Это то же самое, что я понимаю из документации ATM : обнаружение производится на основе запросов GET, которые не выполняются 4 раза (не 200 ответов), и в этом случае соединения будут закрыты сервером.

Учитывая другие сетевые устройства, которые сами кэшируют DNS (например, домашние маршрутизаторы/точки доступа), я бы не рекомендовал отработку отказа DNS в качестве решения для технологии высокой доступности с малой задержкой. Если это необходимо, моя рекомендация для контролируемого аварийного переключения состоит в том, чтобы иметь выделенный LB (аппаратный или программный), который знает, как правильно остановить утечку.

в его нынешнем виде приложение со статическим HttpClient, разрешающим DNS (неосознанно) диспетчером трафика, никогда не получит новый хост, когда ранее разрешенный хост станет неработоспособным.

Как упоминалось выше, если под «неработоспособным» вы подразумеваете, что сервер возвращает код ошибки HTTP и закрывает TCP-соединение, пока вы наблюдаете, как истек срок жизни DNS, это действительно ошибка.

Пожалуйста, опишите ваш сценарий более подробно:

  1. Что такое DNS-кеш клиентского компьютера и TTL во время отработки отказа. Вы можете использовать ipconfig /showdns и nslookup -type=soa <domain_name> чтобы сравнить то, что машина считает TTL, и TTL официального NS.
  2. Есть ли активные подключения к удаленному серверу? Вы можете использовать netstat /a /n /b чтобы увидеть соединения и процессы, владеющие ими.
  3. Нам очень поможет создание репродукции: пожалуйста, прикрепите любую доступную информацию, такую ​​как: версия .Net Core/Framework, код для клиента/сервера, сетевые трассировки, вовлеченный удаленный DNS LB/Traffic Manager и т. д.

Если вы подключаетесь к машине, а соединение TCP/HTTP продолжает работать, почему вы должны в какой-то момент предположить, что сервер больше не подходит?
Я думаю, что на стороне Azure есть возможности для улучшения. Например, когда компьютеры неработоспособны или обмениваются средами, существующие соединения могут быть закрыты.

или просто распоряжаться ... лежащим в основе ServicePoint

@manigandham Как? Он не одноразовый, и единственный подходящий метод, который я вижу, это CloseConnectionGroup который вы не можете использовать, поскольку имя группы является деталью реализации (в настоящее время некоторый хэш обработчика). Кроме того, я не уверен, что произойдет, если вы попытаетесь избавиться от ServicePoint, пока HttpClient использует его (особенно одновременно).

@ohadschn

.NET Core 2.0 возвращает классы и функции ServicePoint : https://docs.microsoft.com/en-us/dotnet/api/system.net.servicepoint?view=netcore-2.0.

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

Нет ничего экзотического в закрытии соединения во время выполнения запроса HttpClient , это было бы так же, как если бы вы потеряли соединение при работе в Интернете. Либо запрос никогда не выполняется, либо он выполняется, и вы не получаете ответ - и то, и другое должно привести к возвращенным ошибкам http, и вам придется обрабатывать их в своем приложении.

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

.NET Core 2.0 возвращает классы и функции ServicePoint: https://docs.microsoft.com/en-us/dotnet/api/system.net.servicepoint?view=netcore-2.0.
Вы можете вернуться к использованию тайм-аута аренды соединения, чтобы установить максимальное время для активных соединений, прежде чем они будут сброшены.

На самом деле, это не возвращает функциональность полностью. Поверхность API вернулась. Но в основном это не работает, поскольку стеки HTTP не используют эту объектную модель.

Копия: @stephentoub @geoffkizer

Я бы назвал многих участников этой ветки чем-то вроде «команды мечты» в этом вопросе, но я все еще не уверен, достигнут ли какой-либо консенсус в отношении того, какие передовые практики учитываются в том, что мы имеем сегодня. Было бы здорово получить от этой группы решительный ответ по паре ключевых моментов.

Допустим, у меня есть HTTP-библиотека общего назначения, предназначенная для многих платформ, не все из которых поддерживают ServicePointManager , и я пытаюсь обеспечить поведение, которое разумно управляет экземплярами HttpClient по умолчанию в соответствии с лучшими практиками. Я заранее не знаю, какие хосты будут называться, не говоря уже о том, хорошо ли они ведут себя в отношении заголовков DNS change /C

  1. Как оптимально управлять экземплярами HttpClient ? По одному на конечную точку? По одному на хост/схему/порт? Всего один синглтон? (Я сомневаюсь в этом, поскольку потребители могут захотеть воспользоваться заголовками по умолчанию и т. д.)

  2. Как мне наиболее эффективно решить эту проблему с DNS? Утилизировать экземпляры через 1 минуту? Относиться к этому как к «крайне маловероятному» и не обеспечивать никакого поведения при утилизации по умолчанию?

(Кстати, это не гипотетическая ситуация; прямо сейчас я борюсь с этими точными вопросами.)

Спасибо!

Меня бы даже не пригласили на пробы команды мечты, но у меня есть одно понимание относительно вашего единственного комментария HttpClient . Во-первых, вы всегда можете использовать метод SendAsync для указания разных заголовков и адресов, чтобы вы могли обернуть его своим классом, который предоставляет общие данные, аналогичные HttpClient (например, заголовки по умолчанию, базовый адрес), но заканчивается вызовом одного и того же клиента. Но что еще лучше, вы можете создать несколько экземпляров HttpClient которые используют один и тот же HttpClientHandler : https://github.com/dotnet/corefx/issues/5811.

Что касается вопроса DNS, то консенсус здесь, похоже, таков:

Чтобы принудить клиентов к следующему IP-адресу, единственное жизнеспособное решение — убедиться, что сервер отклоняет новые подключения и закрывает существующие.

Если это вам не подходит, Даррел Миллер предложил еще несколько вариантов здесь: https://github.com/dotnet/corefx/issues/11224#issuecomment -270498159.

@ohadschn Спасибо, и, надеюсь, я не слишком отклоняюсь от темы, но есть ли вообще какая-то _выгода_ от совместного использования одного и того же HttpClient (или HttpClientHandler) между несколькими хостами? Я могу ошибаться, но мне кажется, что новый хост означает новый поиск DNS, новое соединение, новый сокет. Можно ли восстановить сокет в TIME_WAIT и переназначить его для другого хоста? Я предполагал, что нет, но мы определенно достигаем пределов моих знаний. Если это возможно, возможно, это преимущество использования синглтона HttpClient даже с несколькими хостами?

Что касается совета по DNS, я чувствовал, что консенсуса здесь больше всего не хватало, и это понятно. Обеспечение корректного поведения сервера нецелесообразно, если вы не контролируете то, что происходит на этом конце. Я _могу_ спроектировать что-то, где экземпляры периодически удаляются. Вопрос в том, стоит ли это компромисса в большинстве случаев. Я предполагаю, что это в значительной степени зависит от меня, чтобы сделать суждение. :)

Для меня наиболее очевидным преимуществом совместного использования одного и того же HttpClient [ Handler ] является простота. Вам не нужно будет хранить такие вещи, как сопоставление между хостами и клиентами. Я бы не знал о TIME_WAIT, да и не в моем опыте.

Что касается DNS, вы упомянули об одном возможном подходе, когда вы не контролируете сервер. Вы также можете реализовать механизм, который периодически запрашивает DNS и удаляет экземпляры, только если произошло фактическое изменение...

Хотя данные ответы, которые определяют, что должно происходить на стороне сервера, точны для идеального мира, в реальном мире разработчики часто не контролируют весь стек на стороне сервера, с которым они взаимодействуют. В этом отношении мне очень нравится концепция ConnectionLeaseTimeout, так как это уступка реалиям повседневной жизни Джона и Джейн Доэвелопер.
Кто-то предложил написать промежуточное ПО для обеспечения такого тайм-аута в .NET Core. Справится ли этот класс, основанный на NodaTime и System.Collections.Immutable?

    public class ConnectionLeaseTimeoutHandler : DelegatingHandler
    {
        private static string GetConnectionKey(Uri requestUri) => $"{requestUri.Scheme}://{requestUri.Host}:{requestUri.Port}";

        private readonly IClock clock;
        private readonly Duration leaseTimeout;
        private ImmutableDictionary<string, Instant> connectionLeases = ImmutableDictionary<string, Instant>.Empty;

        public ConnectionLeaseTimeoutHandler(HttpMessageHandler innerHandler, IClock clock, Duration leaseTimeout)
            : base(innerHandler)
        {
            this.clock = clock;
            this.leaseTimeout = leaseTimeout;
        }

        protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            string key = GetConnectionKey(request.RequestUri);
            Instant now = clock.GetCurrentInstant();

            Instant leaseStart = ImmutableInterlocked.GetOrAdd(ref connectionLeases, key, now);

            if (now - leaseStart > leaseTimeout)
            {
                request.Headers.ConnectionClose = true;
                ImmutableInterlocked.TryRemove(ref connectionLeases, key, out leaseStart);
            }

            return base.SendAsync(request, cancellationToken);
        }
    }

@snboisen Я думаю, что это выглядит правильно, но не

Для меня более важный вопрос заключается в том, является ли отправка заголовка Connection: close жизнеспособным решением. Здесь стоит повторить то, что @darrelmiller сказал в комментариях к сообщению в блоге, которое вызвало эту тему:

ConnectionLeaseTimeout — странный зверь, о котором я не знал, пока Орен не упомянул об этом сегодня. Это приведет к тому, что исходящие запросы будут включать заголовки C onnection:Close после истечения срока аренды, что приведет к

Я уважаю мнение Даррела, и весь этот подход кажется довольно хакерским. С другой стороны, я согласен, что делать разработчику, который не имеет контроля над тем, что происходит на стороне сервера? Если это работает _иногда_, может быть, это лучший вариант, который у нас есть? Это, безусловно, «дешевле», чем периодическое удаление HttpClients или периодический поиск DSN.

Открыт ли билет Azure для решения этой проблемы?
С моей точки зрения, похоже, что вы все пытаетесь обойти то, как это реализовано в диспетчере трафика Azure.
Я не пользователь Azure, поэтому я смотрю на него со стороны, чувствуя себя менее склонным прыгать через его обручи.

@tmds Я его не видел, но, как уже упоминали другие, это относится не только к диспетчеру трафика Azure: у AWS аналогичная концепция, и есть служба приложений Azure с заменой промежуточного/производственного слота. И я уверен, что другие облачные провайдеры также предлагают что-то похожее на этот механизм обмена.

@snboisen Вы говорите, что AWS и другие облачные провайдеры (какие?) также используют тайм-ауты DNS для реализации этих функций?

@tmds Для AWS да, по крайней мере, для аварийного переключения: https://aws.amazon.com/route53/faqs/#adjust_ttl_to_use_failover
Я не знаю о других облачных провайдерах, но вполне вероятно, что некоторые из них тоже.

@tmds Для AWS да, по крайней мере, для аварийного переключения: https://aws.amazon.com/route53/faqs/#adjust_ttl_to_use_failover
Я не знаю о других облачных провайдерах, но вполне вероятно, что некоторые из них тоже.

Здесь есть два проблемных сценария:

  • клиент подключен к серверу, которого больше нет
  • клиент подключен к серверу, на котором сейчас установлена ​​устаревшая версия программного обеспечения

Переключение DNS связано с первым. Уместно удалить недоступные серверы из DNS (в AWS, Azure, ...).
Правильный способ обнаружить это на клиенте - это механизм тайм-аута. Я не уверен, есть ли такое свойство на HttpClient (максимальный тайм-аут ответа при установленном соединении).
На уровне протокола более новые протоколы websocket и http2 определяют сообщения ping/pong для проверки живости соединения.

Когда я предлагаю открыть вопрос с Azure, это для второго сценария.
Нет причин, по которым клиент должен закрывать рабочее соединение. Если сервер больше не подходит, клиенты должны быть отключены, а сервер должен быть недоступен.
Я нашел хороший документ по развертыванию AWS Blue/Green: https://d0.awsstatic.com/whitepapers/AWS_Blue_Green_Deployments.pdf.
В нем перечислены несколько техник. Для тех, кто использует DNS, упоминаются _DNS TTL сложности_. Для тех, кто использует балансировщик нагрузки _никаких сложностей с DNS_.

@tmds Спасибо за ссылку, очень интересно.

Мне интересно, насколько распространено использование DNS TTL по сравнению с использованием балансировщика нагрузки. Последнее кажется гораздо более надежным решением в целом.

Было похожее поведение при работе с двумя разными приложениями API в службах приложений Azure, которые обменивались данными через HttpClient . Мы заметили около 250 запросов в наших нагрузочных тестах, мы начали получать тайм-ауты и «Не удалось установить соединение» HttpRequestException — не имело значения, были ли это одновременные пользователи или последовательные запросы.

Когда мы переключились на блок using чтобы обеспечить удаление HttpClient в каждом запросе, мы не получили ни одного из этих исключений. Снижение производительности, если таковое имело место, было незначительным при переходе от синглтона к синтаксису using .

Когда мы переключились на блок using чтобы обеспечить удаление HttpClient в каждом запросе, мы не получили ни одного из этих исключений. Снижение производительности, если таковое имело место, было незначительным при переходе от синглтона к синтаксису using .

Дело не обязательно в производительности, а в том, чтобы не допустить утечки TCP-соединений и получить SocketException s.

Из MSDN :

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

Также см. https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/

@khellang Спасибо за ссылку (и пояснение к SocketException s, у меня определенно возникло недопонимание) - я наткнулся на этот пост и 3 или 4 других поста, описывающих то же самое.

Я понимаю, что целью является создание одного экземпляра, но когда мы используем его в службах приложений Azure, мы постоянно получаем HttpRequestExceptions при довольно скромной нагрузке (упомянутое выше, 250 последовательных запросов). Если нет другого решения, избавление от HttpClient — это обходной путь, который мы должны были реализовать, чтобы избежать появления этих постоянных исключений.

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

Кажется, это немного беспорядок для чего-то, что

предназначен для создания экземпляра один раз и повторного использования в течение всего жизненного цикла приложения.

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

@snboisen Я протестировал ваш ConnectionLeaseTimeoutHandler и обнаружил, что он не работает с несколькими подключениями. «Закрытие» выдается только одному соединению каждый раз, когда происходит тайм-аут аренды, но может быть несколько открытых соединений с одним экземпляром HttpClient при параллельной отправке запросов. Из того, что я вижу в fiddler (я использую неверный IP-адрес для генерации ошибок для проверки переключателя DNS), после отправки «закрытия» некоторые запросы используют новый IP-адрес, а некоторые запросы используют старый IP-адрес. В моем случае у меня есть ограничение на количество подключений, равное 2, и требуется несколько итераций тайм-аута аренды подключения, чтобы обновить оба подключения. Очевидно, было бы намного хуже с большим количеством одновременных подключений с рабочего сервера, но, вероятно, через некоторое время он переключится.

Я не знаю, как выдать «закрыть» каждому отдельному соединению, используемому экземпляром HttpClient. Это, безусловно, было бы идеально, но я собираюсь использовать фабричный метод GetOrCreate() для HttpClient и создавать новый экземпляр HttpClient каждые 120 секунд, чтобы гарантировать создание всех новых подключений. Я просто не уверен, как избавиться от старого экземпляра HttpClient после завершения всех запросов, чтобы закрыть соединения, но я думаю, что .NET позаботится об этом, потому что создание нового экземпляра HttpClient для каждого запроса никогда не было проблемой.

В качестве примечания я считаю, что обновление должно происходить на клиенте. Сервер не может нести ответственность за передачу изменений DNS или промежуточного программного обеспечения. Цель DNS состоит в том, чтобы компенсировать такую ​​ответственность, чтобы вы могли изменить IP-адреса в любое время без координации между клиентом и сервером, а не просто предоставить понятное имя для IP-адреса. Идеальный вариант использования — эластичный IP-адрес AWS. Изменение отражается только в DNS. AWS не будет знать, что нужно отправлять заголовки «close» с вашего веб-сервера, работающего на экземпляре ec2. AWS не будет знать о каждом веб-сервере, расположенном поверх экземпляра ec2, которым они управляют. Сценарий для старого сервера, не отправляющего «закрытый» заголовок, — это развертывание нового сервера, где точно такое же программное обеспечение развертывается на новом сервере со всем трафиком, переключаемым DNS. На старом сервере ничего не должно измениться, особенно если вам придется переключиться обратно. Все должно плавно меняться в одном месте с DNS.

Независимо от более серьезных проблем, обсуждаемых здесь, реализован ли ConnectionLeaseTimeout теперь в ядре? И если да, то почему этот вопрос не закрыт? Я смог закодировать пример точно так же, как исходный пост, без проблем только сейчас. Независимо от того, решит ли это проблемы с моим подключением к DNS или нет, он _кажется_ ConnectionLeaseTimeout _is_ реализован в ядре.

ServicePointMananager.ConnectionLeaseTimeout не реализован в .NET Core. Использование этого свойства в .NET Core не имеет смысла. Он работает только в .NET Framework. По умолчанию это свойство отключено в .NET Framework.

Я понимаю, что целью является создание одного экземпляра, но когда мы используем его в службах приложений Azure, мы постоянно сталкиваемся с исключениями HttpRequestException при довольно скромной нагрузке (упомянутое выше, 250 последовательных запросов). Если нет другого решения, избавление от HttpClient — это обходной путь, который нам пришлось реализовать, чтобы избежать появления этих постоянных исключений.

Нам удалось решить проблему исчерпания сокетов, обернув HttpClient в пользовательский API, который позволяет каждому HTTP-запросу в приложении повторно использовать один и тот же экземпляр HttpClient (он делает больше, чем просто это , как RestSharp но это тоже одно из преимуществ). Получение этого происходит за lock{} потому что каждые 60 минут мы создаем новый HttpClient и вместо этого передаем его обратно. Это позволило нам выполнять большие объемы http-запросов из нашего веб-приложения (из-за имеющейся у нас тяжелой интеграции), не используя повторно старые соединения, которые не были закрыты. Мы успешно занимаемся этим уже около года (после того, как столкнулись с проблемами исчерпания сокетов).

Я уверен, что в этом все еще есть влияние на производительность (особенно из-за всех блокировок), но это лучше, чем все, что умирает из-за исчерпания сокетов.

@KallDrexx, зачем вам нужно блокировать «глобальный» экземпляр? Наивно я бы хранил его в статике и просто заменял каждые 60 минут на новый. Этого будет недостаточно?

Наш код для получения HttpClient :

```С#
вар timeSinceCreated = DateTime.UtcNow - _lastCreatedAt;
если (timeSinceCreated.TotalSeconds > SecondsToRecreateClient)
{
замок (замок)
{
timeSinceCreated = DateTime.UtcNow - _lastCreatedAt;
если (timeSinceCreated.TotalSeconds > SecondsToRecreateClient)
{
_currentClient = новый HttpClient();
_lastCreatedAt = DateTime.UtcNow;
}
}
}

        return _currentClient;

```

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

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

Я вижу, так что это причина удобства.

Не знаю, можно ли отнести это к удобству.

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

Кроме того, мне все еще нужна некоторая мера блокировки, потому что я не знаю, является ли обновление DateTime атомарной операцией (я подозреваю, что нет), и поэтому некоторые потоки могут читать значение _lastCreatedAt в середине операции обновления.

И для того, чтобы все потоки могли повторно использовать старый клиент, в то время как один поток создает нового клиента, требуется много сложной логики, поэтому мы гарантируем, что один поток успешно создаст нового клиента. Не говоря уже о том, что нам все еще нужны блокировки, потому что мы не можем гарантировать, что один поток не вернет _currentClient пока другой поток создает его экземпляр.

Слишком много неизвестных и дополнительных сложностей, чтобы рисковать делать это в продакшене.

@KallDrexx

  • Кажется, ReaderWriterLockSlim лучше подходит для вашего использования.
  • Присваивание DateTime должно быть атомарным, если вы используете настоящий x64 (поскольку оно содержит одно поле ulong ). Обратите внимание, что ваш текущий код не является потокобезопасным, если это не так, потому что вы наблюдаете значение DateTime вне блокировки.
  • Подход на основе таймера, предложенный @karelz , вообще не потребует DateTime ... таймер просто переключит HttpClient . Вам все равно нужно окружить поле HttpClient некоторым барьером памяти ( volatile , Interlocked.Exchange т.д.).

Кажется, ReaderWriterLockSlim лучше подходит для вашего использования.

Опрятно, я не знал об этом классе! Тем не менее, ваше упоминание об этом также заставило меня задуматься об использовании SemaphoreSlim , так как это дружественно к асинхронности/ожиданию (там, где кажется, что ReaderWriterLockSlim нет), что может помочь с возможным конфликтом потоков, когда требуется новый HttpClient . Опять же, ReaderWriterLockSlim лучше справится с потенциальной проблемой неатомарности DateTime которую вы правильно указали, поэтому мне нужно подумать об этом, спасибо!

Подход на основе таймера, предложенный @karelz , вообще не потребует DateTime... таймер просто переключит HttpClient. Однако вам все равно нужно окружить поле HttpClient некоторым барьером памяти (volatile, Interlocked.Exchange и т. д.).

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

Спасибо.

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

Интересно, это все еще проблема в .net core 2.0? Нужно ли нам по-прежнему беспокоиться об изменении DNS?

Интересно, это все еще проблема в .net core 2.0? Нужно ли нам по-прежнему беспокоиться об изменении DNS?

Насколько я понимаю, это не проблема с самим DNS, просто HttpClient попытается повторно использовать существующие соединения (чтобы предотвратить исчерпание сокетов), и поэтому даже при изменении DNS вы все еще подключаетесь к старому сервер, потому что вы переходите на предыдущее соединение.

@KallDrexx , значит, у него все еще будут проблемы с сине-зеленым развертыванием?

@inoneyear, насколько я понимаю, да. Если вы создадите HttpClient и используете его против Production Green, а затем переключите производство на Blue, HttpClient прежнему будет иметь открытое соединение с Green, пока соединение не будет закрыто.

Я знаю, что это устарело, но я считаю, что вы не должны использовать DateTime.UtcNow для измерения временного интервала. Используйте источник мониторинга времени, например Stopwatch.GetTimestamp. Таким образом, вы защищены от локальной корректировки времени оператором или синхронизации времени. Были некоторые наборы микросхем с ошибками, из-за которых значения QPC возвращались, но я не думаю, что многие из них используются сейчас.

var sp = ServicePointManager.FindServicePoint(новый Uri("http://foo.bar/baz/123?a=ab"));
sp.ConnectionLeaseTimeout = 60*1000; // 1 минута

Этот код помещается в конструктор?

@whynotme8998 @lundcm Думаю, что нет. Если вы HttpClient - это синглтон, и вы получаете доступ к нескольким точкам, конструктор не будет правильным местом для его размещения.
Проверьте эту реализацию . Также в README проекта есть ссылка на сообщение в блоге, объясняющее проблему. Эта статья также указывает на эту тему.

Может ли кто-нибудь помочь мне точно понять, что означает ConnectionLeaseTimeout ?

Если в моем приложении я установил его на 1 минуту, значит ли это, что каждую минуту аренда соединения будет истекать по тайм-ауту? Что именно это означает для разговора с моим сервером?

Я вижу, люди говорят об обновлении DNS на серверной части? В стандартном веб-приложении Azure публикация моей серверной части ASP.NET приведет к обновлению DNS?

Разве 1-минутный тайм-аут не может оставить окно, в котором DNS обновляется, но тайм-аут еще не наступил?

Редактировать. Понимание того, что это не работает в .NET Standard 2.0. Какая работа вокруг?

Жду этого https://www.stevejgordon.co.uk/introduction-to-httpclientfactory-aspnetcore или внедряю его самостоятельно.

Изменение ConnectionLeaseTimeout не работало на Xamarin с .netstandard 2.0!!!

@paradisehuman Я полагаю, вы имеете в виду ServicePoint.ConnectionLeaseTimeout . Насколько я знаю, это не работает и на .NET Core. В .NET Core 2.1 мы представили SocketsHttpHandler.PooledConnectionLifetime . Кроме того, новый HttpClientFactory в ASP.NET может использовать оба и даже больше для вас.
Mono/Xamarin имеет собственную реализацию сетевого стека (они не используют исходный код CoreFX для работы в сети), поэтому было бы лучше зарегистрировать ошибку в их репозитории с более подробной информацией.

К сожалению, сегодня нет возможности сделать это с .NET Core. Либо ServicePointManager должен быть перенесен в .NET Core, либо аналогичная эквивалентная функциональность должна быть включена другим способом.

@onovotny , SocketsHttpHandler в .NET Core 2.1 предоставляет PooledConnectionLifetime, который служит цели, аналогичной ConnectionLeaseTimeout, только на уровне обработчика. Можем ли мы считать этот вопрос решенным на данном этапе?

@stephentoub только что увидел это сейчас, да, я думаю, что PooledConnectionLifetime должен решить эту проблему. Сообщество может открыть другую тему, если что-то еще нужно.

@onovotny @karelz @stephentoub ,

Согласитесь ли вы с тем, что это точное изложение того, как лучше всего решить исходную проблему singleton HttpClient/DNS?

  • В .NET Framework 2.0+ используйте ServicePoint.ConnectionLeaseTimeout .

  • В .NET Core 2.1 используйте SocketsHttpHandler.PooledConnectionLifetime .

  • Для всех других платформ/целей/версий нет надежного решения/хака/обхода, так что даже не пытайтесь.

По мере того, как я продолжаю узнавать что-то из этой темы и других (например, ConnectionLeaseTimeout не работает надежно на разных платформах в .NET Standard 2.0), я возвращаюсь к чертежной доске, пытаясь решить эту проблему с помощью библиотеки. который работает на как можно большем количестве платформ/версий. Обнюхивание платформ — это нормально. Периодическое удаление/воссоздание HttpClient допустимо, если это эффективно. Я почти уверен, что ошибся в своей первой попытке, которая заключалась в том, чтобы просто посылать хосту заголовок connection:close через равные промежутки времени. Заранее благодарю за любой совет!

@tmenier Для всех других платформ/целей/версий нет надежного решения/хака/обхода, так что даже не пытайтесь.

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

@karelz Спасибо, я думаю, это дает мне путь вперед. Возможно, мне следует просто пропустить анализ платформы и повторно использовать экземпляры везде, или рекомендуется использовать другие подходы, где они доступны (чтобы избежать накладных расходов и т. д.)?

Хитрость может заключаться в том, чтобы знать, когда безопасно удалять повторно используемые экземпляры (мне нужно беспокоиться о параллелизме/ожидающих запросах), но я могу взглянуть на то, как HttpClientFactory справляется с этим. Еще раз спасибо.

@tmenier Вы не хотите перерабатывать экземпляры каждый вызов HttpClient , так как это приведет к исчерпанию сокета TCP. Переработкой необходимо управлять (тайм-аут или поддержка пула с HttpClient s, срок действия которых может истечь.

@KallDrexx Верно, я просто говорю о _периодическом_ повторном использовании синглтона (или «псевдо»-синглетона), например, каждые несколько минут. Я действительно считаю, что это будет включать управление пулом и некоторую отложенную утилизацию мертвых экземпляров, как только будет определено, что у них нет ожидающих запросов.

Ах, извините, неправильно понял, что вы имели в виду под переработкой.

Вам не нужно избавляться от старых. Пусть GC их соберет.
Код должен быть таким же простым, как периодическая установка нового «псевдо»-синглета на основе таймера. Больше ничего.

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

@tmenier Об использовании SocketsHttpHandler.PooledConnectionLifetime в .NET Core 2.1 создание HttpClientHandler полностью инкапсулировано в https://github.com/dotnet/wcf/blob/master/src/System.Private.ServiceModel/src/System/ ServiceModel/Channels/HttpChannelFactory.cs. Есть ли способ сделать это при использовании клиента WCF?

@edavedian Я бы посоветовал спросить в репозитории dotnet/wcf. копия @mconnew @Lxiamail

базовый стек разрешается либо в WinHTTP, либо в Curl, которые не предоставляют дополнительные ручки управления

Есть ли в .Net Core HTTP-клиент, работающий через сокеты?

Есть ли в .Net Core HTTP-клиент, работающий через сокеты?

Да, SocketsHttpHandler, и с версии 2.1 он используется по умолчанию.

Кэширует ли HttpClient URI, которые он находит , аналогично тому, что, RestClient ? Если это так, сообщите мне, в какой версии (.NET Core 3.0/.NET Framework 3.0 и т. д.) это поддерживается.

Я вижу, что кэширование выполняется только в RestClient если проект является приложением .Net Core 2.1+?

Кэширует ли HttpClient URI, которые он попадает, аналогично тому, что, как я полагаю, делает RestClient?

HttpClient не выполняет никакого кэширования.

@SpiritBob неясно, о какой форме кэширования вы говорите. Этот вопрос закрыт; если вы не могли бы открыть новый вопрос с вопросами, мы можем помочь вам найти ответы.

@scalablecory Я считаю, что Дэвидш ответил с тем, что я имел в виду. Спасибо!

Только что закончил читать эту ветку, и кажется, что был вариант использования, который не был рассмотрен.

При использовании HttpClientFactory получающиеся HttpClient автоматически закрывают соединения и выполняют новый поиск DNS, когда они получают HttpRequestException с внутренним SocketException указывающим, что хост больше недействителен и этот DNS потенциально изменился (код ошибки 11001 - такой хост не известен)?

Если нет, то каким обходным путем можно заставить HttpClient выполнять поиск в DNS до истечения срока жизни в записях DNS?

В настоящее время я использую HttpClientFactory с пользовательским HttpMessageHandler (настроенным с помощью внедрения зависимостей с помощью AddHttpClient<T, U>().ConfigurePrimaryHttpMessageHandler(...) ), и когда запись DNS изменяется до истечения срока жизни, возникает ошибка. В частности, я вижу, что это происходит при выполнении нескольких загрузок в хранилище BLOB-объектов Azure.

Была ли эта страница полезной?
0 / 5 - 0 рейтинги

Смежные вопросы

btecu picture btecu  ·  3Комментарии

EgorBo picture EgorBo  ·  3Комментарии

bencz picture bencz  ·  3Комментарии

jchannon picture jchannon  ·  3Комментарии

matty-hall picture matty-hall  ·  3Комментарии