次の方法で共有


.NET でのキャッシュ

この記事では、さまざまなキャッシュ メカニズムについて説明します。 キャッシュは、中間レイヤーにデータを格納し、後続のデータ取得を高速化する動作です。 概念的には、キャッシュはパフォーマンス最適化戦略と設計上の考慮事項です。 キャッシュを使用すると、データの変更頻度が低い (または取得にコストがかかる) ため、アプリのパフォーマンスが大幅に向上します。 この記事では、次の 3 つのキャッシュ方法を紹介し、それぞれのサンプル ソース コードを提供します。

Von Bedeutung

.NET には 2 つの MemoryCache クラスがあり、1 つは System.Runtime.Caching 名前空間に、もう 1 つは Microsoft.Extensions.Caching 名前空間にあります。

この記事ではキャッシュに焦点を当てていますが、 System.Runtime.Caching NuGet パッケージは含まれていません。 MemoryCacheへのすべての参照は、Microsoft.Extensions.Caching名前空間内にあります。

すべての Microsoft.Extensions.* パッケージには、依存関係の挿入 (DI) が用意されています。 IMemoryCacheHybridCache、およびIDistributedCacheインターフェイスをサービスとして使用できます。

メモリ内キャッシュ

このセクションでは、 Microsoft.Extensions.Caching.Memory パッケージについて説明します。 IMemoryCacheの現在の実装は、機能豊富な API を公開する、ConcurrentDictionary<TKey,TValue>のラッパーです。 キャッシュ内のエントリは ICacheEntry によって表され、任意の objectにすることができます。 メモリ内キャッシュ ソリューションは、キャッシュされたデータがアプリのプロセスでメモリを借りる単一のサーバー上で実行されるアプリに適しています。

ヒント

マルチサーバー キャッシュのシナリオでは、メモリ内キャッシュの代わりに 分散キャッシュ アプローチを検討してください。

メモリ内キャッシュ API

キャッシュのコンシューマーは、スライディング有効期限と絶対有効期限の両方を制御できます。

有効期限を設定すると、有効期限内にアクセスされていない場合、キャッシュ内のエントリは 削除 されます。 コンシューマーには、 MemoryCacheEntryOptionsを使用してキャッシュ エントリを制御するための追加のオプションがあります。 各 ICacheEntry は、 MemoryCacheEntryOptionsとペアになっています。 IChangeTokenによる有効期限の削除機能、 CacheItemPriorityを使用した優先度設定、および ICacheEntry.Sizeの制御が公開されます。 関連する拡張メソッドは次のとおりです。

メモリ内キャッシュの例

既定の 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 に異なる方法でアクセスする場合があります。 このサンプルでは、IServiceProviderhost インスタンスを使用し、ジェネリック 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 アクセス修飾子は、Program.cs ファイル内で定義され、Program.cs ファイルからのみアクセスされるため、AlphabetLetter型で使用されます。 詳細については、 ファイル (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が渡されます。
  • すべての文字が処理されると、空白行がコンソールに書き込まれます。

キャッシュに項目を追加するには、 Createまたは Set 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;

前述の C# コードでは:

  • 変数addLettersToCacheTaskIterateAlphabetAsyncに委任され、その結果を待機します。
  • Func<char, Task> asyncFuncはラムダと検討されます。
  • MemoryCacheEntryOptionsは、現在に対する絶対有効期限でインスタンス化されます。
  • エヴィクション後のコールバックが登録されます。
  • AlphabetLetter オブジェクトがインスタンス化され、Setletterと共に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;

cacheletter キーが含まれており、valueがコンソールに書き込まれるAlphabetLetterのインスタンスである場合。 letter キーがキャッシュにない場合は、削除され、削除後のコールバックが呼び出されました。

その他の拡張メソッド

IMemoryCacheには、非同期GetOrCreateAsyncなど、便利なベースの拡張メソッドが多数用意されています。

すべてをまとめる

サンプル アプリのソース コード全体は最上位レベルのプログラムであり、次の 2 つの 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.";
}

