この記事では、ASP.NET Core アプリで HybridCache
ライブラリを構成して使う方法について説明します。 このライブラリの概要については、キャッシュの概要についての記事の HybridCache
に関するセクションをご覧ください。
ライブラリの入手
Microsoft.Extensions.Caching.Hybrid
パッケージをインストールします。
dotnet add package Microsoft.Extensions.Caching.Hybrid
サービスを登録する
HybridCache
を呼び出して、 サービスを AddHybridCache コンテナーに追加します。
// Add services to the container.
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddAuthorization();
builder.Services.AddHybridCache();
上記のコードは、HybridCache
サービスを既定のオプションで登録します。 登録 API では、オプションとシリアル化を構成することもできます。
キャッシュ エントリを取得して格納する
HybridCache
サービスは、キーと次の要素を受け取る 2 つのオーバーロードを持つ GetOrCreateAsync メソッドを提供します。
- ファクトリメソッド。
- 状態、およびファクトリ メソッド。
このメソッドは、キーを使って、プライマリ キャッシュからオブジェクトの取得を試みます。 アイテムがプライマリ キャッシュ内に見つからない場合 (キャッシュ ミス)、セカンダリ キャッシュが確認されます (セカンダリが構成されている場合)。 そこにデータが見つからない場合 (別のキャッシュ ミス)、ファクトリ メソッドを呼び出してデータ ソースからオブジェクトを取得します。 その後、プライマリとセカンダリ両方のキャッシュにオブジェクトを格納します。 プライマリまたはセカンダリどちらかのキャッシュでオブジェクトが見つかった場合 (キャッシュ ヒット)、ファクトリ メソッドは呼び出されません。
HybridCache
サービスは、特定のキーに対する同時呼び出し元の内 1 つだけがファクトリ メソッドを呼び出し、他のすべての呼び出し元はその呼び出しの結果を待機することを保証します。
CancellationToken
に渡される GetOrCreateAsync
は、すべての同時呼び出し元のまとめてのキャンセルを表します。
メインの GetOrCreateAsync
オーバーロード
GetOrCreateAsync
のステートレス オーバーロードは、ほとんどのシナリオに推奨されます。 それを呼び出すコードは比較的単純です。 次に例を示します。
public class SomeService(HybridCache cache)
{
private HybridCache _cache = cache;
public async Task<string> GetSomeInfoAsync(string name, int id, CancellationToken token = default)
{
return await _cache.GetOrCreateAsync(
$"{name}-{id}", // Unique key to the cache entry
async cancel => await GetDataFromTheSourceAsync(name, id, cancel),
cancellationToken: token
);
}
public async Task<string> GetDataFromTheSourceAsync(string name, int id, CancellationToken token)
{
string someInfo = $"someinfo-{name}-{id}";
return someInfo;
}
}
キャッシュ キーのガイダンス
key
に渡される GetOrCreateAsync
は、キャッシュされているデータを一意に識別する必要があります。
- ソースからそのデータを取得するために使用される識別子の値に関して。
- アプリケーションにキャッシュされたその他のデータに関して。
通常、どちらの種類の一意性も、文字列連結を使用して、異なる部分で構成される単一のキー文字列を 1 つの文字列に連結することで保証されます。 次に例を示します。
cache.GetOrCreateAsync($"/orders/{region}/{orderId}", ...);
又は
cache.GetOrCreateAsync($"user_prefs_{userId}", ...);
キースキームが有効であり、データが混乱しないようにするのは呼び出し元の責任です。
キャッシュ キーで外部ユーザー入力を直接使用しないでください。 たとえば、キャッシュ キーとしてユーザー インターフェイスの生文字列を使用しないでください。 これにより、キャッシュにランダムキーまたは無意味なキーが殺到することによって発生する未承認のアクセスやサービス拒否攻撃などのセキュリティ リスクにアプリが公開される可能性があります。 上記の有効な例では、 注文 データと ユーザー設定 データは明確に分離され、信頼できる識別子が使用されています。
-
orderid
とuserId
は、内部で生成される識別子です。 -
region
は、既知の領域の定義済みリストの列挙型または文字列である可能性があります。
/
や_
などのトークンには重要な意味はありません。 キー値全体は、不透明な識別文字列として扱われます。 この場合は、キャッシュの機能を変更せず、/
と _
を省略できますが、あいまいさを回避するために区切り記号が通常使用されます。たとえば、$"order{customerId}{orderId}"
は次の間で混乱を引き起こす可能性があります。
-
customerId
42 とorderId
123 -
customerId
421 とorderId
23
上記の例はどちらもキャッシュ キー order42123
を生成します。
このガイダンスは、string
、HybridCache
、IDistributedCache
など、IMemoryCache
ベースのキャッシュ API にも同様に適用されます。
インライン補間文字列構文 (前の有効なキーの例$"..."
) が、GetOrCreateAsync
呼び出し内に直接含まれていることに注意してください。 この構文は、多くのシナリオでキーに HybridCache
を割り当てる必要性を回避する計画的な将来の改善が可能であるため、string
を使用する場合に推奨されます。
その他の重要な考慮事項
- キーは、有効な最大長に制限できます。 たとえば、既定の
HybridCache
実装 (AddHybridCache(...)
を使用) では、既定でキーが 1024 文字に制限されます。 この数は、HybridCacheOptions.MaximumKeyLength
を介して構成できます。長いキーでは、飽和を防ぐためにキャッシュ メカニズムがバイパスされます。 - キーは有効な Unicode シーケンスである必要があります。 無効な Unicode シーケンスが渡された場合、動作は未定義です。
-
IDistributedCache
などのアウトプロセス セカンダリ キャッシュを使用する場合、バックエンドの実装によって追加の制限が課される場合があります。 架空の例として、特定のバックエンドでは、大文字と小文字を区別しないキー ロジックが使用される場合があります。 既定のHybridCache
(AddHybridCache(...)
経由) では、混乱攻撃やエイリアス攻撃を防ぐために (ビットごとの文字列の等価性を使用して) このシナリオが検出されます。 ただし、このシナリオでは、競合するキーが想定よりも早く上書きまたは削除される可能性があります。
代替の GetOrCreateAsync
オーバーロード
代替オーバーロードを使うと、キャプチャした変数とインスタンスごとのコールバックによるオーバーヘッドが軽減される可能性がありますが、代わりにコードは複雑になります。 ほとんどのシナリオでは、パフォーマンスの向上はコードの複雑さを上回りません。 代替オーバーロードを使用する例を次に示します。
public class SomeService(HybridCache cache)
{
private HybridCache _cache = cache;
public async Task<string> GetSomeInfoAsync(string name, int id, CancellationToken token = default)
{
return await _cache.GetOrCreateAsync(
$"{name}-{id}", // Unique key to the cache entry
(name, id, obj: this),
static async (state, token) =>
await state.obj.GetDataFromTheSourceAsync(state.name, state.id, token),
cancellationToken: token
);
}
public async Task<string> GetDataFromTheSourceAsync(string name, int id, CancellationToken token)
{
string someInfo = $"someinfo-{name}-{id}";
return someInfo;
}
}
SetAsync
メソッド
多くのシナリオでは、必要な API は GetOrCreateAsync
のみです。 ただし、HybridCache
には、最初に取得を試みずにオブジェクトをキャッシュに格納する SetAsync もあります。
キーを使用してキャッシュ エントリを削除する
キャッシュ エントリの基になるデータが期限切れになる前に変化した場合は、そのエントリのキーを使用して RemoveAsync を呼び出すことでエントリを明示的に削除します。 オーバーロードを使用すると、キー値のコレクションを指定できます。
エントリが削除される際には、プライマリとセカンダリ両方のキャッシュから削除されます。
タグを使用してキャッシュ エントリを削除する
タグを使用すると、キャッシュ エントリをグループ化し、それらをまとめて無効にすることができます。
次の例に示すように、GetOrCreateAsync
を呼び出すときにタグを設定します。
public class SomeService(HybridCache cache)
{
private HybridCache _cache = cache;
public async Task<string> GetSomeInfoAsync(string name, int id, CancellationToken token = default)
{
var tags = new List<string> { "tag1", "tag2", "tag3" };
var entryOptions = new HybridCacheEntryOptions
{
Expiration = TimeSpan.FromMinutes(1),
LocalCacheExpiration = TimeSpan.FromMinutes(1)
};
return await _cache.GetOrCreateAsync(
$"{name}-{id}", // Unique key to the cache entry
async cancel => await GetDataFromTheSourceAsync(name, id, cancel),
entryOptions,
tags,
cancellationToken: token
);
}
public async Task<string> GetDataFromTheSourceAsync(string name, int id, CancellationToken token)
{
string someInfo = $"someinfo-{name}-{id}";
return someInfo;
}
}
タグ値を使用して RemoveByTagAsync を呼び出すことで、指定したタグのすべてのエントリを削除します。 オーバーロードを使用すると、タグ値のコレクションを指定できます。
IMemoryCache
もIDistributedCache
もタグの概念を直接サポートしていないので、タグベースの無効化は論理操作のみです。 ローカル キャッシュまたは分散キャッシュから値がアクティブに削除されることはありません。 代わりに、このようなタグを持つデータを受信するときに、データがローカル キャッシュとリモート キャッシュの両方からキャッシュ ミスとして扱われるようにします。 値の有効期限は、構成された有効期間に基づいて通常通り IMemoryCache
と IDistributedCache
から切れます。
すべてのキャッシュ エントリの削除
アスタリスク タグ (*
) はワイルドカードとして予約されており、個々の値に対して許可されません。
RemoveByTagAsync("*")
を呼び出すと、タグのないデータであっても、すべてのHybridCache
データが無効になります。 個々のタグと同様に、これは 論理的 な操作であり、個々の値は自然に期限切れになるまで存在し続けます。 Glob スタイルの一致はサポートされていません。 たとえば、 RemoveByTagAsync("foo*")
を使用して、 foo
以降のすべてを削除することはできません。
タグに関するその他の考慮事項
- 使用できるタグの数はシステムによって制限されませんが、タグのセットが大きいとパフォーマンスに悪影響を与える可能性があります。
- タグは空にすることや、単なる空白にすること、あるいは予約値
*
にすることはできません。
[オプション]
AddHybridCache
メソッドを使って、グローバルな既定値を構成できます。 次の例では、使用可能なオプションの一部を構成する方法を示します。
// Add services to the container.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthorization();
builder.Services.AddHybridCache(options =>
{
options.MaximumPayloadBytes = 1024 * 1024;
options.MaximumKeyLength = 1024;
options.DefaultEntryOptions = new HybridCacheEntryOptions
{
Expiration = TimeSpan.FromMinutes(5),
LocalCacheExpiration = TimeSpan.FromMinutes(5)
};
});
GetOrCreateAsync
メソッドは、HybridCacheEntryOptions
オブジェクトを受け取って、特定のキャッシュ エントリのグローバル既定値をオーバーライドすることもできます。 次に例を示します。
public class SomeService(HybridCache cache)
{
private HybridCache _cache = cache;
public async Task<string> GetSomeInfoAsync(string name, int id, CancellationToken token = default)
{
var tags = new List<string> { "tag1", "tag2", "tag3" };
var entryOptions = new HybridCacheEntryOptions
{
Expiration = TimeSpan.FromMinutes(1),
LocalCacheExpiration = TimeSpan.FromMinutes(1)
};
return await _cache.GetOrCreateAsync(
$"{name}-{id}", // Unique key to the cache entry
async cancel => await GetDataFromTheSourceAsync(name, id, cancel),
entryOptions,
tags,
cancellationToken: token
);
}
public async Task<string> GetDataFromTheSourceAsync(string name, int id, CancellationToken token)
{
string someInfo = $"someinfo-{name}-{id}";
return someInfo;
}
}
オプションについて詳しくは、ソース コードを参照してください。
- HybridCacheOptions クラス。
- HybridCacheEntryOptions クラス。
制限
HybridCacheOptions
の次のプロパティを使うと、すべてのキャッシュ エントリに適用される制限を構成できます。
- MaximumPayloadBytes: キャッシュ エントリの最大サイズ。 既定値は 1 MB です。 このサイズを超えて値を保存しようという試みはログされ、その値はキャッシュに保存されません。
- MaximumKeyLength: キャッシュ キーの最大長。 既定値は 1,024 文字です。 このサイズを超えて値を保存しようという試みはログされ、その値はキャッシュに保存されません。
シリアル化
セカンダリのアウトプロセス キャッシュを使用するには、シリアル化が必要です。 シリアル化は、HybridCache
サービスの登録の一環として構成されます。
AddSerializer 呼び出しからチェーンされた AddSerializerFactory および AddHybridCache
メソッドを介して、型固有シリアライザーと汎用シリアライザーを構成できます。 既定では、このライブラリは string
と byte[]
を内部的に処理し、その他すべてに System.Text.Json
を使用します。
HybridCache
は、protobuf や XML など、他の種類のシリアライザーを使用することもできます。
次の例では、型固有の protobuf シリアライザーを使用するようにサービスを構成します。
// Add services to the container.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthorization();
builder.Services.AddHybridCache(options =>
{
options.DefaultEntryOptions = new HybridCacheEntryOptions
{
Expiration = TimeSpan.FromSeconds(10),
LocalCacheExpiration = TimeSpan.FromSeconds(5)
};
}).AddSerializer<SomeProtobufMessage,
GoogleProtobufSerializer<SomeProtobufMessage>>();
次の例では、多くの protobuf 型を処理できる汎用 protobuf シリアライザーを使用するようにサービスを構成します。
// Add services to the container.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthorization();
builder.Services.AddHybridCache(options =>
{
options.DefaultEntryOptions = new HybridCacheEntryOptions
{
Expiration = TimeSpan.FromSeconds(10),
LocalCacheExpiration = TimeSpan.FromSeconds(5)
};
}).AddSerializerFactory<GoogleProtobufSerializerFactory>();
セカンダリ キャッシュには、Redis や SqlServer などのデータ ストアが必要です。 たとえば、Azure Cache for Redis を使用するには次の操作を行います。
Microsoft.Extensions.Caching.StackExchangeRedis
パッケージをインストールします。Azure Cache for Redis のインスタンスを作成します。
Redis インスタンスに接続するための接続文字列を取得します。 Azure portal の [概要] ページで [アクセス キーの表示] を選択して、接続文字列を見つけます。
接続文字列をアプリの構成に格納します。 たとえば、接続文字列を含むセクションがある、次のような JSON のユーザーシークレットファイルを使用します。
<the connection string>
を実際の接続文字列に置き換えます。{ "ConnectionStrings": { "RedisConnectionString": "<the connection string>" } }
DI に、Redis パッケージで提供される
IDistributedCache
実装を登録します。 そのためには、AddStackExchangeRedisCache
を呼び出し、接続文字列を渡します。 次に例を示します。builder.Services.AddStackExchangeRedisCache(options => { options.Configuration = builder.Configuration.GetConnectionString("RedisConnectionString"); });
これで、Redis
IDistributedCache
実装をアプリの DI コンテナーから使用できるようになりました。HybridCache
は、その実装をセカンダリ キャッシュとして使用し、それ用に構成されたシリアライザーを使用します。
詳細については、HybridCache シリアル化サンプル アプリを参照してください。
キャッシュ ストレージ
既定では、HybridCache
はプライマリ キャッシュ ストレージに MemoryCache を使います。 キャッシュ エントリはプロセス内で保存されるため、サーバーごとに、サーバー プロセスが再起動されるたびに失われる個別のキャッシュがあります。 Redis や SQL Server などのセカンダリ プロセス外ストレージの場合、HybridCache
は、IDistributedCache
の実装が構成されている場合はそれを使います。 ただし、IDistributedCache
実装がなくても、HybridCache
サービスはインプロセス キャッシュと スタンピード保護を提供します。
メモ
キーまたはタグによってキャッシュ エントリを無効にすると、現在のサーバーとセカンダリアウトプロセス ストレージで無効になります。 ただし、他のサーバーのメモリ内キャッシュは影響を受けません。
パフォーマンスを最適化する
パフォーマンスを最適化するには、オブジェクトを再利用し、HybridCache
の割り当てが行われないように、byte[]
を構成します。
オブジェクトを再利用する
インスタンスを再利用することで、HybridCache
は、呼び出しごとの逆シリアル化に関連する CPU とオブジェクトの割り当てのオーバーヘッドを削減できます。 この結果、キャッシュされたオブジェクトが大きいか頻繁にアクセスされるシナリオでパフォーマンスが向上する可能性があります。
IDistributedCache
を使用する一般的な既存のコードでは、キャッシュからオブジェクトを取得するたびに、逆シリアル化が行われます。 この動作は、同時呼び出し元のそれぞれがオブジェクトの個別のインスタンスを取得し、それらは他のインスタンスとやり取りを行うことができないことを意味します。 この結果、同じオブジェクト インスタンスに同時に変更を加えるリスクがなくなるため、スレッド セーフになります。
HybridCache
の使用の多くは既存の IDistributedCache
コードを作り変えたものになるため、同時実行によるバグの発生を防ぐために HybridCache
は既定でこの動作を引き継ぎます。 ただし、次の場合、オブジェクトは本質的にスレッドセーフです。
- これらは不変型です。
- コードでそれを変更しない。
このような場合は、次のいずれかの変更を行うことでインスタンスを再利用しても安全であることを HybridCache
に通知します。
- 型を
sealed
としてマークします。 C# のsealed
キーワードは、クラスを継承できないことを意味します。 - その型に
[ImmutableObject(true)]
属性を追加します。[ImmutableObject(true)]
属性は、オブジェクトの作成後にその状態を変更できないことを示します。
byte[]
割り当てを回避する
HybridCache
には、IDistributedCache
の割り当てを回避するため、byte[]
の実装に対するオプションの API も用意されています。 この機能は、Microsoft.Extensions.Caching.StackExchangeRedis
および Microsoft.Extensions.Caching.SqlServer
パッケージのプレビュー バージョンによって実装されます。 詳細については、IBufferDistributedCacheを参照してください。
パッケージをインストールするための .NET CLI コマンドを次に示します。
dotnet add package Microsoft.Extensions.Caching.StackExchangeRedis
dotnet add package Microsoft.Extensions.Caching.SqlServer
カスタム HybridCache の実装
HybridCache
抽象クラスの具象実装は、共有フレームワークに含まれており、依存関係の挿入によって提供されます。 ただし、開発者は、 FusionCache などの API のカスタム実装を提供または使用できます。
ネイティブ AOT でハイブリッド キャッシュを使用する
HybridCache
には、次のネイティブ AOT 固有の考慮事項が適用されます。
シリアル化
ネイティブ AOT では、ランタイムリフレクションベースのシリアル化はサポートされていません。 カスタム型をキャッシュする場合は、ソース ジェネレーターを使用するか、AOT と互換性のあるシリアライザーを明示的に構成する必要があります (
System.Text.Json
ソース生成など)。HybridCache
はまだ開発中であり、AOT で使用する方法を簡素化することは、その開発にとって最優先事項です。 詳細については、「pull request dotnet/extensions#6475」を参照してください。トリミング
キャッシュするすべての型が、AOT コンパイラによってトリミングされないように参照されていることを確認します。 シリアル化にソース ジェネレーターを使用すると、この要件に役立ちます。 詳細については、ASP.NET Core のネイティブ AOT サポートを参照してください。
シリアル化とトリミングを正しく設定した場合、 HybridCache
は通常の ASP.NET Core アプリと同じようにネイティブ AOT で動作します。
互換性
HybridCache
ライブラリでは、.NET Framework 4.7.2 および .NET Standard 2.0 までの古い .NET ランタイムがサポートされています。
その他のリソース
詳細については、HybridCache
ソース コードを参照してください。
ASP.NET Core