写点什么

MASA Framework 缓存入门与设计

  • 2022-10-24
    浙江
  • 本文字数:6757 字

    阅读完需:约 22 分钟

概念

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



功能

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


入门

分布式缓存

  1. 新建 ASP.NET Core 空项目Assignment.DistributedCache,并安装Masa.Contrib.Caching.Distributed.StackExchangeRedis


dotnet new web -o Assignment.DistributedCachecd 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<RedisConfigurationOptions>,如果本地未正确在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<User>(id); return Results.Ok(value);});
复制代码

多级缓存

  1. 新建 ASP.NET Core 空项目Assignment.DistributedCache,并安装Masa.Contrib.Caching.MultilevelCacheMasa.Contrib.Caching.Distributed.StackExchangeRedis


dotnet new web -o Assignment.MultilevelCachecd Assignment.MultilevelCache
dotnet add package Masa.Contrib.Caching.MultilevelCache --version 0.6.0-rc.5dotnet 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<User>(id); return Results.Ok(value);});
复制代码

测试

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


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

规则

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


  1. 缓存 Key 不同 (与我们设置的 Key 不完全一致)

  2. 结构不同 (实际存储的为 Hash 类型)

  3. 内容不同 (内容经过压缩)


缓存 Key 的生成规则

缓存 Key 支持三种规则:



详细规则可查看

存储结构与规则

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


内容压缩规则

  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<RedisServerOptions>()        {            new("localhost", 6379)        };        options.DefaultDatabase = 3;        options.ConnectionPoolSize = 10;        options.GlobalCacheOptions = new CacheOptions()        {            CacheKeyType = CacheKeyType.None //全局禁用缓存Key格式化处理        };    });});
复制代码


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


  1. 通过 Configure 方法使其支持选项模式


builder.Services.Configure<RedisConfigurationOptions>(redisConfigurationOptions =>{    redisConfigurationOptions.Servers = new List<RedisServerOptions>()    {        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<RedisConfigurationOptions>("{替换为Dcc中配置所属的AppId}", "{替换为Redis配置的对象名称}"); });});
复制代码


  1. 注册分布式 Redis 缓存


builder.Services.AddDistributedCache(distributedCacheOptions =>{    distributedCacheOptions.UseStackExchangeRedisCache();});
复制代码


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

修改缓存 Key 映射规则

修改缓存 Key 映射规则十分简单,我们在配置时更改 CacheKeyType 为对应的规则即可,但当 CacheKeyType = 3 需要注意,它需要额外提供类型名与别名的对应关系,完整例子如下:


  1. 修改appsettings.json, 将 CacheKeyType 的值改为 3