MillisecondsDelayAfterAddMillisecondsAbsoluteExpirationの値を調整して、キャッシュされたエントリの有効期限と削除に対する動作の変化を観察できます。 このコードを実行した場合の出力例を次に示します。 (.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) が設定されているため、キャッシュされたすべての項目は最終的に削除されます。

Worker サービスのキャッシュ

データをキャッシュするための一般的な方法の 1 つは、使用するデータ サービスとは別にキャッシュを更新することです。 ワーカー サービス テンプレートは、BackgroundServiceが他のアプリケーション コードとは別に (またはバックグラウンドで) 実行されるため、優れた例です。 IHostedServiceの実装をホストするアプリケーションの実行を開始すると、対応する実装 (この場合は BackgroundService または "worker") が同じプロセスで実行を開始します。 これらのホステッド サービスは、 AddHostedService<THostedService>(IServiceCollection) 拡張メソッドを使用して、シングルトンとして DI に登録されます。 その他のサービスは、任意の サービス有効期間で DI に登録できます。

Von Bedeutung

サービスの有効期間を理解しておくことが重要です。 AddMemoryCacheを呼び出してすべてのメモリ内キャッシュ サービスを登録すると、サービスはシングルトンとして登録されます。

フォト サービスのシナリオ

HTTP 経由でアクセス可能なサード パーティ製 API に依存するフォト サービスを開発しているとします。 この写真データは頻繁に変更されませんが、その多くがあります。 各写真は単純な recordで表されます。

namespace CachingExamples.Memory;

public readonly record struct Photo(
    int AlbumId,
    int Id,
    string Title,
    string Url,
    string ThumbnailUrl);

次の例では、いくつかのサービスが DI に登録されています。 各サービスには 1 つの責任があります。

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# コードでは:

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# コードでは:

  • コンストラクターには、 IMemoryCacheCacheSignal<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# コードでは:

  • コンストラクターには、 ILoggerHttpClient、および IMemoryCacheが必要です。
  • _updateIntervalは3時間のために定義されています。
  • ExecuteAsyncメソッド:
    • アプリの実行中にループします。
    • "https://jsonplaceholder.typicode.com/photos"への HTTP 要求を行い、応答を Photo オブジェクトの配列としてマップします。
    • 写真の配列は、IMemoryCache キーの下の"Photos"に配置されます。
    • _cacheSignal.Release()が呼び出され、シグナルを待機していたコンシューマーが解放されます。
    • 更新間隔を指定すると、 Task.Delay の呼び出しが待機されます。
    • 3 時間遅延した後、キャッシュは再び更新されます。

同じプロセスのコンシューマーは写真の IMemoryCache を要求できますが、 CacheWorker はキャッシュの更新を担当します。

ハイブリッド キャッシュ

HybridCache ライブラリは、メモリ内キャッシュと分散キャッシュの利点を組み合わせ、既存のキャッシュ API で一般的な課題に対処します。 .NET 9 で導入された HybridCache は、キャッシュの実装を簡略化し、スタンプ保護や構成可能なシリアル化などの組み込み機能を含む統合 API を提供します。

主要な機能

HybridCache には、 IMemoryCacheIDistributedCache を個別に使用するよりもいくつかの利点があります。

  • 2 レベルのキャッシュ: インメモリ (L1) キャッシュ レイヤーと分散 (L2) キャッシュ レイヤーの両方を自動的に管理します。 データは、最初にメモリ内キャッシュから高速で取得され、次に必要に応じて分散キャッシュから取得され、最後にソースから取得されます。
  • スタンプ保護: 複数の同時要求で同じコストの高い操作が実行されないようにします。 1 つの要求だけがデータをフェッチし、他の要求は結果を待機します。
  • 構成可能なシリアル化: JSON (既定)、protobuf、XML を含む複数のシリアル化形式をサポートします。
  • タグベースの無効化: 効率的なバッチ無効化のために、関連するキャッシュ エントリをタグでグループ化します。
  • 簡略化された API: GetOrCreateAsync メソッドは、キャッシュ ミス、シリアル化、ストレージを自動的に処理します。

HybridCache を使用するタイミング

