缓存是显著提高应用程序性能的最简单技术之一。这是将数据临时存储在更快的访问位置的过程。您通常会缓存成本高昂的操作的结果或频繁访问的数据。
缓存允许从缓存中提供对相同数据的后续请求,而不是从其源获取数据。
ASP.NET Core 提供了多种类型的缓存,例如 、 和即将推出的 (.NET 9)。
在本新闻稿中,我们将探讨如何在 ASP.NET Core 应用程序中实现缓存。
缓存通过减少延迟和服务器负载来提高应用程序的性能,同时增强可扩展性和用户体验。
在我最近参与的一个项目中,我们使用 Redis 扩展到超过 1,000,000 个用户。我们只有一个 SQL Server 实例,该实例带有用于报告的只读副本。缓存的力量,嗯?
ASP.NET Core 为处理缓存提供了两个主要抽象:
我们必须向 DI 注册这些服务才能使用它们。 将配置 的内存中实现,该实现不是分布式的。
builder.Services.AddMemoryCache();
builder.Services.AddDistributedMemoryCache();
以下是如何使用 .我们将首先检查缓存的值是否存在,如果存在,则直接返回它。否则,我们必须从数据库中获取值并将其缓存以供后续请求使用。IMemoryCache
app.MapGet(
"products/{id}",
(int id, IMemoryCache cache, AppDbContext context) =>
{
if (!cache.TryGetValue(id, out Product product))
{
product = context.Products.Find(id);
var cacheEntryOptions = new MemoryCacheEntryOptions()
.SetAbsoluteExpiration(TimeSpan.FromMinutes(10))
.SetSlidingExpiration(TimeSpan.FromMinutes(2));
cache.Set(id, product, cacheEntryOptions);
}
return Results.Ok(product);
});
缓存过期是另一个要讨论的重要话题。我们希望删除未使用并过时的缓存条目。您可以传入 ,从而配置缓存过期时间。例如,我们可以设置 and 值来控制缓存条目何时过期。MemoryCacheEntryOptionsAbsoluteExpirationSlidingExpiration
缓存端模式是最常见的缓存策略。其工作原理如下:
以下是如何实现缓存端模式作为扩展方法的方法:IDistributedCache
public static class DistributedCacheExtensions
{
public static DistributedCacheEntryOptions DefaultExpiration => new()
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(2)
};
public static async Task<T> GetOrCreateAsync<T>(
this IDistributedCache cache,
string key,
Func<Task<T>> factory,
DistributedCacheEntryOptions? cacheOptions = null)
{
var cachedData = await cache.GetStringAsync(key);
if (cachedData is not null)
{
return JsonSerializer.Deserialize<T>(cachedData);
}
var data = await factory();
await cache.SetStringAsync(
key,
JsonSerializer.Serialize(data),
cacheOptions ?? DefaultExpiration);
return data;
}
}
我们用于管理到 JSON 字符串和从 JSON 字符串的序列化。该方法还接受一个参数来控制缓存过期。JsonSerializerSetStringAsyncDistributedCacheEntryOptions
以下是我们如何使用此扩展方法:
app.MapGet(
"products/{id}",
(int id, IDistributedCache cache, AppDbContext context) =>
{
var product = cache.GetOrCreateAsync($"products-{id}", async () =>
{
var productFromDb = await context.Products.FindAsync(id);
return productFromDb;
});
return Results.Ok(product);
});
优点:
缺点:
Redis 是一种流行的内存数据存储,通常用作高性能分布式缓存。要在 ASP.NET Core 应用程序中使用 Redis,您可以使用该库。StackExchange.Redis
但是,还有库,允许您将 Redis 与 集成。Microsoft.Extensions.Caching.StackExchangeRedisIDistributedCache
Install-Package Microsoft.Extensions.Caching.StackExchangeRedis
以下是通过向 Redis 提供连接字符串来使用 DI 配置它的方法:
string connectionString = builder.Configuration.GetConnectionString("Redis");
builder.Services.AddStackExchangeRedisCache(options =>
{
options.Configuration = connectionString;
});
另一种方法是将 注册为服务。然后,我们将使用它来为 提供一个函数。IConnectionMultiplexerConnectionMultiplexerFactory
string connectionString = builder.Configuration.GetConnectionString("Redis");
IConnectionMultiplexer connectionMultiplexer =
ConnectionMultiplexer.Connect(connectionString);
builder.Services.AddSingleton(connectionMultiplexer);
builder.Services.AddStackExchangeRedisCache(options =>
{
options.ConnectionMultiplexerFactory =
() => Task.FromResult(connectionMultiplexer);
});
现在,当您注入时,它将在引擎盖下使用 Redis。IDistributedCache
ASP.NET Core 中的内存中缓存实现容易受到争用条件的影响,这可能会导致缓存踩踏。当并发请求遇到缓存未命中并尝试从源获取数据时,会发生缓存踩踏。这可能会使应用程序过载,并抵消缓存的好处。
锁定是解决缓存踩踏问题的一种解决方案。.NET 为锁定和并发控制提供了许多选项。最常用的锁定原语是语句和 (or ) 类。lockSemaphoreSemaphoreSlim
以下是我们在获取数据之前引入锁定的方法:SemaphoreSlim
public static class DistributedCacheExtensions
{
private static readonly SemaphoreSlim Semaphore = new SemaphoreSlim(1, 1);
// Arguments omitted for brevity
public static async Task<T> GetOrCreateAsync<T>(...)
{
// Fetch data from cache, and return if present
// Cache miss
try
{
await Semaphore.WaitAsync();
var data = await factory();
await cache.SetStringAsync(
key,
JsonSerializer.Serialize(data),
cacheOptions ?? DefaultExpiration);
}
finally
{
Semaphore.Release();
}
return data;
}
}
前面的实现存在锁争用问题,因为所有请求都必须等待信号量。一个更好的解决方案是基于值的锁定。key
.NET 9 引入了一种新的缓存抽象,称为 ,旨在解决 的缺点。有关详细信息,请参阅混合缓存文档。HybridCacheIDistributedCache
缓存是一种用于提高 Web 应用程序性能的强大技术。ASP.NET Core 的缓存抽象使实现各种缓存策略变得容易。