0 2.9K ru

Лучшие практики для увеличения производительности ASP.NET Core приложений

Categories: 💻 Programming

Всегда используйте последнюю версию ASP.NET Core и nuget пакетов

Команда майкрософт активно обновляет ASP.NET и выпускает довольно таки значимые обновления, рекомендуем держать руку на пульсе и старатся обновлять библиотеки и саму версию ASP.NET, как только выходит новая.

Примечание в статье часто используется понятие 'Hot code path'. Под этим имеется ввиду место в вашем коде которое очень долго выполняется.

Используйте кэширование

Мы можем повысить производительность приложения, если сможем каждый раз уменьшать количество запросов к серверу. Это не означает, что вы не будете делать запросы на сервер вовсе, это только означает, что вы не будете делать запросы на сервер КАЖДЫЙ РАЗ. В первый раз вы сделаете запрос на сервер и получите ответ, и этот ответ будет храниться где-то на будущее в течение некоторого времени (конечное количество времени, например 90 минут), и в следующий раз, когда вы сделаете реквест на тот же эндпоинт,  сначала вы проверите, если у вас уже есть данные которые где-то сохранены, и если да, то вы будете использовать сохраненные данные вместо того, чтобы делать вызов серверу.

Кэширование бывает нескольких типов:

Кэширование ответов на основе HTTP

Устанавливается заголовком Cache-Control

Распределенный кэш

Используйте распределенный кэш для хранения данных к которым нужен быстрый доступ. Кэширование с помощью сторонних библиотек Например Redis Cache

Кэширование в памяти

Кэширование в памяти использует память сервера для хранения кэшированных данных. Этот тип кэширования подходит для одного или нескольких серверов. 

Cache Tag Helper

Используйте cache tag helper в MVC приложениях, например:

<cache expires-after="@TimeSpan.FromSeconds(120)">
    Current Time Inside Cache Tag Helper: @DateTime.Now
</cache>

Distributed Cache Tag Helper

Можно использовать Cache Tag Helper более широко и сохранять этот кэш например в Redis или SQL Server. В ASP.NET Core есть две встроенные реализации интерфейса IDistributedCache. Одна из них основана на SQL Server services.AddDistributedSqlServerCache, а другая — на Redis services.AddStackExchangeRedisCache.

Redis Cache

Когда мы работаем с редис кэшом. Мы это делаем скорее всего через самый популярный пакет StackExchange 

Для инициализации коннекшина мы вызываем такой код:

ConnectionMultiplexer.Connect(Connection)

Это может стать большой проблемой, если коннект был установлен быстро, вы не почуствуете разницу, но если ним были заминки и он может выполняться например около 30 секунд, блокируя наш поток. Для того чтобы так не происходило, необходимо использовать асинхронный код для этого:

ConnectionMultiplexer.ConnectAsync(Connection)

ResponseCache Атрибут

 Позволяет настроить для метода или класса параметры, необходимые для задания соответствующих заголовков в кэширование ответов (кэшировать или не, если да то на сколько кэшировать и др параметры)

Профили кэша

Вместо дублирования ResponseCache Атрибута, можно использовать кэш профиль, например:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc(options =>
    {
        options.CacheProfiles.Add("Default30",
            new CacheProfile()
            {
                Duration = 30
            });
    }).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}

Всегда используйте асинхронное программирование и избегайте блокирующих запросов

При разработке приложений ASP.NET Core старайтесь избегать создания блокирующих запросов. Блокирующий запрос означает запрос, который блокирует следующее выполнение, пока оно не будет завершено. Блокирующий запросили синхронный запросможет быть любым: либо вы извлекаете данные из API, либо выполняете некоторые внутренние операции. Вы должны всегда выполнять запрос в асинхронном режиме.

Создавая ASP.NET Core приложение, вы должны его проектировать таким образом чтобы оно могло обрабатывать множество запросов одновременно. Асинхронные интерфейсы API позволяют использовать небольшой пул потоков для обработки тысяч одновременных запросов, не ожидая (как они это делают в синхронном программировании). Вместо того чтобы ожидать завершения выполнения синхронной задачи выполняющейся длительное время, поток может работать на другой запрос.

Рекомендации:

Х Не используйте:

  • Вызовы методов которые могут бликировать асинхронные вызовы путем выхова Task.Wait или Task.Result.
  • Избегайте блокировок на всех стадиях разработки приложения. ASP.NET Core спроектирован так чтобы выполнять все в асинхронном режиме.

 Используйте 

  • Сделайте 'hot code path' места асинхронными.
  • Делайте запросы к Data access layer'y и долгим API операциям асинхронно.
  • Сделайте методы Контроллера асинхронными. Весь стек вызовов является асинхронным, это позволит вам использовать преимущества async/await шаблонов.

