ASP.NET Core 中的缓存:提高应用程序性能

作者:微信公众号:【架构师老卢】
7-12 20:54
38

概述:缓存是显著提高应用程序性能的最简单技术之一。这是将数据临时存储在更快的访问位置的过程。您通常会缓存成本高昂的操作的结果或频繁访问的数据。缓存允许从缓存中提供对相同数据的后续请求,而不是从其源获取数据。ASP.NET Core 提供了多种类型的缓存,例如 、 和即将推出的 (.NET 9)。在本新闻稿中,我们将探讨如何在 ASP.NET Core 应用程序中实现缓存。缓存如何提高应用程序性能缓存通过减少延迟和服务器负载来提高应用程序的性能,同时增强可扩展性和用户体验。更快的数据检索:与从源(如数据库或 API)检索缓存数据相比,访问缓存数据的速度要快得多。缓存通常存储在内存 (RAM) 中。减少

缓存是显著提高应用程序性能的最简单技术之一。这是将数据临时存储在更快的访问位置的过程。您通常会缓存成本高昂的操作的结果或频繁访问的数据。

缓存允许从缓存中提供对相同数据的后续请求,而不是从其源获取数据。

ASP.NET Core 提供了多种类型的缓存,例如 、 和即将推出的 (.NET 9)。

在本新闻稿中,我们将探讨如何在 ASP.NET Core 应用程序中实现缓存。

缓存如何提高应用程序性能

缓存通过减少延迟和服务器负载来提高应用程序的性能,同时增强可扩展性和用户体验。

  • 更快的数据检索:与从源(如数据库或 API)检索缓存数据相比,访问缓存数据的速度要快得多。缓存通常存储在内存 (RAM) 中。
  • 减少数据库查询:缓存经常访问的数据可以减少数据库查询的数量。这样可以减少数据库服务器上的负载。
  • 降低 CPU 使用率:渲染网页或处理 API 响应可能会消耗大量 CPU 资源。缓存结果可以减少对重复性 CPU 密集型任务的需求。
  • 处理增加的流量:通过减少后端系统的负载,缓存使您的应用程序能够处理更多的并发用户和请求。
  • 分布式缓存Redis 等分布式缓存解决方案支持跨多个服务器扩展缓存,从而进一步提高性能和弹性。

在我最近参与的一个项目中,我们使用 Redis 扩展到超过 1,000,000 个用户。我们只有一个 SQL Server 实例,该实例带有用于报告的只读副本。缓存的力量,嗯?

在 ASP.NET Core 中缓存抽象

ASP.NET Core 为处理缓存提供了两个主要抽象:

  • IMemoryCache:将数据存储在 Web 服务器的内存中。简单易用,但不适用于分布式场景。
  • IDistributedCache:为分布式应用程序提供更强大的解决方案。它允许您将缓存数据存储在像 Redis 这样的分布式缓存中。

我们必须向 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

缓存端模式

缓存端模式是最常见的缓存策略。其工作原理如下:

  1. 检查缓存:在缓存中查找请求的数据。
  2. **从源获取(如果缓存未命中):**如果数据不在缓存中,请从源获取。
  3. 更新缓存:将获取的数据存储在缓存中,以供后续请求使用。

以下是如何实现缓存端模式作为扩展方法的方法: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);  
    });

内存中缓存的优点和缺点

优点:

  • 极快
  • 易于实施
  • 无外部依赖性

缺点:

  • 如果服务器重新启动,缓存数据将丢失
  • 仅限于单个服务器的内存 (RAM)
  • 缓存数据不会在应用程序的多个实例之间共享

使用 Redis 的分布式缓存

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

Cache Stampede 和 HybridCache

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 的缓存抽象使实现各种缓存策略变得容易。

阅读排行