У нас есть 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.Можем ли мы ограничить количество потоков, доступных для процесса?