2014-02-14 4 views
18

Недавно мы разработали сайт, основанный на SOA, но на этом сайте возникли проблемы с загрузкой и производительностью при загрузке. Я отвечал на вопрос, связанный этот вопрос здесь:async и ждут: они плохие?

ASP.NET website becomes unresponsive under load

Сайт сделан из API (WEB API) сайта, который размещен на кластере 4-узла и веб-сайт, который размещен на другой 4- узлового кластера и вызывает вызовы API. Оба они разработаны с использованием ASP.NET MVC 5, и все действия/методы основаны на методе async-wait.

После запуска сайта под некоторыми инструментами мониторинга, такими как NewRelic, при исследовании нескольких файлов дампа и профилирования рабочего процесса выяснилось, что при очень небольшой нагрузке (например, 16 одновременных пользователей) у нас было около 900 потоков, которые использовались 100% от процессора и заполнили очередь потоков IIS!

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

Я лично не доволен подходом, потому что мое чувство кишки заключается в том, что мы не использовали методы асинхронного программирования, иначе это означает, что Microsoft внедрила функцию, которая в основном довольно разрушительна и непригодна для использования!

Знаете ли вы какую-либо ссылку, которая очищает ее, где и как следует использовать/использовать методы async? Как мы должны использовать их, чтобы избежать таких драм? например Основываясь на том, что я читал в MSDN, я считаю, что уровень API должен быть асинхронным, но веб-сайт может быть обычным сайтом без ASP.NET MVC.

Update:

Вот метод асинхронной, что делает все коммуникации с API.

public static async Task<T> GetApiResponse<T>(object parameters, string action, CancellationToken ctk) 
{ 
     using (var httpClient = new HttpClient()) 
     { 
      httpClient.BaseAddress = new Uri(BaseApiAddress); 

      var formatter = new JsonMediaTypeFormatter(); 

      return 
       await 
        httpClient.PostAsJsonAsync(action, parameters, ctk) 
         .ContinueWith(x => x.Result.Content.ReadAsAsync<T>(new[] { formatter }).Result, ctk); 
     } 
    } 

Есть ли что-нибудь глупое с помощью этого метода? Обратите внимание, что когда мы преобразовали весь метод в неасинхронные методы, мы получили более высокую производительность.

Вот пример использования (я вырезал другие биты кода, связанные с проверкой, протоколированием и т. Д. Этот код является телом метода действия MVC).

В нашей обертке службы:

public async static Task<IList<DownloadType>> GetSupportedContentTypes() 
{ 
    string userAgent = Request.UserAgent; 
    var parameters = new { Util.AppKey, Util.StoreId, QueryParameters = new { UserAgent = userAgent } }; 
    var taskResponse = await Util.GetApiResponse<ApiResponse<SearchResponse<ProductItem>>>(
        parameters, 
        "api/Content/ContentTypeSummary", 
        default(CancellationToken)); 
        return task.Data.Groups.Select(x => x.DownloadType()).ToList(); 
} 

И в действии:

