Запросы находятся в очереди в Azure AppService, хотя в пуле потоков достаточно потоков
Я написал API с использованием asp.net webapi и развернул его в Azure как Appservice. Имя моего контроллера - TestController, а метод действия - что-то вроде ниже.
[Route("Test/Get")]
public string Get()
{
Thread.Sleep(10000);
return "value";
}
Таким образом, для каждого запроса он должен ждать 10 секунд, прежде чем вернуть строку "значение". Я также написал другую конечную точку, чтобы увидеть количество потоков в пуле потоков, работающих для выполнения запросов. Это действие что-то вроде ниже.
[Route("Test/ThreadInfo")]
public ThreadPoolInfo Get()
{
int availableWorker, availableIO;
int maxWorker, maxIO;
ThreadPool.GetAvailableThreads(out availableWorker, out availableIO);
ThreadPool.GetMaxThreads(out maxWorker, out maxIO);
return new ThreadPoolInfo
{
AvailableWorkerThreads = availableWorker,
MaxWorkerThreads = maxWorker,
OccupiedThreads = maxWorker - availableWorker
};
}
Теперь, когда мы выполняем 29 вызовов одновременно для конечной точки Test/Get, для успешного выполнения всех запросов требуется почти 11 секунд. Таким образом, сервер выполняет все запросы одновременно в 11 потоках. Чтобы увидеть состояние потоков, вызов Test/ThreadInfo сразу после вызова Test/Get возвращается немедленно (без ожидания) { "AvailableWorkerThreads": 8161, "MaxWorkerThreads": 8191, "OccupiedThreads": 30 }
Кажется, 29 потоков выполняют запросы Test/Get, а 1 поток выполняет запрос Test/ThreadInfo.
Когда я делаю 60 звонков в Test/Get, для достижения успеха требуется почти 36 секунд. Выполнение вызова Test/ThreadInfo(занимает некоторое время) возвращает {"AvailableWorkerThreads": 8161, "MaxWorkerThreads": 8191, "OccupiedThreads": 30}
Если мы увеличиваем количество запросов, значение OccupiedThreads увеличивается. Как и для 1000 запросов, это занимает 2 минуты 22 секунды, а значение OccupiedThreads равно 129.
Кажется, запрос и получение в очередь после 30 одновременных вызовов, хотя в WorkerThread доступно множество потоков. Постепенно это увеличивает поток для одновременного выполнения, но этого недостаточно (129 для 1000 запросов).
Поскольку наши сервисы имеют много вызовов ввода-вывода (некоторые из них являются внешними вызовами API, а некоторые - запросами к базе данных), задержка также высока. Поскольку мы используем все вызовы ввода-вывода асинхронным способом, сервер может одновременно обслуживать большую часть запросов, но нам нужно больше параллелизма, когда процессор выполняет реальную работу. Мы используем сервисный план S2 с одним экземпляром. Увеличение экземпляра увеличит параллелизм, но нам нужно больше параллелизма из одного экземпляра.
После прочтения некоторого блога и документации по IIS мы увидели настройку minFreeThreads. Если число доступных потоков в пуле потоков падает ниже, значение для этого параметра IIS начинает помещать запрос в очередь. Есть ли в appservice что-нибудь подобное? И действительно ли возможно получить больше параллелизма от службы приложений Azure, или нам там не хватает конфигурации?
2 ответа
Наконец-то получил ответ на мой вопрос. Дело в том, что пул потоков ASP.NET поддерживает пул потоков, которые уже понесли затраты на инициализацию потоков и их легко использовать повторно. Пул потоков.NET также самонастраивается. Он контролирует использование ресурсов ЦП и других ресурсов, а также добавляет новые потоки или обрезает размер пула потоков по мере необходимости. Когда запросов много, а в пуле доступно недостаточно потоков, пул потоков начинает добавлять новые потоки в пул, а перед этим запускает собственный алгоритм для просмотра состояния памяти и использования ЦП системой, которая занимает много времени. время, и именно поэтому мы видим медленное увеличение рабочего потока в пуле и получаем большое количество запросов в очереди. Но, к счастью, есть возможность установить количество рабочих потоков, прежде чем пул потоков переключится на алгоритм добавления нового потока. Код что-то вроде ниже.
public string Settings()
{
int minWorker, minIOC;
ThreadPool.GetMinThreads(out minWorker, out minIOC);
if (ThreadPool.SetMinThreads(300, minIOC)){
return "The minimum number of threads was set successfully.";
}
else
{
return "The minimum number of threads was not changed.";
}
}
Здесь ThreadPool.SetMinThreads(300, minIOC) задает значение минимального потока потоков, который будет создан перед переключением на алгоритм добавления или удаления потока. Я добавил этот метод в качестве действия моего контроллера we bapi, а затем после выполнения этого действия сделал запрос, когда я сделал 300 параллельных запросов к конечной точке Test/Get, все было запущено и завершено за 11 секунд, и ни один запрос не был поставлен в очередь.
Насколько я понимаю, вы должны проверить MinWorkerThreads
с помощью ThreadPool.GetMinThreads
который мог бы получить количество незанятых потоков, поддерживаемых ThreadPool в ожидании новых запросов. Я бы порекомендовал вам вернуть информацию о текущем пуле потоков после выполнения вашего действия (Tetst/Get
) вместо того, чтобы сделать новый запрос Test/ThreadInfo
, Основываясь на вашем коде, я протестировал его в своем веб-приложении с ценовым уровнем B1 следующим образом:
1000 одновременных запросов, 15 с для каждого действия, в общем 3 минуты 20 секунд.
Пул потоков создает и уничтожает рабочие потоки, чтобы оптимизировать пропускную способность. Если текущие потоки могут обрабатывать запросы, больше потоков не будет создано. Как только эти потоки завершат выполнение своих действий, они затем возвращаются в пул потоков.
Затем я использовал асинхронное программирование и изменил действие следующим образом:
public async Task<ActionResult> DoJob(int id)
{
await Task.Delay(TimeSpan.FromSeconds(15));
return ThreadInfo();
}
Результат:
Почти 3000 одновременных запросов:
В общем, AvailableWorkerThreads просто означает количество дополнительных рабочих потоков, которые могут быть запущены вместо рабочих потоков, созданных для вас. Я бы порекомендовал вам использовать асинхронное программирование и развернуть реальную работу в веб-приложении Azure, а затем проверить реальную производительность и найти подходящие подходы, если есть какие-либо узкие места.