MasaFramework -- 缓存入门与规则配置


概念

什么是缓存,在项目中,为了提高数据的读取速度,我们会对不经常变更但访问频繁的数据做缓存处理,我们常用的缓存有:

  • 本地缓存
    • 内存缓存:IMemoryCache
  • 分布式缓存
    • Redis: StackExchange.Redis

功能

目前,MasaFramework为我们提供了以下能力

  • Masa.Contrib.Caching.Distributed.StackExchangeRedis: 基于StackExchange.Redis实现的分布式缓存
  • Masa.Contrib.Caching.MultilevelCache: 基于内存缓存以及分布式缓存实现的多级缓存,支持监控缓存变更,分布式缓存更新后相应的内存缓存也会同步更新,避免命中过时的内存缓存导致获取错误的数据,同时也尽可能的将多个副本的内存缓存保持同步
  • 入门

    • 前提条件:安装.NET 6.0

    分布式缓存

    1. 新建ASP.NET Core 空项目Assignment.DistributedCache,并安装Masa.Contrib.Caching.Distributed.StackExchangeRedis
    dotnet new web -o Assignment.DistributedCache
    cd Assignment.DistributedCache
    
    dotnet add package Masa.Contrib.Caching.Distributed.StackExchangeRedis --version 0.6.0-rc.5
    
    1. 配置Redis配置信息
    {
        "RedisConfig":{
            "Servers":[
                {
                    "Host":"localhost",
                    "Port":6379
                }
            ],
            "DefaultDatabase":3,
            "ConnectionPoolSize":10
        }
    }
    
    1. 注册分布式缓存,并使用Redis缓存,修改Program.cs
    var builder = WebApplication.CreateBuilder(args);
    
    //注册分布式缓存
    builder.Services.AddDistributedCache(distributedCacheOptions =>
    {
        distributedCacheOptions.UseStackExchangeRedisCache();//使用分布式Redis缓存, 默认使用本地`RedisConfig`下的配置
    });
    

    使用分布式缓存的数据来源默认为 IOptionsMonitor,如果本地未正确在RedisConfig节点配置缓存信息,且项目中也没有通过其它方式配置使其支持选项模式,则默认使用的Redis配置为: 地址: localhost、端口:6379,密码:空,数据库:db0

    1. 新建User类,用于接收用户信息
    public class User
    {
        public string Name { get; set; }
    
        public int Age { get; set; }
    }
    
    1. 如何使用IDistributedCacheClient,修改Program.cs
    // 设置缓存
    app.MapPost("/set/{id}", async (IDistributedCacheClient distributedCacheClient, [FromRoute] string id, [FromBody] User user) =>
    {
        await distributedCacheClient.SetAsync(id, user);
        return Results.Accepted();
    });
    
    // 获取缓存
    app.MapGet("/get/{id}", async (IDistributedCacheClient distributedCacheClient, [FromRoute] string id) =>
    {
        var value = await distributedCacheClient.GetAsync(id);
        return Results.Ok(value);
    });
    

    多级缓存

    1. 新建ASP.NET Core 空项目Assignment.DistributedCache,并安装Masa.Contrib.Caching.MultilevelCacheMasa.Contrib.Caching.Distributed.StackExchangeRedis
    dotnet new web -o Assignment.MultilevelCache
    cd Assignment.MultilevelCache
    
    dotnet add package Masa.Contrib.Caching.MultilevelCache --version 0.6.0-rc.5
    dotnet add package Masa.Contrib.Caching.Distributed.StackExchangeRedis --version 0.6.0-rc.5
    
    1. 注册多级缓存,并使用分布式Redis缓存,修改Program.cs
    var builder = WebApplication.CreateBuilder(args);
    
    //注册多级缓存
    builder.Services.AddMultilevelCache(distributedCacheOptions =>
    {
        distributedCacheOptions.UseStackExchangeRedisCache();//使用分布式Redis缓存
    });
    
    1. 新建User类,用于接收用户信息
    public class User
    {
        public string Name { get; set; }
    
        public int Age { get; set; }
    }
    
    1. 如何使用IMultilevelCacheClient,修改Program.cs
    // 设置缓存
    app.MapPost("/set/{id}", async (IMultilevelCacheClient multilevelCacheClient, [FromRoute] string id, [FromBody] User user) =>
    {
        await multilevelCacheClient.SetAsync(id, user);
        return Results.Accepted();
    });
    
    // 获取缓存
    app.MapGet("/get/{id}", async (IMultilevelCacheClient multilevelCacheClient, [FromRoute] string id) =>
    {
        var value = await multilevelCacheClient.GetAsync(id);
        return Results.Ok(value);
    });
    

    测试

    借助Postman或者Swagger或者使用其它API测试工具,分别测试设置缓存与获取缓存,以验证分布式缓存以及多级缓存是可以正常使用的。

    友情提示:检查Redis缓存,找到刚刚你配置的缓存,确定下它的存储结果是否与你想象的一致!!

    规则

    经过测试,我们的分布式缓存与多级缓存是可以正常使用的,但查看Redis的存储结果后,发现它们实际的存储与我们心目中的结果好像是有点出入,它们分别是:

    1. 缓存Key不同 (与我们设置的Key不完全一致)
    2. 结构不同 (实际存储的为Hash类型)
    3. 内容不同 (内容经过压缩)

    image.png

    缓存Key的生成规则

    缓存Key支持三种规则:

    枚举 描述
    None 1 不做处理,传入的Key即为实际的缓存Key
    TypeName 2 实际的缓存Key = $"{GetTypeName(T)}.{传入缓存Key}" (默认)
    TypeAlias 3 根据TypeName得到对应的别名与Key的组合,Format: ${TypeAliasName}{:}{key}

    详细规则可查看

    存储结构与规则

    Masa.Contrib.Caching.Distributed.StackExchangeRedis使用的是Hash存储,通过使用Hash存储,支持缓存的绝对过期以及相对过期,其中:

    描述 详细 特殊
    absexp 绝对过期时间的Ticks 自公历 0001-01-01 00:00:00:000 到绝对过期时间的计时周期数 (1周期 = 100ns 即 1/10000 ms) -1 为永不过期
    sldexp 滑动过期时间的Ticks 自公历 0001-01-01 00:00:00:000 到滑动过期时间的计时周期数 (1周期 = 100ns 即 1/10000 ms,每次获取数据时会刷新滑动过期时间) -1 为永不过期
    data 数据 存储用户设置的缓存数据

    内容压缩规则

    1. 当存储值类型为以下类型时,不对数据进行压缩:
    • Byte
    • SByte
    • UInt16
    • UInt32
    • UInt64
    • Int16
    • Int32
    • Int64
    • Double
    • Single
    • Decimal
    1. 当存储值类型为字符串时,对数据进行压缩
    2. 当存储值类型不满足以上条件时,对数据进行序列化并进行压缩

    分布式Redis缓存示例

    分布式缓存注册

    方案一. 通过本地配置文件注册

    1. 修改appsettings.json文件
    {
        "RedisConfig":{
            "Servers":[
                {
                    "Host":"localhost",
                    "Port":6379
                }
            ],
            "DefaultDatabase":3,
            "ConnectionPoolSize":10
        }
    }
    
    1. 注册分布式Redis缓存
    builder.Services.AddDistributedCache(distributedCacheOptions =>
    {
        distributedCacheOptions.UseStackExchangeRedisCache();
    });
    

    方案二. 手动指定Redis配置注册

    builder.Services.AddDistributedCache(distributedCacheOptions =>
    {
        distributedCacheOptions.UseStackExchangeRedisCache(options =>
        {
            options.Servers = new List()
            {
                new("localhost", 6379)
            };
            options.DefaultDatabase = 3;
            options.ConnectionPoolSize = 10;
            options.GlobalCacheOptions = new CacheOptions()
            {
                CacheKeyType = CacheKeyType.None //全局禁用缓存Key格式化处理
            };
        });
    });
    

    方案三. 通过选项模式注册

    1. 通过Configure方法使其支持选项模式
    builder.Services.Configure(redisConfigurationOptions =>
    {
        redisConfigurationOptions.Servers = new List()
        {
            new("localhost", 6379)
        };
        redisConfigurationOptions.DefaultDatabase = 3;
        redisConfigurationOptions.ConnectionPoolSize = 10;
        redisConfigurationOptions.GlobalCacheOptions = new CacheOptions()
        {
            CacheKeyType = CacheKeyType.None
        };
    });
    
    1. 注册分布式Redis缓存
    builder.Services.AddDistributedCache(distributedCacheOptions =>
    {
        distributedCacheOptions.UseStackExchangeRedisCache();
    });
    

    方案四. 通过指定Configuration注册

    1. 在Redis缓存的配置存储到本地appsettings.json文件
    {
        "RedisConfig":{
            "Servers":[
                {
                    "Host": "localhost",
                    "Port": 6379
                }
            ],
            "DefaultDatabase": 3,
            "ConnectionPoolSize": 10
        }
    }
    
    1. 指定Configuration注册分布式Redis缓存
    var builder = WebApplication.CreateBuilder(args);
    
    //注册分布式缓存
    builder.Services.AddDistributedCache(distributedCacheOptions =>
    {
        // 使用存储Redis配置的Configuration
        distributedCacheOptions.UseStackExchangeRedisCache(builder.Configuration.GetSection("RedisConfig"));
    });
    

    方案五. 将配置存储到Dcc上,并通过Configuration提供的手动映射功能,实现选项模式

    1. 使用Dcc,并手动指定映射
    builder.AddMasaConfiguration(configurationBuilder =>
    {
        configurationBuilder.UseDcc();//使用Dcc 扩展Configuration能力,支持远程配置
    
        configurationBuilder.UseMasaOptions(options =>
        {
            //通过手动映射RedisConfigurationOptions的配置,实现选项模式
            options.MappingConfigurationApi("{替换为Dcc中配置所属的AppId}", "{替换为Redis配置的对象名称}");
        });
    });
    
    1. 注册分布式Redis缓存
    builder.Services.AddDistributedCache(distributedCacheOptions =>
    {
        distributedCacheOptions.UseStackExchangeRedisCache();
    });
    

    方案三、四、五的本质都是通过支持选项模式来注册分布式Redis缓存

    修改缓存Key映射规则

    修改缓存Key映射规则十分简单,我们在配置时更改CacheKeyType为对应的规则即可,但当 选项模式,我们可以通过Dcc或者利用 builder.Services.Configure(builder.Configuration)来支持选项模式

    修改缓存Key映射规则

    源码解读

    Masa Framework提供了分布式缓存以及多级缓存的实现,其中有几个优秀的功能:

    • 多级缓存提供了缓存更新后同步更新内存缓存功能
      • 当我们的服务是多副本时,不必担心会缓存更新后其它副本由于内存缓存未过期,导致获取到过期的缓存数据,大大提升我们的用户体验
    • 支持滑动过期以及绝对过期混合使用
      • 避免无用的缓存长时间被持久化,但对于热点数据又可以避免打到Redis或者数据库
    • 配置支持热更新,配置更新后同步生效,无需重启项目
    • 缓存Key支持格式化,可根据当前缓存值类型与传入缓存Key结合形成新的缓存Key,提高了开发效率以及代码可读性
      • 比如获取用户id为1的数据,可通过Client.Get("1"),而无需:Client.Get("User.1")

    本章源码

    Assignment16

    https://github.com/zhenlei520/MasaFramework.Practice

    开源地址

    MASA.Framework:https://github.com/masastack/MASA.Framework

    MASA.EShop:https://github.com/masalabs/MASA.EShop

    MASA.Blazor:https://github.com/BlazorComponent/MASA.Blazor

    如果你对我们的 MASA Framework 感兴趣,无论是代码贡献、使用、提 Issue,欢迎联系我们

    16373211753064.png