HttpWebResponse не масштабируется для одновременных исходящих запросов
У меня есть серверное приложение ASP.NET 3.5, написанное на C#. Это делает исходящие запросы к API REST, используя HttpWebRequest и HttpWebResponse.
Я настроил тестовое приложение для отправки этих запросов в отдельных потоках (чтобы смутно имитировать параллелизм к серверу).
Обратите внимание, что это больше вопрос моно / среды, чем вопрос кода; поэтому имейте в виду, что приведенный ниже код не является дословным; просто вырезать / вставить функциональные биты.
Вот некоторый псевдокод:
// 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 и получить такое же среднее время отклика с минимальными изменениями, будь то 1 пользователь или 100.
После публикации и развертывания приложения в Apache2 в среде Mono 2.10.2 время отклика масштабируется почти линейно. (т. е. 1 поток = 300 мс, 5 потоков = 1500 мс, 10 потоков = 3000 мс). Это происходит независимо от конечной точки сервера (другое имя хоста, другая сеть и т. Д.).
Используя IPTRAF (и другие сетевые инструменты), создается впечатление, что приложение открывает только 1 или 2 порта для маршрутизации всех соединений, а оставшиеся ответы должны ждать.
Мы создали похожее PHP-приложение и развернули его в Mono с теми же запросами и ответами, соответственно масштабируемыми.
Я пробежал через каждый отдельный параметр конфигурации, который я могу придумать для Mono и Apache, и единственный параметр, который отличается в двух средах (по крайней мере, в коде), заключается в том, что иногда ServicePoint SupportsPipelining = false в Mono, хотя это верно для моего машина.
Похоже, что ConnectionLimit (по умолчанию 2) по какой-то причине не изменяется в Mono, но я устанавливаю для него более высокое значение как в коде, так и в файле web.config для указанных хостов.
Либо я и моя команда пропускаем что-то значительное, либо это какая-то ошибка в Mono.
3 ответа
Я считаю, что вы попали в узкое место в HttpWebRequest
, Каждый веб-запрос использует общую инфраструктуру точки обслуживания в.NET Framework. Похоже, это предназначено для повторного использования запросов к одному и тому же хосту, но, по моему опыту, приводит к двум узким местам.
Во-первых, точки обслуживания разрешают только два одновременных подключения к данному хосту по умолчанию, чтобы соответствовать спецификации HTTP. Это можно переопределить, установив статическое свойство ServicePointManager.DefaultConnectionLimit
на более высокое значение. Смотрите эту страницу MSDN для более подробной информации. Похоже, что вы уже обращаетесь к этому для отдельной точки обслуживания, но из-за схемы блокировки параллелизма на уровне точки обслуживания, это может способствовать возникновению узкого места.
Во-вторых, существует проблема с гранулярностью блокировки в ServicePoint
сам класс. Если вы декомпилируете и посмотрите на источник для lock
Ключевое слово, вы обнаружите, что он использует сам экземпляр для синхронизации и делает это во многих местах. С учетом того, что экземпляр точки обслуживания распределяется между веб-запросами для данного хоста, по моему опыту, это приводит к узким местам, поскольку HttpWebRequests
открыты и заставляют его масштабироваться плохо. Этот второй пункт в основном касается личного наблюдения и осмотра источника, так что возьмите его с крошкой соли; Я бы не стал считать это авторитетным источником.
К сожалению, в то время, когда я работал с ним, я не нашел разумной замены. Теперь, когда веб-API ASP.NET выпущен, вы, возможно, захотите взглянуть на HttpClient. Надеюсь, это поможет.
Я знаю, что это довольно старо, но я помещаю это здесь в случае, если это могло бы помочь кому-то еще, кто сталкивается с этой проблемой. Мы столкнулись с той же проблемой с параллельными исходящими HTTPS-запросами. Есть несколько вопросов в игре.
Первая проблема заключается в том, что ServicePointManager.DefaultConnectionLimit
насколько я могу судить, не изменил лимит подключения. Установка этого значения на 50, создание нового соединения, а затем проверка предела соединения в точке обслуживания для нового соединения говорит 2. Установка этого значения в этой точке обслуживания на 50, как только кажется, работает и сохраняется для всех соединений, которые в конечном итоге будут проходить этот пункт обслуживания.
Вторая проблема, с которой мы столкнулись, была с многопоточностью. Текущая реализация пула монопотоков, по-видимому, создает не более 2 новых потоков в секунду. Это вечность, если вы делаете много параллельных запросов, которые начинаются в одно и то же время. Чтобы противодействовать этому, мы попытались установить для ThreadPool.SetMinThreads более высокое значение. Похоже, что Mono создает только 1 новый поток, когда вы делаете этот вызов, независимо от разницы между текущим числом потоков и желаемым номером. Мы смогли обойти эту проблему, вызывая SetMinThreads в цикле, пока в пуле потоков не будет необходимое количество свободных потоков.
Я открыл ошибку, связанную с последней проблемой, потому что я уверен, что она не работает должным образом: https://bugzilla.xamarin.com/show_bug.cgi?id=7055
Если @ Джейк-Мошенко прав насчет ServicePointManager.DefaultConnectionLimit
не имеет никакого эффекта, если изменено в Mono, пожалуйста, сообщите об этом как ошибку в http://bugzilla.xamarin.com/.
Тем не менее, я бы попробовал кое-что, прежде чем полностью отбросить это как проблему Mono:
- Попробуйте использовать сборщик мусора SGen вместо старого boehm, передав
--gc=sgen
как флаг моно. - Если вышеперечисленное не помогает, обновитесь до Mono 3.2 (кстати, по умолчанию также SGEN GC), потому что с тех пор, как вы задали вопрос, было много исправлений.
- Если вышеперечисленное не помогает, создайте свой собственный Mono (главная ветвь), так как этот важный запрос на извлечение потоков был недавно объединен
- Если вышеупомянутое не помогает, создайте свое собственное Mono с добавленным этим запросом. Если это решит вашу проблему, пожалуйста, добавьте "+1" к запросу. Это может быть исправлением ошибки 7055.