Školení
Modul
Zvýšení výkonu pomocí mezipaměti v projektu .NET Aspire - Training
V tomto modulu se dozvíte o mezipaměti v aplikaci nativní pro cloud .NET Aspire a o tom, jak je použít k optimalizaci výkonu mikroslužeb.
Tento prohlížeč se už nepodporuje.
Upgradujte na Microsoft Edge, abyste mohli využívat nejnovější funkce, aktualizace zabezpečení a technickou podporu.
V tomto článku se dozvíte o různých mechanismech ukládání do mezipaměti. Ukládání do mezipaměti je ukládání dat v přechodné vrstvě, což zrychlová následné načítání dat. Ukládání do mezipaměti je strategie optimalizace výkonu a aspekty návrhu. Ukládání do mezipaměti může výrazně zlepšit výkon aplikace tím, že se data často mění (nebo jsou nákladnější) snadno dostupná. Tento článek představuje dva primární typy ukládání do mezipaměti a poskytuje vzorový zdrojový kód pro oba:
Důležité
V rámci .NET existují dvě MemoryCache
třídy, jedna v System.Runtime.Caching
oboru názvů a druhá v Microsoft.Extensions.Caching
oboru názvů:
I když se tento článek zaměřuje na ukládání do mezipaměti, neobsahuje System.Runtime.Caching
balíček NuGet. Všechny odkazy, na které se vztahují MemoryCache
, Microsoft.Extensions.Caching
jsou v oboru názvů.
Microsoft.Extensions.*
Všechny balíčky jsou připravené injektáž závislostí (DI), a to jak rozhraní IMemoryCacheIDistributedCache, tak i rozhraní lze použít jako služby.
V této části se dozvíte o Microsoft.Extensions.Ukládání do mezipaměti. Balíček paměti. Aktuální implementace IMemoryCache je obálka kolem ConcurrentDictionary<TKey,TValue>rozhraní API s bohatými funkcemi. Položky v mezipaměti jsou reprezentovány ICacheEntrya mohou být libovolné object
. Řešení mezipaměti v paměti je skvělé pro aplikace, které běží na jednom serveru, kde všechna data uložená v mezipaměti pronajímají paměť v procesu aplikace.
Tip
U scénářů ukládání do mezipaměti s více servery zvažte přístup distribuované mezipaměti jako alternativu k ukládání do mezipaměti v paměti.
Příjemce mezipaměti má kontrolu nad posuvným i absolutním vypršením platnosti:
Nastavení vypršení platnosti způsobí, že se položky v mezipaměti vyřadí , pokud nebudou v době vypršení platnosti přístupné. Uživatelé mají další možnosti pro řízení položek mezipaměti prostřednictvím .MemoryCacheEntryOptions Každý ICacheEntry je spárován s MemoryCacheEntryOptions tím, že zveřejňuje funkce vyřazení vypršení platnosti s IChangeToken, nastavení priority s CacheItemPrioritya řízení ICacheEntry.Size. Zvažte následující metody rozšíření:
Chcete-li použít výchozí IMemoryCache implementaci, zavolejte metodu AddMemoryCache rozšíření pro registraci všech požadovaných služeb v DI. V následující ukázce kódu se obecný hostitel používá ke zveřejnění funkcí 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();
V závislosti na úloze .NET můžete přistupovat k jiným způsobem, například injektáž konstruktoru IMemoryCache
. V této ukázce použijete instanci pro metodu IServiceProvider
host
obecného GetRequiredService<T>(IServiceProvider) rozšíření a zavoláte ji:
IMemoryCache cache =
host.Services.GetRequiredService<IMemoryCache>();
S zaregistrovanými službami ukládání do mezipaměti v paměti a vyřešenými prostřednictvím direktu můžete začít s ukládáním do mezipaměti. Tato ukázka prochází písmeny v anglické abecedě A až Z. Typ record AlphabetLetter
obsahuje odkaz na písmeno a vygeneruje zprávu.
file record AlphabetLetter(char Letter)
{
internal string Message =>
$"The '{Letter}' character is the {Letter - 64} letter in the English alphabet.";
}
Tip
Modifikátor file
přístupu se používá u AlphabetLetter
typu, protože je definován v souboru Program.cs a je k němu přístup pouze z souboru Program.cs . Další informace najdete v souboru (referenční dokumentace jazyka C#). Úplný zdrojový kód najdete v části Program.cs .
Ukázka obsahuje pomocnou funkci, která prochází písmeny abecedy:
static async ValueTask IterateAlphabetAsync(
Func<char, Task> asyncFunc)
{
for (char letter = 'A'; letter <= 'Z'; ++letter)
{
await asyncFunc(letter);
}
Console.WriteLine();
}
V předchozím kódu jazyka C#:
Func<char, Task> asyncFunc
při každé iteraci a předává aktuální letter
.Přidání položek do mezipaměti volání jednoho z Create
rozhraní API nebo Set
rozhraní API:
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;
V předchozím kódu jazyka C#:
addLettersToCacheTask
deleguje IterateAlphabetAsync
a očekává se.Func<char, Task> asyncFunc
lambda.MemoryCacheEntryOptions
se instance s absolutním vypršením platnosti vzhledem k této chvíli.AlphabetLetter
instanci objektu a předá se spolu Set s objektem letter
a options
.Pro každé písmeno v abecedě se zapíše položka mezipaměti s vypršením platnosti a zpětné volání po vyřazení.
Zpětné volání po vyřazení zapíše podrobnosti o hodnotě, která byla vyřazena do konzoly:
static void OnPostEviction(
object key, object? letter, EvictionReason reason, object? state)
{
if (letter is AlphabetLetter alphabetLetter)
{
Console.WriteLine($"{alphabetLetter.Letter} was evicted for {reason}.");
}
};
Teď, když je mezipaměť naplněná, čeká se další volání IterateAlphabetAsync
, ale tentokrát zavoláte 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
Pokud klíč obsahuje letter
a value
jedná se o AlphabetLetter
instanci, která se zapíše do konzoly. letter
Pokud klíč není v mezipaměti, byl vyřazen a jeho zpětné volání po vyřazení bylo vyvoláno.
Dodává IMemoryCache
se s mnoha metodami rozšíření založenými na pohodlí, včetně asynchronního GetOrCreateAsync
:
Celý zdrojový kód ukázkové aplikace je program nejvyšší úrovně a vyžaduje dva balíčky 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.";
}
Nebojte se upravit MillisecondsDelayAfterAdd
hodnoty a MillisecondsAbsoluteExpiration
sledovat změny chování vypršení platnosti a vyřazení položek uložených v mezipaměti. Následuje ukázkový výstup spuštění tohoto kódu. Vzhledem k ne deterministické povaze událostí .NET se může výstup lišit.
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.
Vzhledem k tomu, že je nastaveno absolutní vypršení platnosti (MemoryCacheEntryOptions.AbsoluteExpirationRelativeToNow), všechny položky uložené v mezipaměti se nakonec vyřadí.
Jednou z běžných strategií ukládání dat do mezipaměti je aktualizace mezipaměti nezávisle na využívání datových služeb. Šablona Služby pracovního procesu je skvělým příkladem, protože BackgroundService běží nezávisle (nebo na pozadí) z jiného kódu aplikace. Když aplikace spustí spuštění, které hostuje implementaci IHostedService, odpovídající implementace (v tomto případě BackgroundService
"pracovní proces") se spustí ve stejném procesu. Tyto hostované služby jsou prostřednictvím metody rozšíření zaregistrované v DI jako singletony AddHostedService<THostedService>(IServiceCollection) . Ostatní služby je možné zaregistrovat v DI s libovolnou životností služeb.
Důležité
Životnost služby je velmi důležitá pro pochopení. Když voláte AddMemoryCache k registraci všech služeb ukládání do mezipaměti v paměti, jsou služby registrovány jako singletony.
Představte si, že vyvíjíte fotoslužbě, která využívá rozhraní API třetích stran přístupné přes protokol HTTP. Tato data fotek se moc často nemění, ale je jich hodně. Každá fotografie je reprezentována jednoduchým record
:
namespace CachingExamples.Memory;
public readonly record struct Photo(
int AlbumId,
int Id,
string Title,
string Url,
string ThumbnailUrl);
V následujícím příkladu uvidíte registraci několika služeb v DI. Každá služba má jednu zodpovědnost.
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();
V předchozím kódu jazyka C#:
HttpClient
je registrována CacheWorker
pro třídu s AddHttpClient<TClient>(IServiceCollection).CacheWorker
v AddHostedService<THostedService>(IServiceCollection).PhotoService
v AddScoped<TService>(IServiceCollection).CacheSignal<T>
v AddSingleton.host
instanci tvůrce a spustí se asynchronně.Zodpovídá PhotoService
za získání fotek, které odpovídají zadaným kritériím (nebo 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();
}
}
}
V předchozím kódu jazyka C#:
IMemoryCache
CacheSignal<Photo>
a ILogger
.GetPhotosAsync
: Func<Photo, bool> filter
Definuje parametr a vrátí hodnotu IAsyncEnumerable<Photo>
._cacheSignal.WaitAsync()
vydání zajistí, že se mezipaměť před přístupem k mezipaměti naplní._cache.GetOrCreateAsync()
, asynchronně získání všech fotek v mezipaměti.factory
zaznamená upozornění a vrátí prázdné pole fotek . K tomu by nikdy nemělo dojít.yield return
.Uživatelé této služby mohou volat metodu volání GetPhotosAsync
a odpovídajícím způsobem zpracovávat fotky. Nevyžaduje HttpClient
se, protože mezipaměť obsahuje fotky.
Asynchronní signál je založen na zapouzdřené SemaphoreSlim instanci v rámci omezeného singletonu obecného typu. Spoléhá CacheSignal<T>
na instanci 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();
}
V předchozím kódu jazyka C# se vzor dekorátoru používá k zabalení instance objektu SemaphoreSlim
. CacheSignal<T>
Vzhledem k tomu, že je zaregistrovaný jako jednoúčelový, lze ho použít ve všech životnostech služeb s jakýmkoli obecným typem – v tomto případě .Photo
Zodpovídá za signalizaci počátečních hodnot mezipaměti.
Jedná se CacheWorker
o podtřídu 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;
}
}
}
}
V předchozím kódu jazyka C#:
ILogger
HttpClient
a IMemoryCache
._updateInterval
po dobu tří hodin.ExecuteAsync
: "https://jsonplaceholder.typicode.com/photos"
a mapuje odpověď jako pole Photo
objektů.IMemoryCache
podklíče "Photos"
._cacheSignal.Release()
a vydává všechny příjemce, kteří čekali na signál.Uživatelé ve stejném procesu mohou požádat IMemoryCache
o fotky, ale CacheWorker
je zodpovědný za aktualizaci mezipaměti.
V některých scénářích se vyžaduje distribuovaná mezipaměť – to je případ několika aplikačních serverů. Distribuovaná mezipaměť podporuje horizontální navýšení kapacity než přístup k ukládání do mezipaměti v paměti. Použití distribuované mezipaměti přesměruje paměť mezipaměti do externího procesu, ale vyžaduje další vstupně-výstupní operace sítě a zavádí trochu větší latenci (i když je nominální).
Distribuovaná Microsoft.Extensions.Caching.Memory
abstrakce ukládání do mezipaměti jsou součástí balíčku NuGet a existuje dokonce i AddDistributedMemoryCache
metoda rozšíření.
Upozornění
Měla AddDistributedMemoryCache by se používat pouze ve scénářích vývoje a/nebo testování a nejedná se o realizovatelnou produkční implementaci.
Vezměte v úvahu některou z dostupných implementací IDistributedCache
následujících balíčků:
Microsoft.Extensions.Caching.SqlServer
Microsoft.Extensions.Caching.StackExchangeRedis
NCache.Microsoft.Extensions.Caching.OpenSource
Distribuovaná rozhraní API pro ukládání do mezipaměti jsou trochu primitivnější než jejich protějšky rozhraní API pro ukládání do mezipaměti v paměti. Páry klíč-hodnota jsou trochu základní. Klíče ukládání do mezipaměti v paměti jsou založeny na , object
zatímco distribuované klíče jsou .string
Při ukládání do mezipaměti v paměti může být hodnota libovolný obecný typ silného typu, zatímco hodnoty v distribuované mezipaměti jsou trvalé jako byte[]
. To neznamená, že různé implementace nezpřístupňují obecné hodnoty silného typu, ale to by bylo podrobnosti implementace.
Pokud chcete vytvořit hodnoty v distribuované mezipaměti, zavolejte jedno z nastavených rozhraní API:
AlphabetLetter
Pomocí záznamu z příkladu mezipaměti v paměti můžete objekt serializovat do formátu JSON a pak kódovat string
jako 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);
Podobně jako ukládání do mezipaměti v paměti můžou mít položky mezipaměti možnosti, které pomáhají vyladit jejich existenci v mezipaměti – v tomto případě .DistributedCacheEntryOptions
Existuje několik metod rozšíření založených na pohodlí pro vytváření hodnot, které pomáhají vyhnout se kódování string
reprezentací objektů do byte[]
:
Pokud chcete číst hodnoty z distribuované mezipaměti, zavolejte jedno z rozhraní API get:
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);
}
Jakmile se položka mezipaměti přečte z mezipaměti, můžete získat reprezentaci zakódovanou string
kódováním UTF8 z byte[]
Existuje několik metod rozšíření založených na pohodlí pro čtení hodnot, které pomáhají vyhnout se dekódování byte[]
do string
reprezentace objektů:
Neexistuje způsob, jak aktualizovat hodnoty v distribuované mezipaměti jedním voláním rozhraní API, místo toho můžou mít hodnoty resetování jejich posuvných vypršení platnosti pomocí některého z rozhraní API pro aktualizaci:
Pokud je potřeba aktualizovat skutečnou hodnotu, musíte tuto hodnotu odstranit a pak ji znovu přidat.
Pokud chcete odstranit hodnoty v distribuované mezipaměti, zavolejte jedno z rozhraní API pro odebrání:
Tip
I když existují synchronní verze výše uvedených rozhraní API, zvažte skutečnost, že implementace distribuovaných mezipamětí jsou závislé na vstupně-výstupních operacích sítě. Z tohoto důvodu je vhodnější častěji než použití asynchronních rozhraní API.
Zpětná vazba k produktu .NET
.NET je open source projekt. Vyberte odkaz pro poskytnutí zpětné vazby:
Školení
Modul
Zvýšení výkonu pomocí mezipaměti v projektu .NET Aspire - Training
V tomto modulu se dozvíte o mezipaměti v aplikaci nativní pro cloud .NET Aspire a o tom, jak je použít k optimalizaci výkonu mikroslužeb.