次の場合は、 HybridCache の使用を検討してください。

  • マルチサーバー環境では、ローカル (メモリ内) と分散キャッシュの両方が必要です。
  • キャッシュスタンピードのシナリオに対する保護が必要です。
  • IMemoryCacheIDistributedCacheを手動で調整するよりも、簡略化された API を使用することをお望みです。
  • 関連エントリのタグベースのキャッシュ無効化が必要です。

ヒント

単純なキャッシュニーズを持つ単一サーバー アプリケーションの場合は、 メモリ内キャッシュで 十分な場合があります。 スタンプ保護やタグベースの無効化を必要としないマルチサーバー アプリケーションの場合は、 分散キャッシュを検討してください。

HybridCache のセットアップ

HybridCacheを使用するには、Microsoft.Extensions.Caching.Hybrid NuGet パッケージをインストールします。

dotnet add package Microsoft.Extensions.Caching.Hybrid

HybridCacheを呼び出して、AddHybridCache サービスを DI に登録します。

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 メソッドは、一意のキーとファクトリ メソッドを受け取ります。
  • データがキャッシュにない場合は、ファクトリ メソッドが呼び出されて取得されます。
  • データは、メモリ内キャッシュと分散キャッシュの両方に自動的に格納されます。
  • ファクトリ メソッドを実行する同時実行要求は 1 つだけです。他のユーザーは結果を待ちます。

入力オプション

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
    );
}

エントリ オプションを使用すると、次の構成を行うことができます。

タグベースの無効化

タグを使用すると、関連するキャッシュ エントリをグループ化し、それらを一緒に無効にできます。 これは、関連するデータを 1 つの単位として更新する必要があるシナリオに役立ちます。

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 シリアル化が必要です。 既定では、 stringbyte[] を内部的に処理し、他の型に 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 は、分散 (L2) キャッシュに構成された IDistributedCache 実装を使用します。 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)
    };
});

分散キャッシュの実装の詳細については、「 分散キャッシュ」を参照してください。

分散キャッシュ

一部のシナリオでは、分散キャッシュが必要です。複数のアプリ サーバーの場合などです。 分散キャッシュでは、メモリ内キャッシュアプローチよりも高いスケールアウトがサポートされます。 分散キャッシュを使用すると、キャッシュ メモリが外部プロセスにオフロードされますが、追加のネットワーク I/O が必要になり、(わずかな場合でも) 少し待ち時間が長くなります。

分散キャッシュの抽象化は、 Microsoft.Extensions.Caching.Memory NuGet パッケージの一部であり、 AddDistributedMemoryCache 拡張メソッドもあります。

注意事項

AddDistributedMemoryCache は、開発またはテストのシナリオでのみ使用する必要があり、実行可能な運用環境の実装 ではありません

次のパッケージから IDistributedCache の使用可能な実装を検討してください。

分散キャッシュ API

分散キャッシュ API は、メモリ内キャッシュ API に対応する API よりも少しプリミティブです。 キーと値のペアは、もう少し基本的なものです。 メモリ内キャッシュ キーは objectに基づいていますが、分散キーは stringです。 メモリ内キャッシュでは、値は厳密に型指定された任意のジェネリックにすることができますが、分散キャッシュの値は byte[]として保持されます。 さまざまな実装が厳密に型指定されたジェネリック値を公開しているわけではありませんが、これは実装の詳細の問題です。

値を作成する

分散キャッシュに値を作成するには、次のいずれかのセット API を呼び出します。

メモリ内キャッシュの例の AlphabetLetter レコードを使用して、オブジェクトを JSON にシリアル化し、 stringbyte[]としてエンコードできます。

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のデコードを回避するのに役立ちます。

値を更新する

分散キャッシュ内の値を 1 つの API 呼び出しで更新する方法はありません。 代わりに、次のいずれかの更新 API を使用して、値のスライディング有効期限をリセットできます。

実際の値を更新する必要がある場合は、値を削除してから、再度追加する必要があります。

値を削除する

分散キャッシュ内の値を削除するには、 Remove API のいずれかを呼び出します。

ヒント

これらの API には同期バージョンがありますが、分散キャッシュの実装はネットワーク I/O に依存するという事実を考慮してください。 このため、通常は非同期 API を使用することをお勧めしています。

こちらも参照ください