Проблема масштабируемости при использовании исходящих асинхронных веб-запросов в IIS 7.5

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

ОБНОВЛЕНИЕ 19/2, 2013: Мы сняли некоторые знаки вопроса в этом, и у меня есть теория о том, что главная проблема, которую яобновлю ниже. Не готов написатьрешено» Ответ на это еще, хотя.

ОБНОВЛЕНИЕ 24/4, 2013: Вещи были стабильны в производстве (хотя я считаю, что это временно) в течение некоторого времени, и я думаю, что это связано с двумя причинами. 1) увеличение порта и 2) уменьшение количества исходящих (переадресованных) запросов. Я'Я продолжу это обновление дальше в правильном контексте.

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

Процессор только на 20%, но мы получаем ошибки HTTP 503 на входящие запросы, и многие исходящие веб-запросы получают следующее исключение: «SocketException: Операция над сокетом не может быть выполнена, потому что системе не хватило буферного пространства или потому что очередь была переполнена » Очевидно, что где-то существует узкое место в масштабируемости, и нам нужно выяснить, что это такое, и можно ли решить его с помощью конфигурации.

Контекст приложения:

Мы используем интегрированный управляемый конвейер IIS v7.5 с использованием .NET 4.5 в 64-разрядной операционной системе Windows 2008 R2. Мы используем только 1 рабочий процесс в IIS. Аппаратные средства немного различаются, но машина, используемая для проверки ошибки, - это ядро Intel Xeon 8 (16-многопоточное)

Мы используем как асинхронные, так и синхронные веб-запросы. Те, кто работает асинхронно, используют новую поддержку асинхронности .NET, чтобы каждый входящий запрос делал несколько HTTP-запросов в приложении к другим серверам по постоянным соединениям TCP (keep-alive). Время выполнения синхронного запроса составляет 0-32 мс (более длительное время происходит из-за переключения контекста потока). Для асинхронных запросов время выполнения может быть до 120 мс, прежде чем запросы будут прерваны.

Обычно каждый сервер обслуживает до ~ 1000 входящих запросов. Исходящие запросы составляют ~ 300 запросов / сек до ~ 600 запросов / сек, когда проблема начинает возникать. Проблемы возникают только при исходящей асинхронности. запросы на сервере включены, и мы превышаем определенный уровень исходящих запросов (~ 600 запросов / с).

Возможные решения проблемы:

Поиск в Интернете по этой проблеме выявляет множество возможных решений кандидатов. Однако они очень сильно зависят от версий .NET, IIS и операционной системы, поэтому требуется время, чтобы что-то найти в нашем контексте (anno 2013).

Ниже приведен список кандидатов на решения и выводы, к которым мы пришли к настоящему моменту в отношении нашего контекста конфигурации. На данный момент я классифицировал обнаруженные проблемные области по следующим основным категориям:

Некоторые очереди заполняютсяПроблемы с TCP-соединениями и портами (ОБНОВЛЕНИЕ 19/2, 2013: Это проблема)Слишком медленное распределение ресурсовПроблемы с памятью (ОБНОВЛЕНИЕ 19/2, 2013: Это скорее всего другая проблема)1) Некоторые очереди заполняются

Сообщение об исключении исходящего асинхронного запроса действительно указывает, что некоторая очередь буфера была заполнена. Но это не говорит, какая очередь / буфер. ЧерезIIS форум (и ссылка на блог там упоминается) Мне удалось выделить 4 из 6 (или более) различных типов очередей в конвейере запросов, помеченных A-F ниже.

Хотя следует отметить, что из всех указанных ниже очередей мы наверняка видим, что 1.B) Счетчик производительности ThreadPool «Запросы в очереди» переполняется во время проблемной загрузки.Таким образом, вполне вероятно, что причина проблемы находится на уровне .NET, а не ниже этого (C-F).

1.А) Очередь уровня .NET Framework?

Мы используем класс платформы .NET WebClient для выдачи асинхронного вызова (асинхронная поддержка), в отличие от HttpClient, с которым мы столкнулись, имели ту же проблему, но с гораздо более низким пороговым значением req / s. Мы не знаем, скрывает ли реализация .NET Framework какие-либо внутренние очереди или нет над пулом потоков. Мы неЯ думаю, что это так.

