Запросы находятся в очереди в 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, а затем проверить реальную производительность и найти подходящие подходы, если есть какие-либо узкие места.

Другие вопросы по тегам