HttpWebResponse не будет масштабироваться для одновременных исходящих запросов
У меня есть серверное приложение ASP.NET 3.5, написанное на С#. Он отправляет исходящие запросы в REST API с помощью HttpWebRequest и HttpWebResponse.
Я установил тестовое приложение для отправки этих запросов на отдельные потоки (чтобы смутно имитировать concurrency для сервера).
Обратите внимание, что это скорее вопрос Mono/Environment, чем вопрос с кодом; поэтому имейте в виду, что приведенный ниже код не является дословным; просто вырезать/вставить функциональные биты.
Вот несколько псевдокодов:
// threaded client piece
int numThreads = 1;
ManualResetEvent doneEvent;
using (doneEvent = new ManualResetEvent(false))
{
for (int i = 0; i < numThreads; i++)
{
ThreadPool.QueueUserWorkItem(new WaitCallback(Test), random_url_to_same_host);
}
doneEvent.WaitOne();
}
void Test(object some_url)
{
// setup service point here just to show what config settings Im using
ServicePoint lgsp = ServicePointManager.FindServicePoint(new Uri(some_url.ToString()));
// set these to optimal for MONO and .NET
lgsp.Expect100Continue = false;
lgsp.ConnectionLimit = 100;
lgsp.UseNagleAlgorithm = true;
lgsp.MaxIdleTime = 100000;
_request = (HttpWebRequest)WebRequest.Create(some_url);
using (HttpWebResponse _response = (HttpWebResponse)_request.GetResponse())
{
// do stuff
} // releases the response object
// close out threading stuff
if (Interlocked.Decrement(ref numThreads) == 0)
{
doneEvent.Set();
}
}
Если я запустил приложение на своей локальной машине разработки (Windows 7) на веб-сервере Visual Studio, я могу запустить numThreads и получить такое же время отклика avg с минимальным изменением, будь то 1 "пользователь" или 100.
Публикация и развертывание приложения в Apache2 в среде Mono 2.10.2, время отклика масштабируется почти линейно. (т.е. 1 нить = 300 мс, 5 ниток = 1500 мс, 10 потоков = 3000 мс). Это происходит независимо от конечной точки сервера (другое имя хоста, другая сеть и т.д.).
Используя IPTRAF (и другие сетевые инструменты), похоже, что приложение открывает только 1 или 2 порта для маршрутизации всех подключений и оставшихся ответов ждать.
Мы создали аналогичное PHP-приложение и развернуто в Mono с одинаковыми запросами и шкалой ответов соответственно.
Я пропустил каждый параметр конфигурации, о котором я могу думать о Mono и Apache, и параметр ONLY, который отличается от двух сред (по крайней мере, в коде), заключается в том, что иногда ServicePoint SupportsPipelining = false в Mono, в то время как это true от моей машины.
Кажется, что ConnectionLimit (по умолчанию 2) по какой-то причине не изменяется в Mono, но я устанавливаю его более высокое значение как в коде, так и в файле web.config для указанных узлов.
Либо я, и моя команда упускают из виду что-то значительное или это какая-то ошибка в Mono.
Ответы
Ответ 1
Я считаю, что вы столкнулись с узким местом в HttpWebRequest
. В веб-запросах каждый использует общую инфраструктуру точек обслуживания в рамках платформы .NET. Это, как представляется, предназначено для повторного использования запросов на один и тот же хост, но по моему опыту возникают два узких места.
Во-первых, точки обслуживания допускают только два одновременных подключения к данному хосту по умолчанию, чтобы соответствовать спецификации HTTP. Это можно переопределить, установив статическое свойство ServicePointManager.DefaultConnectionLimit
на большее значение. Подробнее см. MSDN. Похоже, вы уже обращаетесь к этому для самой отдельной точки обслуживания, но из-за схемы блокировки concurrency на уровне точки обслуживания это может способствовать узкому месту.
Во-вторых, возникает проблема с детализацией блокировки в классе ServicePoint
. Если вы декомпилируете и посмотрите на источник ключевого слова lock
, вы обнаружите, что он использует сам экземпляр для синхронизации и делает это во многих местах. Когда экземпляр точки обслуживания разделяется между веб-запросами для данного хоста, по моему опыту это имеет тенденцию к узкому месту, так как больше HttpWebRequests
открывается и заставляет его плохо масштабироваться. Этот второй пункт - это, в основном, личное наблюдение и тряска вокруг источника, поэтому возьмите его с солью; Я бы не считал это авторитетным источником.
К сожалению, я не нашел разумной замены в то время, когда я работал с ней. Теперь, когда веб-API ASP.NET был выпущен, вы можете обратиться к HttpClient. Надеюсь, что это поможет.
Ответ 2
Я знаю, что это довольно старый, но я делаю это здесь, если это может помочь кому-то другому, кто сталкивается с этой проблемой. Мы столкнулись с той же проблемой с параллельными исходящими HTTPS-запросами. В игре есть несколько проблем.
Первая проблема заключается в том, что ServicePointManager.DefaultConnectionLimit
не изменил лимит подключения, насколько я могу судить. Установив это значение 50, создав новое соединение, а затем проверим ограничение соединения на точке обслуживания для нового подключения, выполните следующие действия. 2. Установив его на этой точке обслуживания до 50, как представляется, работает и сохраняется для всех подключений, которые в конечном итоге пройдут эта точка обслуживания.
Вторая проблема, с которой мы столкнулись, - это потоки. Текущая реализация пула моно потоков, по-видимому, создает не более 2 новых потоков в секунду. Это вечность, если вы делаете много параллельных запросов, которые начинаются точно в одно и то же время. Чтобы противодействовать этому, мы попытались установить ThreadPool.SetMinThreads на большее число. Похоже, что Mono создает только один новый поток при выполнении этого вызова, независимо от дельта между текущим числом потоков и желаемым номером. Мы смогли обойти это, вызвав SetMinThreads в цикле, пока пул потоков не будет иметь желаемое количество простоя.
Я открыл ошибку в последнем выпуске, потому что тот, который я наиболее уверен, работает не так, как предполагалось: https://bugzilla.xamarin.com/show_bug.cgi?id=7055
Ответ 3
Если @jake-moshenko прав насчет ServicePointManager.DefaultConnectionLimit
не имеет никакого эффекта, если он изменен в Mono, пожалуйста, укажите это как ошибку в http://bugzilla.xamarin.com/.
Однако я бы попробовал некоторые вещи, прежде чем полностью отбросить это как проблему Mono:
- Попробуйте использовать сборщик мусора SGen вместо старого behm one, передав
--gc=sgen
в качестве флага для моно.
- Если вышеизложенное не помогает, перейдите на Mono 3.2 (кстати, BTW по умолчанию использует SGEN GC), потому что было много исправлений, так как вы задали вопрос.
- Если приведенное выше не помогает, создайте свою собственную Mono (ведущую ветвь), так как этот важный запрос на перенос о потоковом соединении недавно.
- Если приведенное выше не помогает, добавьте свой собственный моно с этот запрос на перенос. Если он исправляет вашу проблему, добавьте "+1" к запросу pull. Это может быть исправление для ошибка 7055.