{    "RedisConfig":{        "Servers":[            {                "Host":"localhost",                "Port":6379            }        ],        "DefaultDatabase":3,        "ConnectionPoolSize":10,        "GlobalCacheOptions": {          "CacheKeyType": 3 //CacheKeyType为3时启用别名格式化缓存Key,可节省缓存Key的键长度        }    }}
复制代码


  1. 注册分布式缓存并配置类型名与别名的对应关系


builder.Services.AddDistributedCache(distributedCacheOptions =>{    distributedCacheOptions.UseStackExchangeRedisCache();}, typeAliasOptions =>{    typeAliasOptions.GetAllTypeAliasFunc = () => new Dictionary<string, string>()    {        { "String", "s" }//当类型为String时,格式化后的Key为 s:key    };});
复制代码


通过指定类型与别名的对应关系,从而使得最终形成较短的缓存 Key,以达到节省存储空间的目的,缓存 Key 生成规则可查看

多级缓存示例

多级缓存注册

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


  1. 修改appsettings.json文件,分别配置多级缓存配置以及 Redis 缓存配置


{  // 多级缓存全局配置,非必填  "MultilevelCache": {    "SubscribeKeyPrefix": "masa",//默认订阅方key前缀,用于拼接channel    "SubscribeKeyType": 3, //默认订阅方key的类型,默认ValueTypeFullNameAndKey,用于拼接channel    "CacheEntryOptions": {      "AbsoluteExpirationRelativeToNow": "00:00:30",//绝对过期时长(距当前时间)      "SlidingExpiration": "00:00:50"//滑动过期时长(距当前时间)    }  },
// Redis分布式缓存配置 "RedisConfig": { "Servers": [ { "Host": "localhost", "Port": 6379 } ], "DefaultDatabase": 3 }}
复制代码


  1. 添加多级缓存并使用分布式 Redis 缓存


builder.Services.AddMultilevelCache(distributedCacheOptions =>{    distributedCacheOptions.UseStackExchangeRedisCache();});
复制代码


方案二. 通过手动指定配置


builder.Services.AddMultilevelCache(distributedCacheOptions =>{    distributedCacheOptions.UseStackExchangeRedisCache(RedisConfigurationOptions);});
复制代码


未配置内存缓存时,默认内存缓存永久有效


除了上述两种方式以外,多级缓存的内存缓存配置也同样支持选项模式,我们可以通过Dcc或者利用 builder.Services.Configure<MultilevelCacheOptions>(builder.Configuration)来支持选项模式

修改缓存 Key 映射规则

源码解读

<a id = "IDistributedCacheClient">IDistributedCacheClient (分布式缓存客户端)</a>

IDistributedCacheClient接口提供以下方法来处理分布式缓存


以下方法会根据全局缓存 Key 的规则配置以及传入缓存 Key 的规则配置,检测是否需要格式化缓存 Key,对需要格式化 Key 的操作按照缓存 Key 格式化规则进行处理,详细查看:


  • Get<T>GetAsync<T>: 根据缓存 Key 返回类型为T的结果 (如果缓存不存在,则返回 Null)

  • GetList<T>GetListAsync<T>: 根据缓存 Key 集合返回对应的缓存值的集合 (针对不存在的缓存 key,其值返回 Null)

  • GetOrSet<T>GetOrSetAsync<T>: 如果在缓存中找到,则返回类型为T的结果,如果缓存未找到,则执行Setter,并返回Setter的结果

  • Set<T>SetAsync<T>: 将指定的缓存 Key 以及缓存值添加到缓存

  • SetList<T>SetListAsync<T>: 将指定的缓存 Key、Value 集合添加缓存

  • Remove<T>RemoveAsync<T>: 将指定的缓存 Key (缓存 Key 集合) 从缓存中移除

  • Refresh<T>RefreshAsync<T>: 刷新指定的缓存 Key (缓存 Key 集合) 的生命周期

  • 适用于未被删除、绝对过期时间没有到,但相对过期时间快到的缓存 (延长滑动过期时间)

  • Exists<T>ExistsAsync<T>: 如果在缓存中找到,则返回 true,否则返回 false

  • GetKeys<T>GetKeysAsync<T>: 根据 key pattern 得到符合规则的所有缓存 Key

  • GetByKeyPattern<T>GetByKeyPatternAsync<T>: 根据 key pattern 得到符合规则的所有缓存 Key、Value 集合

  • HashIncrementAsync: 将指定的缓存 Key 的值增加 Value,并返回增长后的结果

  • HashDecrementAsync: 将指定的缓存 Key 的值减少 Value,并返回减少后的结果

  • 支持设置最小的 Value,避免减少后的值低于设置的最小值,执行失败则返回: -1

  • KeyExpire<T>KeyExpireAsync<T>: 设置缓存 Key 的生命周期


以下方法不执行缓存 Key 格式化, 应传入缓存完整 Key:


  • RemoveRemoveAsync: 将指定的缓存 Key (缓存 Key 集合) 从缓存中移除

  • RefreshRefreshAsync: 刷新指定的缓存 Key (缓存 Key 集合) 的生命周期

  • 适用于未被删除、绝对过期时间没有到,但相对过期时间快到的缓存

  • ExistsExistsAsync: 如果在缓存中找到,则返回 true,否则返回 false

  • GetKeysGetKeysAsync: 根据 key pattern 得到符合规则的所有缓存 Key

  • 例: 传入 User*,可得到缓存中以 User 开头的所有缓存 Key

  • KeyExpireKeyExpireAsync: 设置缓存 Key 的生命周期

<a id = "IMultilevelCacheClient">IMultilevelCacheClient (多级缓存客户端)</a>

  • Get<T>GetAsync<T>: 根据缓存 Key 返回类型为T的结果 (如果缓存不存在,则返回 Null) (支持监控缓存变更)

  • GetList<T>GetListAsync<T>: 根据缓存 Key 集合返回对应的缓存值的集合 (针对不存在的缓存 key,其值返回 Null)

  • GetOrSet<T>GetOrSetAsync<T>: 如果在缓存中找到,则返回类型为T的结果,如果缓存未找到,则执行Setter,并返回Setter的结果

  • Set<T>SetAsync<T>: 将指定的缓存 Key 以及缓存值添加到缓存

  • SetList<T>SetListAsync<T>: 将指定的缓存 Key、Value 集合添加缓存

  • Remove<T>RemoveAsync<T>: 将指定的缓存 Key (缓存 Key 集合) 从缓存中移除

  • Refresh<T>RefreshAsync<T>: 刷新指定的缓存 Key (缓存 Key 集合) 的生命周期

  • 适用于未被删除、绝对过期时间没有到,但相对过期时间快到的缓存 (延长滑动过期时间)

<a id = "IDistributedCacheClient">IDistributedCacheClientFactory (分布式缓存工厂)</a>

  • Create: 返回指定 Name 的分布式缓存客户端

<a id = "IMultilevelCacheClientFactory">IMultilevelCacheClientFactory (多级缓存工厂)</a>

  • Create: 返回指定 Name 的多级缓存客户端


如果 Name 为空字符串时,可直接使用IDistributedCacheClientIMultilevelCacheClient, 默认注册不指定 Name 时,则其 Name 为空字符串,可不通过 Factory 创建

总结

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


  • 多级缓存提供了缓存更新后同步更新内存缓存功能

  • 当我们的服务是多副本时,不必担心会缓存更新后其它副本由于内存缓存未过期,导致获取到过期的缓存数据,大大提升我们的用户体验

  • 支持滑动过期以及绝对过期混合使用

  • 避免无用的缓存长时间被持久化,但对于热点数据又可以避免打到 Redis 或者数据库

  • 配置支持热更新,配置更新后同步生效,无需重启项目

  • 缓存 Key 支持格式化,可根据当前缓存值类型与传入缓存 Key 结合形成新的缓存 Key,提高了开发效率以及代码可读性

  • 比如获取用户 id 为 1 的数据,可通过Client.Get<User>("1"),而无需:Client.Get<User>("User.1")

本章源码

Assignment16


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

开源地址

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


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


  • WeChat:MasaStackTechOps

  • QQ:7424099

发布于: 刚刚阅读数: 5
用户头像

还未添加个人签名 2021-10-26 加入

MASA技术团队官方账号,我们专注于.NET现代应用开发解决方案,Wechat:MasaStackTechOps ,Website:www.masastack.com

评论

发布
暂无评论
MASA Framework 缓存入门与设计_.net_MASA技术团队_InfoQ写作社区