Подтвердить что ты не робот

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1) Некоторые очереди заполняются

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

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

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

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

1.B) Пул потоков .NET

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

Счетчик производительности: [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 queue

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

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

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

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

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

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

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

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

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

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

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

Однако проблема может возникнуть из-за того, что порт выделяется в течение более длительного периода запроса. Исходящий запрос может занять до 120 мс для выполнения (до отмены задачи .NET(поток)), что может означать, что количество портов распределяется в течение более длительного периода времени. Анализируя счетчик производительности Windows, проверяет это предположение с момента появления TCPv4. [Connection Foundlished] идет от нормального 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. [Connections Foundlished] никогда не показывает нам большее количество, чем ~ 12.000 в проблемные моменты? Теория MY: Скорее всего, хотя и не установлена ​​как факт (пока), счетчик производительности TCPv4. [Connections Established] не эквивалентен количеству портов, которые в настоящее время выделены. Я еще не успел догнать состояние TCP-процесса, но я предполагаю, что существует больше состояний TCP, чем показано в "Connection Founded", что сделает порт зацикленным. Хотя, поскольку мы не можем использовать счетчик производительности "Установленный соединение" как способ обнаружить опасность выхода из портов, важно найти другой способ обнаружения при достижении этого максимального диапазона портов. И, как описано в вышеприведенном тексте, мы не можем использовать ни NetStat, ни приложение TCPview для этого на наших производственных серверах. Это проблема! (Я напишу об этом в следующем ответе, который я думаю на этот пост)
  • Количество портов ограничено в окнах до максимального уровня 65.535 (хотя, вероятно, первый ~ 1000, вероятно, не будет использоваться). Но должно быть возможно избежать проблемы с исчерпанием портов, уменьшив время для TCP-состояния TIME_WAIT (по умолчанию до 240 секунд), как описано во многих местах. Он должен быстрее освобождать порты. Вначале я был немного настроен на это, так как мы используем как длительные запросы к базе данных, так и вызовы WCF для TCP, и я не хотел бы сокращать временные ограничения. Несмотря на то, что я еще не добрался до своего компьютера, я думаю, что это может быть не проблема. Состояние TIME_WAIT, я думаю, существует только там, чтобы разрешить рукопожатие надлежащего закрытия клиента. Таким образом, фактическая передача данных по существующему TCP-соединению не должна истекать из-за этого ограничения времени. В худшем случае клиент не закрывается должным образом, и вместо этого он не приходит в тайм-аут. Я предполагаю, что все браузеры могут не реализовать это правильно, и это может быть проблемой только на стороне клиента. Хотя я немного догадываюсь здесь...

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

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

END UPDATE 24/4

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

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

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

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

3.B) Клапан, специфичный для применения

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

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

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

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

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

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

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

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

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

END UPDATE 24/4

4b9b3361

Ответ 1

Я применил обратный прокси-сервер через Async Http Handler для целей бенчмаркинга (как часть моей кандидатской диссертации) и столкнулся с теми же проблемами, что и вы.

Для масштабирования обязательно иметь параметр processModel в false и точную настройку пулов потоков. Я обнаружил, что, вопреки тому, что говорит документация по умолчанию для processModel, многие пулы потоков неправильно настроены, когда для параметра processModel установлено значение true. Параметр maxConnection также важен, так как он ограничивает вашу масштабируемость, если предел установлен слишком низким. См. http://support.microsoft.com/default.aspx?scid=kb;en-us;821268

Относительно вашего приложения, заканчивающегося из-за задержки TIME_WAIT в сокете, я также столкнулся с той же проблемой, потому что я вводил трафик из ограниченного набора компьютеров с запросами более 64 тыс. за 240 секунд. Я без проблем спустил TIME_WAIT до 30 секунд.

Я также ошибочно повторно использовал прокси-объект для конечной точки веб-служб в нескольких потоках. Хотя прокси-сервер не имеет какого-либо состояния, я обнаружил, что у GC было много проблем с сборкой памяти, связанной с ее внутренними буферами (экземпляры String []), и это вызвало нехватку памяти в моем приложении.

Некоторыми интересными счетчиками производительности, которые вы должны отслеживать, являются те, которые связаны с запросами на очереди, запросами в исполнении и запросом времени в категории приложений ASP.NET. Если вы видите запросы с очередью или что время выполнения меньше, но клиенты видят длинные запросы, то на вашем сервере есть своего рода разногласия. Также отслеживайте счетчики в категории LocksAndThreads, которые ищут конкуренцию.

Ответ 2

Поскольку асинхронные запросы удерживают дольше tcp-сокеты, возможно, вам нужно посмотреть свойство maxconnection в управлении подключением в вашем web.config? Пожалуйста, обратитесь к этой ссылке: http://support.microsoft.com/default.aspx?scid=kb;en-us;821268

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

Изменить: Кроме того, много TIME_WAIT указывают на утечку соединения в коде, основанном на прошлом опыте. Возможные причины: 1) Не использовать используемые соединения. 2) Неправильная реализация пула соединений.