1.B) .NET Thread Pool

Пул потоков действует как естественная очередь, поскольку планировщик потоков .NET (по умолчанию) выбирает потоки из пула потоков для выполнения.

Счетчик производительности: [ASP.NET v4.0.30319]. [Запросы в очереди].

Возможности конфигурации:

(ApplicationPool) maxConcurrentRequestsPerCPU должно быть 5000 (вместо предыдущих 12). Таким образом, в нашем случае это должно быть 5000 * 16 = 80 000 запросов / сек, чего должно быть достаточно в нашем сценарии.(processModel) autoConfig = true / false, что позволяетнекоторая конфигурация, связанная с threadPool устанавливается в соответствии с конфигурацией машины.Мы используем true, который является потенциальным кандидатом на ошибку, поскольку эти значения могут быть ошибочно установлены для нашей (высокой) потребности.1.C) Глобальная, общая для процесса, собственная очередь (только в интегрированном режиме IIS)

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

Счетчик производительности:[ASP.NET v4.0.30319]. [Запросы в собственной очереди]

Возможности конфигурации: ????

1.D) Очередь HTTP.sys

Эта очередь отличается от очереди 1.C) выше. Вот'с объяснением, как мне сказали «Очередь ядра HTTP.sys, по сути, является портом завершения, по которому пользовательский режим (IIS) получает запросы из режима ядра (HTTP.sys). У него есть предел очереди, и когда он будет превышен, вы получите код состояния 503. Журнал HTTPErr также будет указывать, что это произошло, регистрируя состояние 503 и QueueFull «.

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

Возможности конфигурации: Это установлено в IIS для пула приложений, расширенный параметр: Длина очереди. Значение по умолчанию - 1000. Я видел рекомендации увеличить его до 10.000. Хотя попытка этого увеличения не решила нашу проблему.

1.E) Операционная система неизвестных очередей?

Хотя это маловероятно, я полагаю, что ОС может иметь очередь где-то между буфером сетевой карты и очередью HTTP.sys.

1.F) Буфер сетевой карты:

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

Счетчик производительности Windows: [Сетевой интерфейс]. [Полученные пакеты отброшены] с использованием экземпляра сетевой карты.

Возможности конфигурации: ????

2) Проблемы с TCP-соединениями и портами

Это кандидат, который появляется здесь и там, хотя наши исходящие (асинхронные) TCP-запросы сделаны из постоянного (keep-alive) TCP-соединения. Таким образом, по мере роста трафика количество доступных временных портов должно расти только из-за входящих запросов. И мы точно знаем, что проблема возникает только тогда, когда у нас включены исходящие запросы.

Однако проблема все еще может возникнуть из-за того, что порт выделен в течение более длительного периода времени запроса. Для выполнения исходящего запроса может потребоваться до 120 мс (до отмены задачи .NET (потока)), что может означать, что количество портов выделено на более длительный период времени. Анализируя счетчик производительности Windows, проверяет это предположение, поскольку число TCPv4. [Установленное соединение] переходит от обычных 2-3000 к пикам до почти 12.000 в общей сложности при возникновении проблемы.

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

Когда мы пытаемся использовать netstat на сервере, он в основном возвращается без какого-либо вывода вообще, также использование TcpView показывает очень мало элементов в начале. Если мы позволим TcpView работать некоторое время, он скоро начнет показывать новые (входящие) соединения довольно быстро (скажем, 25 соединений в секунду). Почти все соединения находятся в состоянии TIME_WAIT с самого начала, что свидетельствует о том, что они уже завершены и ожидают очистки. Используют ли эти соединения эфемерные порты? Локальный порт всегда равен 80, а удаленный порт увеличивается. Мы хотели использовать TcpView для просмотра исходящих соединений, но мы не можемя не вижу их в списке, что очень странно. Можно'Эти два инструмента управляют количеством соединений, которые у нас есть?(Продолжение следует .... Но, пожалуйста, заполните информацию, если вы знаете это ...)

Более того, как боковой удар здесь. Было предложено в этом блоге "Использование потоков ASP.NET в IIS 7.5, IIS 7.0 и IIS 6.0 " для ServicePointManager.DefaultConnectionLimit должно быть установлено значение int maxValue, которое в противном случае могло бы стать проблемой. Но в .NET 4.5 это значение по умолчанию уже с самого начала.