Минимизируйте аллокации больших объектов

Сборщик мусора .NET Core автоматически управляет распределением и освобождением памяти в приложениях ASP.NET Core. Автоматический сбор мусора обычно означает, что разработчикам не нужно беспокоиться о том, как или когда освобождается память. Однако очистка объектов, на которые нет ссылок, занимает процессорное время, поэтому разработчикам следует минимизировать распределение объектов в 'hot code path'. Сборка мусора особенно дорога для больших объектов (> 85 Кбайт). Большие объекты хранятся в Large object heap объектов, и для их очистки требуется полная сборка мусора (поколение 2). В отличие от коллекций поколения 0 и поколения 1, коллекция поколения 2 требует временной приостановки выполнения приложения. Частое распределение и удаление больших объектов может привести к нестабильной производительности.

Рекомендации:

Х Не Создавайте много больших короткоживущих объектов в 'hot code path.

 Используйте кэширование больших объектов, которые часто используются. Кэширование больших объектов предотвращает дорогие аллокации. Так же можно использовать один из классических паттернов Flyweight для работы с такими объектами.

 Используйте ArrayPool<T> для хранения больших массивов.

Использование класса ArrayPool<T> для "аренды" и "возврата" буферов (с использованием методов Rent и Return) может повысить производительность в ситуациях, когда массивы часто создаются и уничтожаются, что приводит к значительному давлению памяти на сборщик мусора.

Оптмизация Data Access Layer'a

Эффективное чтение и запись данных очень важны для хорошей производительности.

Рекомендации:

 Используйте асинхронные запросы к Data access layer'y.

 Используйте кэширование часто используемых данных, извлеченных из базы данных или удаленного сервиса (если это позволяет бизнес-логика). В зависимости от сценария используйте MemoryCache или DistributedCache. Для дополнительной информации см. раздел "Используйте кэширование".

 Минимизируйте количество запросов для получения данных (цель получить данные за один запрос, не делая несколько запросов)

 Используйте фильтрацию и агрегирование запросов LINQ (например, с помощью операторов .Where, .Select или .Sum), чтобы фильтрация выполнялась базой данных.

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

Х Избегайте больше N + 1 проблемы

Пул HTTP-подключений с HttpClientFactory

Несмотря на то что HttpClient реализует интерфейс IDisposable, он предназначен для повторного использования. Закрытые экземпляры HttpClient оставляют сокеты открытыми в состоянии TIME_WAIT на короткое время. Если часто используется класс, который создает и удаляет объекты HttpClient, приложение может исчерпать доступные сокеты. HttpClientFactory был введен в ASP.NET Core 2.1 как решение этой проблемы. Он обрабатывает пул HTTP-соединений для оптимизации производительности и надежности.

Стремитесь к быстродействию в коде который вы используете в middleware 

Запускайте код который выполняется длительное время за пределами HTTP реквестов

Большинство запросов к приложению ASP.NET Core могут обрабатываться контроллером или моделью страницы, вызывая необходимые сервисы и возвращая HTTP-ответ. Для некоторых запросов, которые включают в себя длительные задачи, лучше сделать весь процесс запрос-ответ асинхронным.

Рекомендации:

Х Избегайте ожидания завершения длительных задач как части обычной обработки HTTP-запроса.
 Используйте обработку долго-выполняющихся запросов с помощью фоновых служб (например Azure function, AWS Lambda, и тд). Завершение работы вне процесса особенно полезно для задач с интенсивным использованием процессора.
 Используйте тулы такие как SignalR, для асинхронной связи с клиентами в режиме реального времени.

Минифицируйте client assets и используйте сжатие ответов с сервера

Приложения ASP.NET Core интерфейсами часто обслуживают множество файлов JavaScript, CSS или изображений. Производительность при "initial load" может быть улучшена за счет:

  • Bundling и минификация (лучше всего на данный момент использовать WebPack для этих целей).
  • Используйте CDN
  • Ужимайте картинки
  • Используйте Gzip для ответов с сервера

Код который блокирует потоки

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

Установим его с Nuget'a

Install-Package Ben.BlockingDetector -Version 0.0.3

И вызываем метод в нашем IApplicationBuilder

 public void Configure(IApplicationBuilder app, IHostingEnvironment env)
      {
         // Other Configurations
         app.UseBlockingDetection();
         // Other Configurations
      }

 

Comments:

Please log in to be able add comments.