C# .NET 8 — 创建具有分布式缓存的缓存服务

d8514d0d34365356595680ebc83077fb.jpeg介绍

加速应用程序的一种常见方法是引入缓存。通常,首先想到的选项是使用 MemoryCache (RAM) 来保存一些数据,以加快检索速度。

此方法适用于单体式应用程序。但是,在微服务解决方案中,每个服务都可以独立扩展,使用本地缓存可能会破坏微服务架构模式的无状态规则。

因此,更好的解决方案是使用分布式缓存。在 .NET 中,与分布式缓存的常见交互是通过称为 的接口。

分布式缓存

在 .NET 应用程序中工作时,可以选择要使用的实现。

.NET 8 中的可用实现包括:

  • Memory Cache

  • Redis

  • SQL Server

  • NCache

  • Azure CosmosDB

最常用的实现之一是 Redis。在本文的后续部分中,我将使用 Redis 进行实现。

有关详细信息,请参阅官方文档:

ASP.NET Core 中的分布式缓存

了解如何使用 ASP.NET Core 分布式缓存来提高应用性能和可扩展性,尤其是在云或...

learn.microsoft.com

重要参数

使用分布式缓存时,需要了解两个重要参数:

  • 滑动到期: 在从缓存中逐出缓存条目之前必须访问缓存条目的时间跨度。

  • 绝对到期时间: 逐出缓存条目的时间点。默认情况下,缓存中保留的条目不会过期。

有关详细信息,请参阅官方文档:

CacheItemPolicy.AbsoluteExpiration 属性 (System.Runtime.Caching)

获取或设置一个值,该值指示是否应在指定时间点逐出缓存项。

learn.microsoft.com

CacheItemPolicy.SlidingExpiration 属性 (System.Runtime.Caching)

获取或设置一个值,该值指示如果在给定范围内未访问过缓存条目,是否应将其逐出...

learn.microsoft.com

缓存服务

我们可以在应用程序中使用的一种方法是在需要时直接与分布式缓存进行交互。

例如,如果服务需要缓存,我们将请求 的实例。这种方法可行,但可能不是最佳方法,因为它可能导致重复操作。

更可取的方法是为缓存交互创建专用服务,并在整个应用程序中使用它。这样可以集中管理缓存,并有助于避免冗余并提高可维护性。

示例项目

这里有一个示例项目,我准备了一个缓存服务的样本:文末

项目结构

该项目具有以下结构:

  • docker:包含一个 Docker Compose 文件,其中包含已配置的 Redis 容器。

  • src:包含项目源代码。

  • test:包含项目测试。

Docker Compose

以下是您将在 GitHub 上找到的 docker-compose 文件的内容:

version: '3'

services:

  redis-monitoring:
    image: redislabs/redisinsight:latest
    pull_policy: always
    ports:
      - '8001:8001'
    restart: unless-stopped
    networks:
          - default

  redis:  
    image: redis:latest
    pull_policy: always
    ports:
      - "6379:6379"
    restart: unless-stopped
    networks:
      - default

networks:
  default:
    driver: bridge

如您所见,compose 文件配置了两个服务:

  • redis: Redis 的实例。

  • RedisInsight: 一个帮助你与 Redis 交互的容器。此容器可用于调试目的。

此 Docker Compose 设置仅用于开发或测试目的。请注意,Redis 已更改其许可策略。

您可以在这篇前篇文章中找到更多信息:

API项目概览

API 项目公开了一些与 交互的方法。

以下是启动应用程序时将显示的内容的示例:

9a9996a9e3132a64b6dde29af72648e4.png

以下是 API 背后的代码:

app.MapGet("/GetOrCreateAsync/{key}", async (string key,ICacheService cache) =>  
{  
   return await cache.GetOrCreateAsync(key, () => Task.FromResult($"{nameof(cache.GetOrCreateAsync)} - Hello World"));  
})  
.WithName("GetOrCreateAsync")  
.WithOpenApi();  
  
  
app.MapGet("/GetOrDefault/{key}", async (string key, ICacheService cache) =>  
{  
    return await cache.GetOrDefaultAsync(key, $"{nameof(cache.GetOrDefault)} - Hello World");  
})  
.WithName("GetOrDefault")  
.WithOpenApi();  
  
  
app.MapGet("/CreateAndSet/{key}", async (string key, ICacheService cache) =>  
{  
    await cache.CreateAndSet(key, $"{nameof(cache.CreateAndSet)} - Hello World");  
})  
.WithName("CreateAndSet")  
.WithOpenApi();  
  
  
app.MapDelete("/RemoveAsync", (string key, ICacheService cache) =>  
{  
    cache.RemoveAsync(key);  
})  
.WithName("RemoveAsync")  
.WithOpenApi();