ОБНОВЛЕНИЕ 19/2, 2013:

Разумно предположить, что мы действительно достигли максимального ограничения в 16,384 порта. Мы удвоили количество портов на всех серверах, кроме одного, и только старый сервер столкнулся бы с проблемой при достижении старой пиковой нагрузки исходящих запросов. Так почему же TCP.v4. [Установленные соединения] никогда не показывали нам большее число, чем ~ 12.000 в трудные времена? МОЯ теория: Скорее всего, хотя и не установлен как факт (пока), счетчик производительности TCPv4. [Установленные соединения] не эквивалентен количеству портов, которые выделены в данный момент. У меня еще не было времени, чтобы наверстать упущенное изучение состояния TCP, но я предполагаю, что TCP больше состояний, чем чтоСоединение установлено" показывает, какие порты будут отображаться как занятые. Хотя, поскольку мы не можем использоватьСоединение установлено" Счетчик производительности как способ обнаружения опасности нехватки портов, важно найти другой способ обнаружения при достижении этого максимального диапазона портов. И, как описано в тексте выше, мы не можем использовать ни с NetStat, ни с приложением TCPview для этого на наших производственных серверах. Это проблема! (Я'подробнее напишу об этом в предстоящем ответе думаю на этот пост)Количество портов ограничено на окнах максимумом до 65,535 (хотя первые ~ 1000, вероятно, не должны использоваться). Но должно быть возможно избежать проблемы нехватки портов, уменьшив время для состояния TCP TIME_WAIT (по умолчанию до 240 секунд), как описано во многих местах. Это должно освободить порты быстрее. Сначала я немного сомневался в том, что это нужно делать, поскольку мы используем как длительные запросы к базе данных, так и вызовы WCF по TCP, и я бы не сталМне нравится уменьшать временные ограничения. Несмотря на то, что я еще не завладел чтением моего конечного автомата TCP, я думаю, что в конце концов это не будет проблемой. Состояние TIME_WAIT, как мне кажется, существует только для того, чтобы дать клиенту возможность рукопожатия при правильном завершении работы. Таким образом, фактическая передача данных по существующему TCP-соединению не должна истекать из-за этого ограничения по времени. В худшем случае клиент не выключается должным образом, и вместо этого ему нужно время ожидания. Я думаю, что все браузеры могут не реализовывать это правильно, и это может быть проблемой только на стороне клиента. Хотя я догадываюсь немного здесь ...

КОНЕЦ ОБНОВЛЕНИЯ 19/2, 2013

ОБНОВЛЕНИЕ 24/4, 2013: Мы увеличили количество портов до максимального значения. В то же время мы не получаем столько отправленных исходящих запросов, сколько раньше. Эти два в сочетании должны быть причиной, почему у нас не было никаких инцидентов. Однако это только временно, так как количество исходящих запросов в будущем должно снова увеличиться на этих серверах. Таким образом, проблема заключается, я думаю, в том, что порт для входящих запросов должен оставаться открытым в течение периода времени для ответа перенаправленных запросов. В нашем приложении этот предел отмены для этих переадресованных запросов составляет 120 мс, которые можно сравнить с обычным <1 мс для обработки неоправданного запроса. Поэтому, по сути, я считаю, что определенное количество портов является основным узким местом масштабируемости на серверах с такой высокой пропускной способностью (>1000 запросов / сек на ~ 16 ядерных машинах), которые мы используем. Это в сочетании с работой GC по перезагрузке кэша (см. Ниже) делает сервер особенно уязвимым.

КОНЕЦ ОБНОВЛЕНИЯ 24/4

3) Слишком медленное распределение ресурсов

Наши счетчики производительности показывают, что количество запросов в очереди в пуле потоков (1B) сильно колеблется во время проблемы. Таким образом, потенциально это означает, что мы имеем динамическую ситуацию, в которой длина очереди начинает колебаться из-за изменений в среде. Например, это может иметь место, если существуют механизмы защиты от затопления, которые активируются при затоплении трафика. Как есть, у нас есть ряд таких механизмов:

3.А) Балансировщик веб-нагрузки

Когда дела идут очень плохо и сервер отвечает ошибкой HTTP 503, балансировщик нагрузки автоматически удаляет веб-сервер из работы в течение 15 секунд. Это означает, что другие серверы будут принимать увеличенную нагрузку в течение периода времени. В течение "период охлаждения »сервер может завершить обслуживание своего запроса, и он будет автоматически восстановлен, когда балансировщик нагрузки выполнит следующий пинг. Конечно, это хорошо, если все серверы неУ меня не будет проблем сразу. К счастью, пока мы не были в такой ситуации.

3.B) Применение конкретного клапана

В веб-приложении у нас есть собственный сконструированный клапан (да. Это "клапан», Не "значение") запускается счетчиком производительности Windows для запросов в очереди в пуле потоков. В Application_Start запущен поток, который каждую секунду проверяет значение этого счетчика производительности. И если значение превышает 2000, весь исходящий трафик перестает инициироваться. В следующую секунду, если значение очереди ниже 2000, исходящий трафик начинается снова.

Странно то, что это не помогло нам достичь сценария ошибки, так как мы неУ этого есть много регистрации этого происходящего. Это может означать, что когда трафик сильно ударяется о нас, дела идут очень быстро, поэтому проверка на 1-секундный интервал времени оказывается слишком высокой.

3.C) Пул потоков медленное увеличение (и уменьшение) потоков

Есть еще один аспект этого. Когда в пуле приложений требуется больше потоков, эти потоки распределяются очень медленно. Из того, что я прочитал, 1-2 темы в секунду. Это так, потому что создавать потоки дорого, а вы неВ любом случае, мне не нужно слишком много потоков, чтобы избежать дорогостоящего переключения контекста в синхронном случае. Я думаю, это естественно. Тем не менее, это также должно означать, что если неожиданно большой поток трафика ударит нас, количество потоков не будет достаточно близко, чтобы удовлетворить потребность в асинхронном сценарии, и начнется очередь запросов. Я думаю, что это очень вероятный проблемный кандидат. Тогда одним из возможных решений может быть увеличение минимального количества созданных потоков в ThreadPool. Но я думаю, это также может повлиять на производительность синхронно выполняющихся запросов.

4) Проблемы с памятью

(Джои Рейес писал об этомздесь в блоге) Поскольку объекты собираются позднее для асинхронных запросов (в нашем случае до 120 мс позже), может возникнуть проблема с памятью, поскольку объекты могут быть переведены в поколение 1, и память не будет вызываться так часто, как следовало бы. Повышенное давление на сборщик мусора может привести к переключению контекста расширенного потока и дальнейшему снижению производительности сервера.

Тем не менее, мы неМы не видим увеличения использования GC- или CPU во время проблемы, поэтому мы неМы не думаем, что предложенный механизм управления процессором является для нас решением.

ОБНОВЛЕНИЕ 19/2, 2013: Мы используем механизм замены кеша через регулярные интервалы, при которых (почти) полный кэш в памяти перезагружается в память, а старый кеш может собирать мусор. В это время GC придется работать усерднее и воровать ресурсы при обычной обработке запросов. Использование счетчика производительности Windows для переключения контекста потока показывает, что количество переключений контекста значительно уменьшается по сравнению с обычным высоким значением во время интенсивного использования ГХ. Я думаю, что во время таких перезагрузок кеша сервер становится более уязвимым для постановки в очередь запросов, и необходимо уменьшить площадь, занимаемую GC. Одним из возможных путей решения этой проблемы было бы просто заполнить кэш, не выделяя память все время. Немного больше работы, но это должно быть выполнимо.

ОБНОВЛЕНИЕ 24/4, 2013: Я все еще нахожусь в середине настройки перезагрузки кеш-памяти, чтобы избежать запуска GC. Но у нас обычно около 1000 запросов в очереди временно, когда GC работает. Поскольку он работает во всех потоках, естественно, что он крадет ресурсы из обычной обработки запросов. Я'Я обновлю этот статус, как только эта настройка будет развернута, и мы увидим разницу.

КОНЕЦ ОБНОВЛЕНИЯ 24/4

Ответы на вопрос(2)

Ваш ответ на вопрос