public async Task<ActionResult> DownloadTypes() 
    { 
     IList<DownloadType> supportedTypes = await ContentService.GetSupportedContentTypes(); 
+2

Любые примеры того, как приложение использует async/wait? При использовании с IO-связанными операциями, такими как вызовы базы данных и файловые операции, это обычно должно увеличивать масштабируемость, а не уменьшать ее. Если он порождает ненужные потоки, так что каждый метод может быть помечен как async, это, вероятно, красный флаг и, вероятно, причина ваших проблем. –

+1

Возможно, ваш код обертывает синхронные API-интерфейсы с помощью 'Task.Run', вместо использования естественно асинхронных API? http://stackoverflow.com/q/21690385/1768303. Как правило, использование 'Task.Run' на сервере - плохая идея. – Noseratio

+0

Это MVC 5? Или MVC 3, как говорит ваш тег? –

ответ

34

Есть ли что-нибудь глупое с помощью этого метода? Обратите внимание, что когда мы преобразовали весь метод в неасинхронные методы, мы получили более высокую производительность.

я могу видеть, по крайней мере, две вещи происходит не так здесь:

public static async Task<T> GetApiResponse<T>(object parameters, string action, CancellationToken ctk) 
{ 
     using (var httpClient = new HttpClient()) 
     { 
      httpClient.BaseAddress = new Uri(BaseApiAddress); 

      var formatter = new JsonMediaTypeFormatter(); 

      return 
       await 
        httpClient.PostAsJsonAsync(action, parameters, ctk) 
         .ContinueWith(x => x.Result.Content 
          .ReadAsAsync<T>(new[] { formatter }).Result, ctk); 
     } 
    } 

Во-первых, лямбда вы передаете к ContinueWith преграждает:

x => x.Result.Content.ReadAsAsync<T>(new[] { formatter }).Result 

Это эквивалентно:

x => { 
    var task = x.Result.Content.ReadAsAsync<T>(new[] { formatter }); 
    task.Wait(); 
    return task.Result; 
}; 

Таким образом, вы блокируете поток пула, на котором выполняется lambda. Это эффективно уничтожает преимущество асинхронного API ReadAsAsync и снижает масштабируемость вашего веб-приложения. Остерегайтесь других мест, подобных этому в вашем коде.

Во-вторых, запрос ASP.NET обрабатывается потоком сервера со специальным контекстом синхронизации, установленным на нем, AspNetSynchronizationContext. Когда вы используете await для продолжения, обратный вызов продолжения будет отправлен в один и тот же контекст синхронизации, код, созданный компилятором, позаботится об этом. OTOH, когда вы используете ContinueWith, это происходит не автоматически.

Таким образом, вы должны явно обеспечить правильный планировщик задач, удалить блокирующий .Result (это будет возвращать задачу) и Unwrap вложенную задачу:

return 
    await 
     httpClient.PostAsJsonAsync(action, parameters, ctk).ContinueWith(
      x => x.Result.Content.ReadAsAsync<T>(new[] { formatter }), 
      ctk, 
      TaskContinuationOptions.None, 
      TaskScheduler.FromCurrentSynchronizationContext()).Unwrap(); 

, что сказал, вы действительно не нужны такие добавлена ​​сложность ContinueWith здесь:

var x = await httpClient.PostAsJsonAsync(action, parameters, ctk); 
return await x.Content.ReadAsAsync<T>(new[] { formatter }); 

следующая статья Стивена Toub весьма актуальна:

"Async Performance: Understanding the Costs of Async and Await".

Если я должен вызвать метод асинхронной в синхронизации контекста, где использование Await не представляется возможным, что это лучший способ сделать это?

Вы почти никогда не должны нужно смешать await и ContinueWith, вы должны придерживаться await. В принципе, если вы используете async, он должен быть асинхронным "all the way".

Для серверной стороны ASP.NET MVC/веб-среды выполнения API, это просто означает, что метод контроллера должен быть async и возвращать Task или Task<>, проверьте this. ASP.NET отслеживает ожидающие задачи для заданного HTTP-запроса. Запрос не будет завершен, пока все задачи не будут завершены.

Если вы действительно нужно вызвать async метод из синхронного метода в ASP.NET, вы можете использовать AsyncManager как this зарегистрировать отложенную задачу. Для классического ASP.NET вы можете использовать PageAsyncTask.

В худшем случае вы должны позвонить task.Wait() и заблокировать, так как иначе ваша задача может продолжаться за пределами этого конкретного HTTP-запроса.

Для приложений пользовательского интерфейса на стороне клиента возможны различные сценарии для вызова метода async из синхронного метода. Например, вы можете использовать ContinueWith(action, TaskScheduler.FromCurrentSynchronizationContext()) и запустить событие завершения с action (например, this).

+1

Хороший улов. Я с подозрением относился к этому ContinueWith, но я не мог сказать, в чем проблема. Мне показалось, что что-то мешало возврату нити в поток. Мне нравится ваше решение избавиться от ContinueWith вообще. Гораздо проще. –

+0

@ErikTheViking, возможно, есть несколько фрагментов, подобных этому. Или что-то вроде того, чтобы называть это жесткой петлей. Я не уверен, что один блокирующий вызов может вызвать проблему для 16 пользователей. – Noseratio

+1

@Noseratio вы гений! Я сделал это изменение, и результаты нагрузочного теста значительно улучшились! Один вопрос: если мне нужно вызвать метод async в контексте синхронизации, где использовать ожидание невозможно, что это лучший способ сделать это? – Aref

3

асинхронной и ждать не следует создавать большое количество потоков, в частности, не только с 16 пользователями. Фактически, это должно помочь вам лучше использовать потоки. Цель async и ожидание в MVC - фактически отказаться от потока пула потоков, когда он занят обработкой связанных с IO задач. Это говорит мне о том, что вы делаете что-то глупое, например, нерестилища, а затем ожидаете бесконечно.

Тем не менее, 900 потоков на самом деле не очень много, и если они используют 100% -ный процессор, то они не ждут .. они что-то пережевывают. Это то, что вы должны изучать. Вы сказали, что используете такие инструменты, как NewRelic, и что они указывают на источник использования этого процессора? Какие методы?

Если бы я был вами, я бы сначала доказал, что просто использование async и ожидание не являются причиной ваших проблем. Просто создайте простой сайт, который имитирует поведение и затем запускает те же тесты на нем.

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

+0

Как я могу найти, что это за глупая вещь? Мы профилировали почти весь сайт и API и фиксировали каждую шейку бутылки производительности. – Aref

+0

@Aref: вы должны искать любое использование 'Task.Run',' Task.Factory.StartNew' или 'Task.Start'; они не должны использоваться в ASP.NET. Кроме того, любое использование 'Parallel' или Parallel LINQ. –

+0

@Aref - Я дал вам некоторые методы, которые вы можете использовать, например, сделать копию и зачистить материал, пока проблема не исчезнет. –

3

Существует много материала для обсуждения.

Прежде всего, async/await может помочь вам, когда ваше приложение практически не имеет бизнес-логики. Я имею в виду, что точка async/await состоит в том, чтобы не иметь много потоков в спящем режиме, ожидая чего-то, в основном, некоторых IO, например. запросы базы данных (и выборки). Если ваше приложение использует огромную бизнес-логику с использованием процессора на 100%, async/await не поможет вам.

Проблема 900 потоков заключается в том, что они неэффективны - если они работают одновременно. Дело в том, что лучше иметь такое количество «деловых» потоков, поскольку у вас на сервере есть ядра/процессоры. Причина - переключение контекста потока, блокировка и т. Д. Существует много таких систем, как шаблон LMAX distruptor или Redis, которые обрабатывают данные в одном потоке (или одном потоке на ядро). Это просто лучше, так как вам не нужно заниматься блокировкой.

Как достичь описанного подхода? Посмотрите на disruptor, очередь входящих запросов и обработайте их один за другим, а не параллельно.

Противоположный подход, когда практически нет бизнес-логики, и многие потоки просто ждут ввода-вывода, это хорошее место, где нужно поставить async/await в работу.

Как это работает в основном: есть поток, который считывает байты из сети - в основном только один. После поступления некоторого запроса этот поток считывает данные. Существует также ограниченный пул потоков работников, который обрабатывает запросы. Точка async заключается в том, что как только один поток обработки ожидает чего-то, в основном io, db, поток возвращается в опросе и может использоваться для другого запроса. Когда IO-ответ готов, для завершения обработки используется поток из пула. Это способ, которым вы можете использовать несколько потоков для запроса на тысячу серверов за секунду.

Я бы предположил, что вы должны нарисовать картинку, как работает ваш сайт, что делает каждый поток и как одновременно работает. Обратите внимание, что необходимо решить, важна ли пропускная способность или латентность для вас.

Смежные вопросы