服务注册

如果未提供 Redis 配置,则该服务配置为使用内存中实现。

在API项目中,只需要使用扩展即可。

builder.Services.AddServiceCache(builder.Configuration);

扩展的详细信息如下:

public static IServiceCollection AddServiceCache(
        this IServiceCollection services,
        IConfiguration configuration
    )
    {
        services
            .AddOptions<CacheOptions>()
            .Bind(configuration.GetSection("Cache"))
            .ValidateDataAnnotations();

        if (!string.IsNullOrEmpty(configuration.GetSection("RedisCache:Configuration").Value))
        {
            services.AddStackExchangeRedisCache(options =>
            {
                configuration.Bind("RedisCache", options);
            });
        }
        else
        {
            services.AddDistributedMemoryCache();
        }

        services.AddTransient<ICacheService, CacheService>();
        return services;
    }

如您所见,它在应用程序设置中搜索名为“Cache”的键,以尝试初始化对象。此选项包含全局 Sliding Expiration 值的值。

ServiceCache 服务缓存

这是接口:CacheService

public interface ICacheService
{
    Task CreateAndSet<T>(string key, T thing, int expirationMinutes = 0)
        where T : class;
    Task<T> CreateAndSetAsync<T>(string key, Func<Task<T>> createAsync, int expirationMinutes = 0);
    Task<T> GetOrCreateAsync<T>(string key, Func<Task<T>> create, int expirationMinutes = 0);
    Task<T> GetOrDefault<T>(string key);
    Task<T> GetOrDefaultAsync<T>(string key, T defaultVal);
    Task RemoveAsync(string key);
}

它提供了一些实用方法来与 进行交互。

特别是,它提供了不同的方法来检索数据并设置自动默认值或创建方法。

让我们看一下示例:

public async Task<T> GetOrCreateAsync<T>(
        string key,
        Func<Task<T>> create,
        int expirationMinutes = 0
    )
    {
        var bytesResult = await _cache.GetAsync(key);

        if (bytesResult?.Length > 0)
        {
            using StreamReader reader = new(new MemoryStream(bytesResult));
            using JsonTextReader jsonReader = new(reader);
            JsonSerializer ser = new();
            ser.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
            ser.TypeNameHandling = TypeNameHandling.All;
            ser.StringEscapeHandling = StringEscapeHandling.EscapeNonAscii;

            var result = ser.Deserialize<T>(jsonReader);
            if (result != null)
            {
                return result;
            }
        }

        return await this.CreateAndSetAsync<T>(key, create, expirationMinutes);
    }

GetOrDefault另一方面,不会尝试初始化分布式缓存。如果找不到键,它只会返回默认值。

public async Task<T> GetOrDefault<T>(string key)
    {
        var bytesResult = await _cache.GetAsync(key);

        if (bytesResult?.Length > 0)
        {
            using StreamReader reader = new(new MemoryStream(bytesResult));
            using JsonTextReader jsonReader = new(reader);
            JsonSerializer ser = new();
            ser.TypeNameHandling = TypeNameHandling.All;
            ser.StringEscapeHandling = StringEscapeHandling.EscapeNonAscii;

            var result = ser.Deserialize<T>(jsonReader);
            if (result != null)
            {
                return result;
            }
        }

        return default;
    }

FusionCache

创建服务以管理分布式缓存的方法对于避免代码重复和具有抽象层非常有用。这可能适用于简单的方案,或者如果您希望完全控制代码库或限制外部影响。

对于更复杂的场景,您可以使用的一个很酷的库是 FusionCache,它本质上是类固醇上的服务缓存,提供高级弹性功能和可选的分布式二级缓存。

源代码获取:公众号回复消息【code:72610

如果你喜欢我的文章,请给我一个赞!谢谢

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值