Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
В этой статье вы узнаете о различных механизмах кэширования. Кэширование — это процесс хранения данных на промежуточном уровне, что ускоряет получение последующих данных. Концептуально кэширование — это стратегия оптимизации производительности и рекомендации по проектированию. Кэширование может значительно повысить производительность приложения, делая редко изменяющиеся (или дорогостоящие для получения) данные более легко доступными. В этой статье представлено три подхода к кэшированию и пример исходного кода для каждого из них:
- Microsoft.Extensions.Caching.Memory: кэширование в памяти для сценариев с одним сервером
- Microsoft.Extensions.Caching.Hybrid: гибридное кэширование, которое объединяет в памяти и распределенное кэширование с дополнительными функциями
- Microsoft.Extensions.Caching.Distributed: распределенное кэширование для сценариев с несколькими серверами
Это важно
Существует два MemoryCache класса в .NET, один в System.Runtime.Caching пространстве имен и другой в Microsoft.Extensions.Caching пространстве имен:
Хотя в этой статье рассматривается кэширование, она не включает пакет NuGet System.Runtime.Caching. Все упоминания MemoryCache находятся в пространстве имен Microsoft.Extensions.Caching.
Все пакеты Microsoft.Extensions.* поддерживают внедрение зависимостей (DI). Интерфейсы IMemoryCache, HybridCache и IDistributedCache можно использовать как службы.
Кэширование в памяти
В этом разделе описан пакет Microsoft.Extensions.Caching.Memory . Текущая реализация IMemoryCache является оболочкой вокруг ConcurrentDictionary<TKey,TValue>, предоставляя функционально богатый API. Записи в кэше представлены с помощью ICacheEntry и могут быть любого типа object. Решение кэша в памяти отлично подходит для приложений, работающих на одном сервере, где кэшированные данные арендуют память в процессе приложения.
Подсказка
Для сценариев кэширования с несколькими серверами рассмотрите подход к распределенному кэшированию в качестве альтернативы кэшированию в памяти.
API кэширования в памяти
Потребитель кэша контролирует как скользящий, так и абсолютный срок действия:
- ICacheEntry.AbsoluteExpiration
- ICacheEntry.AbsoluteExpirationRelativeToNow
- ICacheEntry.SlidingExpiration
Установка срока действия приводит к вытеснениям записей в кэше, если они не доступны в течение срока действия. У потребителей есть дополнительные возможности для управления записями кэша с помощью MemoryCacheEntryOptions. Каждая ICacheEntry связана с MemoryCacheEntryOptions, которая обеспечивает функционал вытеснения по сроку действия с IChangeToken, настройки приоритета с CacheItemPriority, а также контроль ICacheEntry.Size. Ниже приведены соответствующие методы расширения:
- MemoryCacheEntryExtensions.AddExpirationToken
- MemoryCacheEntryExtensions.RegisterPostEvictionCallback
- MemoryCacheEntryExtensions.SetSize
- MemoryCacheEntryExtensions.SetPriority
Пример кэша в памяти
Чтобы использовать реализацию по умолчанию IMemoryCache , вызовите AddMemoryCache метод расширения, чтобы зарегистрировать все необходимые службы в DI. В следующем примере кода генерический хост используется для предоставления функциональности DI.
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddMemoryCache();
using IHost host = builder.Build();
В зависимости от рабочей нагрузки .NET вы можете получить доступ к IMemoryCache по-разному, например, с помощью внедрения зависимостей через конструктор. В этом примере вы используете IServiceProvider экземпляр на host и вызываете универсальный метод расширения GetRequiredService<T>(IServiceProvider).
IMemoryCache cache =
host.Services.GetRequiredService<IMemoryCache>();
После регистрации служб кэширования в памяти и их разрешения с помощью DI, вы можете начать кэширование. Этот пример выполняет итерацию по буквам в английском алфавите от "A" до "Z". Тип record AlphabetLetter содержит ссылку на букву и создает сообщение.
file record AlphabetLetter(char Letter)
{
internal string Message =>
$"The '{Letter}' character is the {Letter - 64} letter in the English alphabet.";
}
Подсказка
Модификатор file доступа используется для AlphabetLetter типа, так как он определен внутри и доступен только из файла Program.cs . Дополнительные сведения см. в файле (справочник по C#). Чтобы просмотреть полный исходный код, см. раздел Program.cs .
Пример включает в себя вспомогательный функцию, которая выполняет итерацию по буквам алфавита:
static async ValueTask IterateAlphabetAsync(
Func<char, Task> asyncFunc)
{
for (char letter = 'A'; letter <= 'Z'; ++letter)
{
await asyncFunc(letter);
}
Console.WriteLine();
}
В приведенном выше коде C#:
- Ожидается
Func<char, Task> asyncFuncдля каждой итерации, передавая текущийletter. - После обработки всех букв в консоль записывается пустая строка.
Чтобы добавить элементы в кэш, вызовите один из API: Create или Set.
var addLettersToCacheTask = IterateAlphabetAsync(letter =>
{
MemoryCacheEntryOptions options = new()
{
AbsoluteExpirationRelativeToNow =
TimeSpan.FromMilliseconds(MillisecondsAbsoluteExpiration)
};
_ = options.RegisterPostEvictionCallback(OnPostEviction);
AlphabetLetter alphabetLetter =
cache.Set(
letter, new AlphabetLetter(letter), options);
Console.WriteLine($"{alphabetLetter.Letter} was cached.");
return Task.Delay(
TimeSpan.FromMilliseconds(MillisecondsDelayAfterAdd));
});
await addLettersToCacheTask;
В приведенном выше коде C#:
- Переменная
addLettersToCacheTaskделегируетIterateAlphabetAsync, и она ожидается. - Аргументом является
Func<char, Task> asyncFuncс лямбда. - Экземпляр
MemoryCacheEntryOptionsсоздается с абсолютным сроком действия от текущего момента. - Обратный вызов после выселения зарегистрирован.
- Объект
AlphabetLetterсоздается и передается в Set вместе сletterиoptions. - Письмо записывается в консоль как кэшированное.
- Наконец, возвращается объект Task.Delay .
Для каждой буквы в алфавите запись кэша создается с временем истечения срока действия и обратным вызовом по завершении вытеснения.
Обратный вызов после вытеснения записывает сведения о значении, вытесненного в консоль.
static void OnPostEviction(
object key, object? letter, EvictionReason reason, object? state)
{
if (letter is AlphabetLetter alphabetLetter)
{
Console.WriteLine($"{alphabetLetter.Letter} was evicted for {reason}.");
}
};
Теперь, когда кэш заполняется, ожидается еще один вызов IterateAlphabetAsync , но на этот раз вы вызываете IMemoryCache.TryGetValue:
var readLettersFromCacheTask = IterateAlphabetAsync(letter =>
{
if (cache.TryGetValue(letter, out object? value) &&
value is AlphabetLetter alphabetLetter)
{
Console.WriteLine($"{letter} is still in cache. {alphabetLetter.Message}");
}
return Task.CompletedTask;
});
await readLettersFromCacheTask;
Если cache содержит ключ letter, и value является экземпляром AlphabetLetter, то это записывается в консоль.
letter Если ключ не в кэше, он был вытеснен, и был вызван обратный вызов после вытеснения.
Дополнительные методы расширения
Поставляется IMemoryCache с множеством удобных методов расширения, включая асинхронный GetOrCreateAsync:
- CacheExtensions.Get
- CacheExtensions.GetOrCreate
- CacheExtensions.GetOrCreateAsync
- CacheExtensions.Set
- CacheExtensions.TryGetValue
Соберите всё вместе
Весь пример исходного кода приложения является программой верхнего уровня и требует двух пакетов NuGet:
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddMemoryCache();
using IHost host = builder.Build();
IMemoryCache cache =
host.Services.GetRequiredService<IMemoryCache>();
const int MillisecondsDelayAfterAdd = 50;
const int MillisecondsAbsoluteExpiration = 750;
static void OnPostEviction(
object key, object? letter, EvictionReason reason, object? state)
{
if (letter is AlphabetLetter alphabetLetter)
{
Console.WriteLine($"{alphabetLetter.Letter} was evicted for {reason}.");
}
};
static async ValueTask IterateAlphabetAsync(
Func<char, Task> asyncFunc)
{
for (char letter = 'A'; letter <= 'Z'; ++letter)
{
await asyncFunc(letter);
}
Console.WriteLine();
}
var addLettersToCacheTask = IterateAlphabetAsync(letter =>
{
MemoryCacheEntryOptions options = new()
{
AbsoluteExpirationRelativeToNow =
TimeSpan.FromMilliseconds(MillisecondsAbsoluteExpiration)
};
_ = options.RegisterPostEvictionCallback(OnPostEviction);
AlphabetLetter alphabetLetter =
cache.Set(
letter, new AlphabetLetter(letter), options);
Console.WriteLine($"{alphabetLetter.Letter} was cached.");
return Task.Delay(
TimeSpan.FromMilliseconds(MillisecondsDelayAfterAdd));
});
await addLettersToCacheTask;
var readLettersFromCacheTask = IterateAlphabetAsync(letter =>
{
if (cache.TryGetValue(letter, out object? value) &&
value is AlphabetLetter alphabetLetter)
{
Console.WriteLine($"{letter} is still in cache. {alphabetLetter.Message}");
}
return Task.CompletedTask;
});
await readLettersFromCacheTask;
await host.RunAsync();
file record AlphabetLetter(char Letter)
{
internal string Message =>
$"The '{Letter}' character is the {Letter - 64} letter in the English alphabet.";
}
Вы можете настроить MillisecondsDelayAfterAdd и MillisecondsAbsoluteExpiration значения, чтобы наблюдать за изменениями в поведении до истечения срока действия и вытеснения кэшированных записей. Ниже приведен пример выходных данных для выполнения этого кода. (Из-за недетерминированной природы событий .NET выходные данные могут отличаться.)
A was cached.
B was cached.
C was cached.
D was cached.
E was cached.
F was cached.
G was cached.
H was cached.
I was cached.
J was cached.
K was cached.
L was cached.
M was cached.
N was cached.
O was cached.
P was cached.
Q was cached.
R was cached.
S was cached.
T was cached.
U was cached.
V was cached.
W was cached.
X was cached.
Y was cached.
Z was cached.
A was evicted for Expired.
C was evicted for Expired.
B was evicted for Expired.
E was evicted for Expired.
D was evicted for Expired.
F was evicted for Expired.
H was evicted for Expired.
K was evicted for Expired.
L was evicted for Expired.
J was evicted for Expired.
G was evicted for Expired.
M was evicted for Expired.
N was evicted for Expired.
I was evicted for Expired.
P was evicted for Expired.
R was evicted for Expired.
O was evicted for Expired.
Q was evicted for Expired.
S is still in cache. The 'S' character is the 19 letter in the English alphabet.
T is still in cache. The 'T' character is the 20 letter in the English alphabet.
U is still in cache. The 'U' character is the 21 letter in the English alphabet.
V is still in cache. The 'V' character is the 22 letter in the English alphabet.
W is still in cache. The 'W' character is the 23 letter in the English alphabet.
X is still in cache. The 'X' character is the 24 letter in the English alphabet.
Y is still in cache. The 'Y' character is the 25 letter in the English alphabet.
Z is still in cache. The 'Z' character is the 26 letter in the English alphabet.
Так как задан абсолютный срок действия (MemoryCacheEntryOptions.AbsoluteExpirationRelativeToNow), все кэшированные элементы в конечном итоге будут вытеснены.
Кэширование рабочей службы
Одна из распространенных стратегий кэширования данных — обновление кэша независимо от используемых служб данных. Шаблон рабочей службы является отличным примером, так как BackgroundService выполняется независимо (или в фоновом режиме) от другого кода приложения. При запуске приложения, содержащего реализацию IHostedService, соответствующая реализация (в данном случае BackgroundService или "рабочий элемент") запускается в том же процессе. Эти размещенные службы регистрируются в DI как одиночные объекты через метод расширения AddHostedService<THostedService>(IServiceCollection). Другие службы можно зарегистрировать в DI с любым временем существования службы.
Это важно
Важно понимать сроки службы. При вызове AddMemoryCache для регистрации всех служб кэширования в памяти, эти службы регистрируются как синглтоны.
Сценарий службы фотографий
Представьте, что вы разрабатываете службу фотографий, которая использует сторонний API, доступный через HTTP. Эти данные фотографий не меняются часто, но их много. Каждая фотография представлена простым record:
namespace CachingExamples.Memory;
public readonly record struct Photo(
int AlbumId,
int Id,
string Title,
string Url,
string ThumbnailUrl);
В следующем примере вы увидите несколько служб, зарегистрированных в DI. Каждая служба несет отдельную ответственность.
using CachingExamples.Memory;
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddMemoryCache();
builder.Services.AddHttpClient<CacheWorker>();
builder.Services.AddHostedService<CacheWorker>();
builder.Services.AddScoped<PhotoService>();
builder.Services.AddSingleton(typeof(CacheSignal<>));
using IHost host = builder.Build();
await host.StartAsync();
В приведенном выше коде C#:
- Универсальный хост создается с настройками по умолчанию.
- Службы кэширования в памяти регистрируются с AddMemoryCache.
- Экземпляр
HttpClientрегистрируется для классаCacheWorkerс помощью AddHttpClient<TClient>(IServiceCollection). - Класс
CacheWorkerзарегистрирован в AddHostedService<THostedService>(IServiceCollection). - Класс
PhotoServiceзарегистрирован в AddScoped<TService>(IServiceCollection). - Класс
CacheSignal<T>зарегистрирован в AddSingleton. - Экземпляр
hostсоздается с помощью построителя и запускается асинхронно.
За PhotoService закреплена ответственность за получение фотографий, соответствующих заданным критериям (или filter):
using Microsoft.Extensions.Caching.Memory;
namespace CachingExamples.Memory;
public sealed class PhotoService(
IMemoryCache cache,
CacheSignal<Photo> cacheSignal,
ILogger<PhotoService> logger)
{
public async IAsyncEnumerable<Photo> GetPhotosAsync(Func<Photo, bool>? filter = default)
{
try
{
await cacheSignal.WaitAsync();
Photo[] photos =
(await cache.GetOrCreateAsync(
"Photos", _ =>
{
logger.LogWarning("This should never happen!");
return Task.FromResult(Array.Empty<Photo>());
}))!;
// If no filter is provided, use a pass-thru.
filter ??= _ => true;
foreach (Photo photo in photos)
{
if (!default(Photo).Equals(photo) && filter(photo))
{
yield return photo;
}
}
}
finally
{
cacheSignal.Release();
}
}
}
В приведенном выше коде C#:
- Конструктору требуется
IMemoryCache,CacheSignal<Photo>иILogger. - Метод:
GetPhotosAsync- Определяет
Func<Photo, bool> filterпараметр и возвращает объектIAsyncEnumerable<Photo>. - Вызывает и ожидает освобождения
_cacheSignal.WaitAsync(); это гарантирует заполнение кэша до доступа к нему. - Вызывает
_cache.GetOrCreateAsync(), асинхронно получая все фотографии из кэша. - Аргумент записывает предупреждение и возвращает пустой массив фотографий. Это
factoryникогда не должно произойти. - Все фотографии в кэше итерируются, фильтруются и материализуются с помощью
yield return. - Наконец, сигнал кэша перезагружается.
- Определяет
Потребители этой услуги могут вызывать метод GetPhotosAsync и обрабатывать фотографии надлежащим образом. Не HttpClient требуется, так как кэш содержит фотографии.
Асинхронный сигнал основан на инкапсулированном SemaphoreSlim экземпляре в ограниченном одноэлементе универсального типа. Элемент CacheSignal<T> зависит от экземпляра SemaphoreSlim.
namespace CachingExamples.Memory;
public sealed class CacheSignal<T>
{
private readonly SemaphoreSlim _semaphore = new(1, 1);
/// <summary>
/// Exposes a <see cref="Task"/> that represents the asynchronous wait operation.
/// When signaled (consumer calls <see cref="Release"/>), the
/// <see cref="Task.Status"/> is set as <see cref="TaskStatus.RanToCompletion"/>.
/// </summary>
public Task WaitAsync() => _semaphore.WaitAsync();
/// <summary>
/// Exposes the ability to signal the release of the <see cref="WaitAsync"/>'s operation.
/// Callers who were waiting, will be able to continue.
/// </summary>
public void Release() => _semaphore.Release();
}
В приведенном выше коде C# шаблон декоратора используется для упаковки экземпляра SemaphoreSlim.
CacheSignal<T> Поскольку он зарегистрирован как синглтон, его можно использовать на протяжении всех этапов жизненного цикла службы с любым универсальным типом — в данном случае Photo. Он отвечает за сигнал о инициализации кэша.
CacheWorker является подклассом BackgroundService.
using System.Net.Http.Json;
using Microsoft.Extensions.Caching.Memory;
namespace CachingExamples.Memory;
public sealed class CacheWorker(
ILogger<CacheWorker> logger,
HttpClient httpClient,
CacheSignal<Photo> cacheSignal,
IMemoryCache cache) : BackgroundService
{
private readonly TimeSpan _updateInterval = TimeSpan.FromHours(3);
private bool _isCacheInitialized = false;
private const string Url = "https://jsonplaceholder.typicode.com/photos";
public override async Task StartAsync(CancellationToken cancellationToken)
{
await cacheSignal.WaitAsync();
await base.StartAsync(cancellationToken);
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
logger.LogInformation("Updating cache.");
try
{
Photo[]? photos =
await httpClient.GetFromJsonAsync<Photo[]>(
Url, stoppingToken);
if (photos is { Length: > 0 })
{
cache.Set("Photos", photos);
logger.LogInformation(
"Cache updated with {Count:#,#} photos.", photos.Length);
}
else
{
logger.LogWarning(
"Unable to fetch photos to update cache.");
}
}
finally
{
if (!_isCacheInitialized)
{
cacheSignal.Release();
_isCacheInitialized = true;
}
}
try
{
logger.LogInformation(
"Will attempt to update the cache in {Hours} hours from now.",
_updateInterval.Hours);
await Task.Delay(_updateInterval, stoppingToken);
}
catch (OperationCanceledException)
{
logger.LogWarning("Cancellation acknowledged: shutting down.");
break;
}
}
}
}
В приведенном выше коде C#:
- Конструктору требуется
ILogger,HttpClientиIMemoryCache. - Определяется
_updateIntervalв течение трех часов. - Метод:
ExecuteAsync- Циклы во время работы приложения.
- Выполняет HTTP-запрос к
"https://jsonplaceholder.typicode.com/photos", и сопоставляет ответ как массив объектовPhoto. - Массив фотографий размещается под ключом
IMemoryCacheв"Photos". - При вызове
_cacheSignal.Release()освобождаются все потребители, ожидавшие сигнала. - Вызов Task.Delay ожидается, учитывая интервал обновления.
- После задержки в течение трех часов кэш снова обновляется.
Потребители в рамках того же процесса могут попросить IMemoryCache фотографии, но CacheWorker отвечает за обновление кэша.
Гибридное кэширование
Библиотека HybridCache объединяет преимущества кэширования в памяти и распределенного кэширования при решении распространенных проблем с существующими API кэширования. Представленный в .NET 9, HybridCache предоставляет единый API, который упрощает реализацию кэширования и включает встроенные функции, такие как защита от эффектов лавины запросов и настраиваемая сериализация.
Ключевые особенности
HybridCache предлагает несколько преимуществ по сравнению с использованием IMemoryCache и IDistributedCache отдельно:
- Кэширование двухуровневого уровня: автоматически управляет уровнями кэша в памяти (L1) и распределенным (L2). Данные сначала извлекаются из кэша в памяти для скорости, а затем из распределенного кэша при необходимости и, наконец, из источника.
- Защита с меткой: предотвращает выполнение одной и той же дорогостоящей операции нескольких одновременных запросов. Только один запрос извлекает данные, а другие ожидают результата.
- Настраиваемая сериализация: поддерживает несколько форматов сериализации, включая JSON (по умолчанию), protobuf и XML.
- Недействительность на основе тегов: группы связанных записей кэша с тегами для эффективной пакетной недействительности.
-
Упрощенный API:
GetOrCreateAsyncметод обрабатывает пропущенные кэши, сериализацию и хранилище автоматически.
Когда следует использовать HybridCache
Рассмотрите возможность использования HybridCache когда:
- Требуется как локальное (в памяти), так и распределенное кэширование в среде с несколькими серверами.
- Вы хотите защиту от сценариев лавины кеша.
- Вы предпочитаете упрощенный API вместо ручной координации
IMemoryCacheиIDistributedCache. - Для связанных записей требуется инвалидация кэша на основе тегов.
Подсказка
Для односерверных приложений с простыми потребностями кэширования может быть достаточно кэширования в памяти . Для многосерверных приложений без необходимости защиты от перегрузки или недействительности с использованием тегов рассмотрите распределенное кэширование.
Настройка HybridCache
Чтобы использовать HybridCache, установите Microsoft.Extensions.Caching.Hybrid пакет NuGet:
dotnet add package Microsoft.Extensions.Caching.Hybrid
HybridCache Зарегистрируйте сервис с помощью DI, вызвав:AddHybridCache
var builder = Host.CreateApplicationBuilder(args);
builder.Services.AddHybridCache();
Предыдущий код регистрирует HybridCache с параметрами по умолчанию. Вы также можете настроить глобальные параметры:
var builderWithOptions = Host.CreateApplicationBuilder(args);
builderWithOptions.Services.AddHybridCache(options =>
{
options.MaximumPayloadBytes = 1024 * 1024; // 1 MB
options.MaximumKeyLength = 1024;
options.DefaultEntryOptions = new HybridCacheEntryOptions
{
Expiration = TimeSpan.FromMinutes(5),
LocalCacheExpiration = TimeSpan.FromMinutes(2)
};
});
Базовое использование
Основным методом взаимодействия с HybridCache является GetOrCreateAsync. Этот метод проверяет кэш на наличие записи с указанным ключом; если она не найдена, вызывает метод фабрики для получения данных.
async Task<WeatherData> GetWeatherDataAsync(HybridCache cache, string city)
{
return await cache.GetOrCreateAsync(
$"weather:{city}",
async cancellationToken =>
{
// Simulate fetching from an external API
await Task.Delay(100, cancellationToken);
return new WeatherData(city, 72, "Sunny");
}
);
}
В приведенном выше коде C#:
- Метод
GetOrCreateAsyncпринимает уникальный ключ и фабричный метод. - Если данные не в кэше, то фабричный метод вызывается для получения данных.
- Данные автоматически хранятся как в памяти, так и в распределенных кэшах.
- Только один одновременный запрос выполняет фабричный метод; другие ожидают результата.
Параметры ввода
Глобальные значения по умолчанию можно переопределить для определенных записей кэша с помощью HybridCacheEntryOptions:
async Task<WeatherData> GetWeatherWithOptionsAsync(HybridCache cache, string city)
{
var entryOptions = new HybridCacheEntryOptions
{
Expiration = TimeSpan.FromMinutes(10),
LocalCacheExpiration = TimeSpan.FromMinutes(5)
};
return await cache.GetOrCreateAsync(
$"weather:{city}",
async cancellationToken => new WeatherData(city, 72, "Sunny"),
entryOptions
);
}
Параметры ввода позволяют настроить:
- HybridCacheEntryOptions.Expiration: как долго запись должна кэшироваться в распределенном кэше.
- HybridCacheEntryOptions.LocalCacheExpiration: Сколько времени запись должна кэшироваться в локальной памяти.
- HybridCacheEntryOptions.Flags: дополнительные флаги для управления поведением кэша.
Аннулирование на основе тегов
Теги позволяют группировать связанные записи кэша и отменять их вместе. Это полезно для сценариев, когда связанные данные необходимо обновить в качестве единого блока.
async Task<CustomerData> GetCustomerAsync(HybridCache cache, int customerId)
{
var tags = new[] { "customer", $"customer:{customerId}" };
return await cache.GetOrCreateAsync(
$"customer:{customerId}",
async cancellationToken => new CustomerData(customerId, "John Doe", "john@example.com"),
new HybridCacheEntryOptions { Expiration = TimeSpan.FromMinutes(30) },
tags
);
}
Чтобы сделать недействительными все записи с определенным тегом:
async Task InvalidateCustomerCacheAsync(HybridCache cache, int customerId)
{
await cache.RemoveByTagAsync($"customer:{customerId}");
}
Можно также одновременно сделать несколько тегов недействительными:
async Task InvalidateAllCustomersAsync(HybridCache cache)
{
await cache.RemoveByTagAsync(new[] { "customer", "orders" });
}
Замечание
Аннулирование на основе тегов — это логическая операция. Он не активно удаляет значения из кэша, но гарантирует, что помеченные записи рассматриваются как промахи кэша. Срок действия записей в конечном итоге истекает на основе их настроенного времени существования.
Удаление записей кэша
Чтобы удалить определенную запись кэша по ключу RemoveAsync , используйте метод:
async Task RemoveWeatherDataAsync(HybridCache cache, string city)
{
await cache.RemoveAsync($"weather:{city}");
}
Чтобы сделать недействительными все кэшированные записи, используйте зарезервированный подстановочный символ "*":
async Task InvalidateAllCacheAsync(HybridCache cache)
{
await cache.RemoveByTagAsync("*");
}
Сериализация
Для распределенных HybridCache сценариев кэширования требуется сериализация. По умолчанию он обрабатывает string и byte[], а System.Text.Json используется для других типов. Вы можете настроить пользовательские сериализаторы для определенных типов или использовать сериализатор общего назначения:
// Custom serialization example
// Note: This requires implementing a custom IHybridCacheSerializer<T>
var builderWithSerializer = Host.CreateApplicationBuilder(args);
builderWithSerializer.Services.AddHybridCache(options =>
{
options.DefaultEntryOptions = new HybridCacheEntryOptions
{
Expiration = TimeSpan.FromMinutes(10),
LocalCacheExpiration = TimeSpan.FromMinutes(5)
};
});
// To add a custom serializer, uncomment and provide your implementation:
// .AddSerializer<WeatherData, CustomWeatherDataSerializer>();
Настройка распределенного кэша
HybridCache использует настроенную IDistributedCache реализацию для распределенного кэша (L2). Даже без настройки IDistributedCache, HybridCache по-прежнему обеспечивает кэширование в памяти и защиту от наплыва. Чтобы добавить Redis в качестве распределенного кэша, выполните приведенные действия.
// Distributed cache with Redis
var builderWithRedis = Host.CreateApplicationBuilder(args);
builderWithRedis.Services.AddStackExchangeRedisCache(options =>
{
options.Configuration = "localhost:6379";
});
builderWithRedis.Services.AddHybridCache(options =>
{
options.DefaultEntryOptions = new HybridCacheEntryOptions
{
Expiration = TimeSpan.FromMinutes(30),
LocalCacheExpiration = TimeSpan.FromMinutes(5)
};
});
Дополнительные сведения о реализации распределенного кэша см. в разделе "Распределенное кэширование".
Распределенное кэширование
В некоторых сценариях требуется распределенный кэш. Это относится к нескольким серверам приложений. Распределенный кэш поддерживает более высокое масштабирование, чем подход к кэшированию в память. Использование распределенного кэша переносит нагрузку памяти кэша на внешний процесс, но требует дополнительных сетевых операций ввода-вывода и увеличивает задержку (пусть и минимально).
Абстракции распределенного кэширования являются частью Microsoft.Extensions.Caching.Memory пакета NuGet, и даже существует AddDistributedMemoryCache метод расширения.
Осторожность
AddDistributedMemoryCache следует использовать только в сценариях разработки или тестирования и не является жизнеспособной производственной реализацией.
Рассмотрите любую из доступных реализаций IDistributedCache из следующих пакетов:
Microsoft.Extensions.Caching.SqlServerMicrosoft.Extensions.Caching.StackExchangeRedisNCache.Microsoft.Extensions.Caching.OpenSource
API распределенного кэширования
Распределенные API кэширования являются немного более примитивными, чем их аналоги API кэширования в памяти. Пары "ключ-значение" немного более базовые. Ключи кэширования в памяти основаны на object, тогда как распределенные ключи — это string. При кэшировании в памяти значение может быть любым строго типизированным универсальным, а значения в распределенном кэшировании сохраняются как byte[]. Это не значит, что различные реализации не предоставляют строго типизированные универсальные значения, но это детали реализации.
Создание ценностей
Чтобы создать значения в распределенном кэше, вызовите один из api набора:
AlphabetLetter Используя запись из примера кэша в памяти, можно сериализовать объект в JSON, а затем закодировать string в видеbyte[]:
DistributedCacheEntryOptions options = new()
{
AbsoluteExpirationRelativeToNow =
TimeSpan.FromMilliseconds(MillisecondsAbsoluteExpiration)
};
AlphabetLetter alphabetLetter = new(letter);
string json = JsonSerializer.Serialize(alphabetLetter);
byte[] bytes = Encoding.UTF8.GetBytes(json);
await cache.SetAsync(letter.ToString(), bytes, options);
Как и кэширование в памяти, записи кэша могут иметь возможности для точной настройки их существования в кэше ( в данном случае — значение DistributedCacheEntryOptions).
Создание методов расширения
Существует несколько удобных методов расширения для создания значений. Эти методы помогают избежать кодирования string представлений объектов в byte[]:
Чтение значений
Чтобы считывать значения из распределенного кэша, вызовите один из Get API:
AlphabetLetter? alphabetLetter = null;
byte[]? bytes = await cache.GetAsync(letter.ToString());
if (bytes is { Length: > 0 })
{
string json = Encoding.UTF8.GetString(bytes);
alphabetLetter = JsonSerializer.Deserialize<AlphabetLetter>(json);
}
После чтения записи кэша из кэша можно получить представление string в кодировке UTF8 из кэша byte[].
Изучение методов расширения
Существует несколько методов расширения, ориентированных на удобство, для чтения значений. Эти методы помогают избежать декодирования byte[] в string представлениях объектов:
Обновление значений
Невозможно обновить значения в распределенном кэше одним вызовом API. Вместо этого значения могут сбросить срок действия с скользящими сроками действия с помощью одного из API обновления:
Если фактическое значение необходимо обновить, необходимо удалить значение, а затем повторно добавить его.
Удалите значения
Чтобы удалить значения в распределенном кэше, вызовите один из Remove API:
Подсказка
Хотя существуют синхронные версии этих API, рассмотрите тот факт, что реализации распределенных кэшей зависят от сетевых операций ввода-вывода. По этой причине обычно предпочтительнее использовать асинхронные API.