Как устранить неполадки, связанные с истощением потоков в ASP.NET Core в Linux (Kubernetes)? - PullRequest
0 голосов
/ 01 мая 2018

Я использую ASP.NET Core API в Linux, в Kubernetes в Google Cloud.

Это API с высокой нагрузкой, и при каждом запросе он выполняет библиотеку, выполняя долгую (1-5 секунд), интенсивную загрузку процессора.

Я вижу, что после развертывания API некоторое время работает правильно, но через 10-20 минут он перестает отвечать на запросы, и даже конечная точка проверки работоспособности (которая просто возвращает фиксированный код 200 OK) перестает работать и время ожидания истекает. (Это заставляет Кубернетов убивать стручки.)

Иногда я также вижу печально известное сообщение об ошибке Heartbeat took longer than "00:00:01" в журналах.

Погугление этих явлений указывает на «истощение потоков», так что слишком много запущенных потоков пула потоков или слишком много потоков блокируют ожидание чего-то, так что в пуле не осталось потоков, которые могли бы получить ASP Запросы .NET Core (отсюда время ожидания даже конечной точки проверки работоспособности).

Каков наилучший способ устранения этой проблемы? Я начал следить за числами, возвращаемыми ThreadPool.GetMaxThreads и ThreadPool.GetAvailableThreads, но они оставались постоянными (порт завершения всегда 1000 как для максимума, так и для доступности, а рабочий всегда 32767).
Есть ли какое-либо другое свойство, которое я должен отслеживать?

Ответы [ 2 ]

0 голосов
/ 02 мая 2018

Вы уверены, что в вашем веб-приложении ASP.NET Core нет потоков? Возможно, это просто насыщает все доступные ресурсы модуля, заставляя Kubernetes просто уничтожить сам модуль и ваше веб-приложение.

У меня был очень похожий сценарий с веб-API ASP.NET Core, работающим в Linux RedHat в среде OpenShift , которая также поддерживает концепцию pod, как в Kubernetes: для одного вызова требовалось приблизительно 1 секунда для завершена, и при большой рабочей нагрузке она сначала стала медленнее, а затем перестала отвечать, в результате чего OpenShift уничтожил модуль и, таким образом, мое веб-приложение.

Возможно, ваше веб-приложение ASP.NET Core не исчерпывает потоки, особенно учитывая большое количество рабочих потоков, доступных в ThreadPool. Вместо этого число активных потоков в сочетании с их потребностью в ЦП, вероятно, слишком велико по сравнению с фактическими милликорами, доступными в модуле, в котором они работают: действительно, после создания этих активных потоков слишком много для доступного ЦП, что большинство из них в конечном итоге планировщик будет поставлен в очередь и ожидает выполнения, в то время как на самом деле будет выполняться только группа. Затем планировщик выполняет свою работу, обеспечивая равномерное распределение ресурсов ЦП между потоками, часто переключая те, которые будут его использовать. Что касается вашего случая, когда потокам требуются тяжелые и длинные операции, связанные с процессором, со временем ресурсы насыщаются, а веб-приложение перестает отвечать на запросы.

Шагом смягчения может быть предоставление большей емкости для ваших стручков, особенно милликор, или увеличение количества стручков, которые Kubernetes может развернуть в зависимости от необходимости. Однако в моем конкретном сценарии этот подход мало помог. Вместо этого, улучшение самого API за счет сокращения выполнения одного запроса с 1 с до 300 мс заметно улучшило общую производительность веб-приложения и фактически решило проблему.

Например, если ваша библиотека выполняет одни и те же вычисления более чем в одном запросе, вы можете рассмотреть вопрос о введении кэширования в ваших структурах данных, чтобы повысить скорость при небольшой стоимости памяти (что сработало для меня), особенно если ваши операции в основном связаны с процессором, и если у вас есть такие требования к вашему веб-приложению. Вы также можете включить ответ кэша в ASP.NET Core , если это имеет смысл с рабочей нагрузкой и ответами вашего API. Используя кеш, вы убедитесь, что ваше веб-приложение не выполняет одну и ту же задачу дважды, освобождая ЦП и снижая риск потоков в очередях.

Более быстрая обработка каждого запроса сделает ваше веб-приложение менее подверженным риску заполнения доступного ЦП и, следовательно, уменьшит риск того, что слишком много потоков будет поставлено в очередь и ожидает выполнения.

0 голосов
/ 01 мая 2018

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

Вы должны разгрузить все, что вы делаете, с другим процессом, а затем отслеживать ход выполнения. Для API типичный подход здесь состоит в том, чтобы запланировать работу над другим процессом, а затем немедленно вернуть 202 Принято , с конечной точкой в ​​теле ответа, которую клиент может использовать для мониторинга прогресса / получения возможного завершенный результат. Вы также можете реализовать веб-крючок, который клиент может зарегистрировать для получения уведомления о завершении процесса, без необходимости постоянно проверять его.

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

Также вполне возможно, что в вашем коде есть некоторая неэффективность или проблема, которую можно исправить, чтобы уменьшить время, затрачиваемое процессом, и / или расходуемые ресурсы. В качестве тривиального примера, скажем, если вы используете что-то вроде Task.Run, вы могли бы потенциально освободить тонну потоков, если бы не сделал бы это. Task.Run почти никогда не следует использовать в контексте веб-приложения. Тем не менее, вы не опубликовали никакого кода, поэтому невозможно дать вам точное руководство.

...