Содержание
В ASP.NET Core предусмотрено кэширование ответов приложения для чего используется сервис IOutputCacheStore
и компонент middleware OutputCacheMiddleware
. Рассмотрим, как мы можем использовать кэширование вывода в своих приложениях.
Пример приложения без кэширования вывода
Рассмотрим следующее приложение ASP.NET Core
public class Program { public static void Main(string[] args) { var builder = WebApplication.CreateBuilder(args); var app = builder.Build(); app.MapGet("/", async () => { await Task.Delay(5000); return Results.Ok(new { name = "John", age = 25 }); }); app.Run(); } }
здесь, для имитации тяжелой работы мы используем задержку в 5 секунд. На месте этой задержки может быть, например, запрос данных с другого сервера, чтение больших объемов данных из БД и и.д. При каждом запросе мы будем ждать 5 секунд, прежде, чем получим ответ сервера. Если получаемые данные редко изменяются, то в этом случае целесообразно использовать кэширование вывода.
Пример приложения с кэшированием вывода
Изменим наше приложение следующим образом:
public class Program { public static void Main(string[] args) { var builder = WebApplication.CreateBuilder(args); builder.Services.AddOutputCache();//добавляем сервис кэширования вывода var app = builder.Build(); app.UseOutputCache();//добавляем middleware кэширования вывода app.MapGet("/", async () => { await Task.Delay(5000); return Results.Ok(new { name = "John", age = 25 }); }).CacheOutput(); app.Run(); } }
Здесь мы включили сервис кэширования вывода, вызвав метод AddOutputCache()
и включили в конвейер обработки запроса компонент кэширования, вызвав UseOutputCache()
. Для указания того, что ответ конкретной конечной точки должен быть кэширован мы вызываем метод CacheOutput()
. Посмотрим на работу приложения теперь. Первая загрузка страницы:
вторая загрузка (обновление страницы в браузере)
вместо 5 секунд, вывод занял всего 4 мс, так как результат был получен из кэша приложения. Теперь разберемся с настройками кэширования вывода.
Ограничения кэширования
По логике кэширования отдельных элементов в памяти при использование In-Memory кэша, для сервиса кэширования вывода также могут применяться свои ограничения. Для указания ограничений, в метод AddOutputCache()
необходимо передать объект типа OutputCacheOptions
с необходимыми настройками:
SizeLimit
— устанавливает максимальный размер кэша. При достижении этого предела новые ответы не будут кэшироваться до тех пор, пока старые записи не будут удалены. Значение по умолчанию — 100 МБMaximumBodySize
— устанавливает максимальный размер отдельного объекта в хранилище. Если размер ответа превышает это значение, то он не будет кэшироваться. Значение по умолчанию — 64 МБDefaultExpirationTimeSpan
— устанавливает срок хранения кэша, который применяется, если он не указан политикой. Значение по умолчанию — 60 секунд
Например, установим следующие настройки кэширования вывода:
builder.Services.AddOutputCache(options => { options.DefaultExpirationTimeSpan = TimeSpan.FromSeconds(100); options.MaximumBodySize = 100 * 1024 * 1024; //100 Мб options.SizeLimit = 200 * 1024 * 1024; //200 Мб });
Так, кэш будет сохраняться в течение 100 секунд и при этом в кэш поместиться максимум 200 Мб, а максимальный размер одного элемента будет равен 100 Мб.
Настройки кэширования вывода
Параметры метода CacheOutput()
Метод CacheOutput()
может принимать в качестве параметра делегат в котором используется объект OutputCachePolicyBuilder
, который позволяет настроить кэширование. У объекта OutputCachePolicyBuilder
определены следующие методы:
Expire(TimeSpan expiration)
— устанавливает время кэширования в виде объектаTimeSpan
. После истечения этого времени кэш сбрасываетсяSetVaryByHeader(string[] headerNames)
— для каждого набора заголовков, переданных в метод, устанавливается своя версия кэшаSetVaryByHost(bool enabled)
— если в метод передаетсяtrue
, то для каждого хоста устанавливается своя версия кэшаSetVaryByQuery(string[] queryKeys)
— устанавливает свою версию кэша для набора параметров строки запроса, которые передаются в методSetVaryByRouteValue(string[] routeValueNames)
— устанавливает свою версию кэша для набора параметров маршрута, которые передаются в метод
Рассмотрим использование методов настройки кэширования на следующем примере:
namespace AspOutputCache { public class Program { public static void Main(string[] args) { var builder = WebApplication.CreateBuilder(args); builder.Services.AddOutputCache(); var app = builder.Build(); app.UseOutputCache();//добавляем middleware кэширования вывода //условная БД List<Entry> entries = new List<Entry>() { new Entry("John", 25), new Entry("Sam", 15), }; app.MapGet("/add/{name}", (string name, int age) => { entries.Add(new Entry(name, age)); return Results.Ok(entries); }); app.MapGet("/", () => { return Results.Ok(entries); }).CacheOutput(options => options.Expire(TimeSpan.FromSeconds(30))); app.Run(); } } public record Entry { public string name {get; set;} public int age { get; set; } public Entry(string name, int age) { this.name = name; this.age = age; } } }
В этом приложении ведется работа с условной базой данных в которой хранятся данные о людях. При выполнении запроса по маршруту /add/{name}
в БД добавляется новая запись, а при переходе на главную страницу приложения выводится весь список людей из нашей базы данных. При этом, ответ с главной страницы сохраняется в кэше в течение 30 секунд. Посмотрим на работу приложения. После запуска мы увидим первоначальный список:
Теперь добавим нового человека в список:
Как можно видеть — человек был добавлен в список успешно. Теперь снова перейдем на главную страницу:
Несмотря на то, что список состоит фактически из трех элементов, на главной странице мы видим только два из них так как ответ был взят из кэша. Чтобы увидеть изменения нам необходимо подождать 30 секунд и обновить страницу в браузере — в этом случае кэш будет сброшен и записан снова, но уже со всеми элементами:
Атрибут OutputCache и настройки кэширования
Вместо метода CacheOutput
мы можем использовать атрибут OutputCache
в котором также настраивается кэширование вывода. Например, изменим наше приложение следующим образом:
app.MapGet("/", [OutputCache(Duration = 30)] () => { return Results.Ok(entries); });
Этот код по своей работе аналогичен предыдущему — кэш будет сохраняться в течение 30 секунд. У атрибута определены свойства, которые по своим действиям аналогичны методам OutputCachePolicyBuilder
, а именно
int Duration
— устанавливает время кэширования в секундахstring[]? VaryByHeaderNames
— устанавливает набор заголовков, для которых создается кэшstring[]? VaryByQueryKeys
— устанавливает набор параметров строки запроса, для которых создается кэшstring[]? VaryByRouteValueNames
— устанавливает набор параметров маршрута, для которых создается кэш
Политики кэширования
Политики кэширования удобно использовать в том случае, если в приложении определено множество конечных точек и для разных конечных точек необходима своя настройка кэширования. В этом случае, мы для каждой политики можем задать свой набор параметров кэширования и использовать в конечных точках имя политики вместо повторного определения всех настроек. При этом мы можем настроить базовую политику, которая будет действовать глобально и задать отдельные именованные политики, которые будут применяться при необходимости.
Настройка политик кэширования осуществляется также в методе AddOutputCache()
следующим образом:
builder.Services.AddOutputCache(options => { options.AddPolicy("Policy10sec", options => { options.Expire(TimeSpan.FromSeconds(10)); }); options.AddPolicy("Policy5sec", options => { options.Expire(TimeSpan.FromSeconds(5)); }); options.AddPolicy("Policy1sec", options => { options.Expire(TimeSpan.FromSeconds(1)); }); //базовая политика options.AddBasePolicy(baseOptions => baseOptions.Expire(TimeSpan.FromMinutes(1))); });
здесь мы определили три политики кэширования в которых указали время жизни кэша — 10 секунд для первой политики, 5 — для второй и 1 секунду для третьей. Также мы определили базовую политику кэширования, которая будет работать в том случае, если для конечной точке не будут указаны прочие настройки кэширования
Чтобы использовать настройки конкретной политики кэширования, мы можем передать её имя, например, через атрибут:
app.MapGet("/", [OutputCache(PolicyName = "Policy10sec")] () => { return Results.Ok(entries); });
Сброс кэша вручную
Вернемся к нашему приложению с условной БД. Ожидание сброса кэша даже в течение такого непродолжительного промежутка времени как 30 секунд может восприниматься рядовым пользователем нашего приложения как ошибка (особенно, если пользователь очень нетерпеливый), да и, в принципе, мешает работе с приложением. Поэтому нам желательно предусмотреть сброс кэша вручную при наступлении определенного события, например, когда добавляется новый элемент в БД. Для этого мы можем устанавливать для кэша метки. Доработаем наше приложение следующим образом:
app.MapGet("/add/{name}", async (string name, int age, [FromServices] IOutputCacheStore _cache) => { entries.Add(new Entry(name, age)); //сбрасываем кэш с меткой users await _cache.EvictByTagAsync("users", new CancellationToken()); //выводим сообщение в лог app.Logger.LogInformation("Кэш сброшен"); return Results.Ok(entries); }); app.MapGet("/", () => { return Results.Ok(entries); }).CacheOutput(options => options.Tag("users"));
Здесь, в настройках кэширования для конечной точки /
мы вызвали метод Tag()
в который передали метку для элемента кэша — users
. Теперь, используя метку мы можем в любой момент вручную удалить этот элемент кэша, что мы и делаем во второй конечной точке:
//сбрасываем кэш с меткой users await _cache.EvictByTagAsync("users", new CancellationToken());
При этом, мы используем сервис кэширования IOutputCacheStore
, который запрашиваем, используя параметры обработчика конечной точки. Теперь можно запустить приложение и убедиться, что после добавления нового человека в список кэш сразу сбрасывается и на главной странице приложение уже без задержек отображается актуальный список:
Итого
Приложения ASP.NET Core могут кэшировать вывод с использованием сервиса кэширования IOutputCacheStore
. Для элементов кэша мы можем устанавливать время их жизни, метки, по которым вручную удаляется устаревший по каким-либо причинам кэш. При этом, размер кэш-контейнера в случае использования IOutputCacheStore
устанавливается в байтах, в отличие от одноименного параметра, используемого при использовании кэширования в памяти.