ASP.NET Core 中的 HybridCache 库
重要
HybridCache
目前仍处于预览状态,但在 .NET 扩展的未来次要版本中,将在 .NET 9.0 之后完全发布。
本文介绍如何在 ASP.NET Core 应用中配置和使用 HybridCache
库。 有关库的简介,请参阅“缓存概述”的 HybridCache
部分。
获取该库
安装 Microsoft.Extensions.Caching.Hybrid
包。
dotnet add package Microsoft.Extensions.Caching.Hybrid --version "9.0.0-preview.7.24406.2"
注册服务
通过调用 AddHybridCache
,将 HybridCache
服务添加到依赖项注入 (DI) 容器:
// Add services to the container.
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddAuthorization();
builder.Services.AddHybridCache();
前面的代码使用默认选项注册 HybridCache
服务。 注册 API 还可以配置选项和序列化。
获取和存储缓存条目
HybridCache
服务提供一个带有两个重载的 GetOrCreateAsync
方法,接受一个密钥和:
- 工厂方法。
- 状态和工厂方法。
该方法使用密钥尝试从主缓存中检索对象。 如果在主缓存中找不到该项(缓存未命中),则它会检查辅助缓存(如果已配置)。 如果在此处找不到数据(另一个缓存未命中),它会调用工厂方法来从数据源获取对象。 然后,它将对象存储在主缓存和辅助缓存中。 如果在主缓存或辅助缓存中找到对象(缓存命中),则永远不会调用工厂方法。
HybridCache
服务确保只有一个给定键的并发调用者调用工厂方法,所有其他调用者都等待该调用的结果。 传递给 GetOrCreateAsync
的 CancellationToken
表示组合取消所有并发调用者。
主要 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;
}
}
替代的 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
方法
在许多情况下,GetOrCreateAsync
是唯一需要的 API。 但是,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
移除指定标记的所有条目。 使用重载可以指定标记值的集合。
移除某个条目时,会从主缓存和辅助缓存中移除该条目。
选项
可使用 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
的下列属性,可配置适用于所有缓存条目的限制:
- MaximumPayloadBytes - 缓存条目的最大大小。 默认值为 1 MB。 存储超过此大小的值的尝试将被记录,并且该值不会存储在缓存中。
- MaximumKeyLength - 缓存键的最大长度。 默认值为 1024 个字符。 存储超过此大小的值的尝试将被记录,并且该值不会存储在缓存中。
序列化
使用辅助、进程外缓存需要序列化。 序列化配置为注册 HybridCache
服务的一部分。 可以通过从 AddHybridCache
调用链接的 AddSerializer
和 AddSerializerFactory
方法配置特定于类型和常规用途的序列化程序。 默认情况下,库在内部处理 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 Redis 缓存,例如:
安装
Microsoft.Extensions.Caching.StackExchangeRedis
包。创建 Azure Cache for Redis 的实例。
获取连接到 Redis 实例的连接字符串。 通过在 Azure 门户中概述页上选择“显示访问密钥”来查找连接字符串。
将连接字符串存储在应用的配置中。 例如,使用类似于以下 JSON 的用户机密文件,并在
ConnectionStrings
节中使用连接字符串。 将<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
的典型现有代码中,每次从缓存中检索对象都会导致反序列化。 此行为意味着每个并发调用方都获取一个单独的对象实例,该实例无法与其他实例交互。 这实现了线程安全性,因为并发修改同一对象实例没有风险。
将根据现有 IDistributedCache
代码调整对 HybridCache
的大量使用,因此 HybridCache
在默认情况下会保留此行为,以避免引入并发 bug。 但是,如果出现以下情况,对象本质上是线程安全的:
- 这些类型是不可变的。
- 代码不会修改它们。
在这种情况下,请通知 HybridCache
,使其知道通过以下方法重用实例很安全:
- 将类型标记为
sealed
。 C# 中的sealed
关键字表示类无法被继承。 - 将
[ImmutableObject(true)]
属性应用于类型。[ImmutableObject(true)]
属性指示创建对象后无法更改该对象的状态。
避免 byte[]
分配
HybridCache
还提供用于 IDistributedCache
实现的可选 API,以避免 byte[]
分配。 此功能由 Microsoft.Extensions.Caching.StackExchangeRedis
和 Microsoft.Extensions.Caching.SqlServer
包的预览版本实现。 有关详细信息,请参阅 IBufferDistributedCache。下面是用于安装包的 .NET CLI 命令:
dotnet add package Microsoft.Extensions.Caching.StackExchangeRedis --prerelease
dotnet add package Microsoft.Extensions.Caching.SqlServer --prerelease
自定义 HybridCache 实现
HybridCache
抽象类的具体实现包含在共享框架中,并通过依赖项注入提供。 不过,欢迎开发人员提供 API 的自定义实现。
兼容性
HybridCache
库支持较旧的 .NET 运行时,最低支持 .NET Framework 4.7.2 和 .NET Standard 2.0。
其他资源
有关 HybridCache
的详细信息,请参阅下列资源:
- GitHub 问题 dotnet/aspnetcore #54647。
HybridCache
源代码