У нас есть API, который реализован с использованием ServiceStack, который размещен в IIS. Выполняя нагрузочное тестирование API, мы обнаружили, что время отклика хорошее, но оно быстро ухудшается, как только мы получаем около 3500 одновременных пользователей на сервер. У нас есть два сервера, и при их использовании 7000 пользователей среднее время отклика для всех конечных точек составляет менее 500 мс. Ящики находятся за балансировщиком нагрузки, поэтому мы получаем 3500 параллелей на сервер. Однако, как только мы увеличиваем количество одновременных пользователей, мы видим значительное увеличение времени отклика. Увеличение числа одновременных пользователей до 5000 на сервер дает нам среднее время ответа на конечную точку около 7 секунд.
Объем памяти и ЦП на серверах довольно низок, как при хороших временах отклика, так и после их ухудшения. На пике с 10 000 одновременных пользователей средняя загрузка ЦП чуть ниже 50%, а объем оперативной памяти составляет около 3-4 ГБ из 16. Это заставляет нас думать, что мы где-то достигли какого-то предела. На приведенном ниже снимке экрана показаны некоторые ключевые счетчики в perfmon во время нагрузочного теста с общим числом пользователей в 10 000 одновременно. Подсвеченный счетчик запросов / сек. Справа от скриншота видно, что график запросов в секунду становится действительно ошибочным. Это основной индикатор для медленного времени отклика. Как только мы видим этот паттерн, мы замечаем медленное время отклика в нагрузочном тесте.
Как нам решить проблему с производительностью? Мы пытаемся определить, является ли это проблемой кодирования или проблемой конфигурации. Есть ли какие-либо настройки в web.config или IIS, которые могли бы объяснить это поведение? Пул приложений работает под управлением .NET v4.0, а версия IIS - 7.5. Единственное изменение, которое мы внесли в настройки по умолчанию, - это обновить значение длины очереди пула приложений с 1000 до 5000. Мы также добавили следующие параметры конфигурации в файл Aspnet.config:
<system.web>
<applicationPool
maxConcurrentRequestsPerCPU="5000"
maxConcurrentThreadsPerCPU="0"
requestQueueLimit="5000" />
</system.web>
Больше деталей:
Целью API является объединение данных из различных внешних источников и возвращение в виде JSON. В настоящее время он использует реализацию кэша InMemory для кэширования отдельных внешних вызовов на уровне данных. Первый запрос к ресурсу извлечет все необходимые данные, а любые последующие запросы к тому же ресурсу получат результаты из кэша. У нас есть «обработчик кэша», который реализован как фоновый процесс, который обновляет информацию в кэше через определенные заданные интервалы. Мы добавили блокировку вокруг кода, который извлекает данные из внешних ресурсов. Мы также реализовали сервисы для извлечения данных из внешних источников асинхронным образом, чтобы конечная точка работала так же медленно, как и самый медленный внешний вызов (если, конечно, у нас нет данных в кеше). Это делается с помощью класса System.Threading.Tasks.Task.Можем ли мы ограничить количество потоков, доступных для процесса?
источник
Ответы:
Вслед за @DavidSchwartz и @Matt это похоже на потоки, блокирует проблему управления.
Я предлагаю:
Заморозьте внешние вызовы и сгенерированный для них кеш и запустите нагрузочный тест со статической внешней информацией, чтобы просто устранить любую проблему, не связанную со стороной серверной среды.
Используйте пулы потоков, если не используете их.
О внешних вызовах вы сказали: «Мы также внедрили службы для извлечения данных из внешних источников асинхронным способом, чтобы конечная точка работала так же медленно, как и самый медленный внешний вызов (если, конечно, у нас нет данных в кэше). "
Вопросы: - Вы проверили, заблокированы ли какие-либо данные кэша во время внешнего вызова или только при записи результата внешнего вызова в кэш? (слишком очевидно, но должен сказать). - Вы блокируете весь кеш или его мелкие части? (слишком очевидно, но должен сказать). - Даже если они асинхронные, как часто выполняются внешние вызовы? Даже если они запускаются не так часто, они могут быть заблокированы из-за чрезмерного количества запросов к кешу от пользовательских вызовов, пока кеш заблокирован. Этот сценарий обычно показывает фиксированный процент использования ЦП, поскольку многие потоки ожидают через фиксированные интервалы, и «блокировка» также должна управляться. - Проверяли ли вы, что внешние задачи означают, что время отклика также увеличивается при появлении медленного сценария?
Если проблема не устранена, я бы рекомендовал избегать использования класса Task и выполнять внешние вызовы через тот же пул потоков, который управляет пользовательскими запросами. Это чтобы избежать предыдущего сценария.
источник