MASA Framework 缓存入门与设计
概念
什么是缓存,在项目中,为了提高数据的读取速度,我们会对不经常变更但访问频繁的数据做缓存处理,我们常用的缓存有:
- 本地缓存
- 内存缓存:IMemoryCache
- 分布式缓存
- Redis: StackExchange.Redis
功能
目前,MasaFramework
为我们提供了以下能力
- IDistributedCacheClient: 分布式缓存
- IMultilevelCacheClient: 多级缓存
- Masa.Contrib.Caching.MultilevelCache: 基于内存缓存以及分布式缓存实现的多级缓存,支持监控缓存变更,分布式缓存更新后相应的内存缓存也会同步更新,避免命中过时的内存缓存导致获取错误的数据,同时也尽可能的将多个副本的内存缓存保持同步
入门
- 前提条件:安装.NET 6.0
分布式缓存
- 新建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
- 配置
Redis
配置信息
{ "RedisConfig":{ "Servers":[ { "Host":"localhost", "Port":6379 } ], "DefaultDatabase":3, "ConnectionPoolSize":10 } }
- 注册分布式缓存,并使用
Redis
缓存,修改Program.cs
var builder = WebApplication.CreateBuilder(args); //注册分布式缓存 builder.Services.AddDistributedCache(distributedCacheOptions => { distributedCacheOptions.UseStackExchangeRedisCache();//使用分布式Redis缓存, 默认使用本地`RedisConfig`下的配置 });
使用分布式缓存的数据来源默认为
IOptionsMonitor<RedisConfigurationOptions>
,如果本地未正确在RedisConfig
节点配置缓存信息,且项目中也没有通过其它方式配置使其支持选项模式,则默认使用的Redis配置为: 地址: localhost、端口:6379,密码:空,数据库:db0
- 新建
User
类,用于接收用户信息
public class User { public string Name { get; set; } public int Age { get; set; } }
- 如何使用
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); });
多级缓存
- 新建ASP.NET Core 空项目
Assignment.DistributedCache
,并安装Masa.Contrib.Caching.MultilevelCache
、Masa.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
- 注册多级缓存,并使用分布式
Redis
缓存,修改Program.cs
var builder = WebApplication.CreateBuilder(args); //注册多级缓存 builder.Services.AddMultilevelCache(distributedCacheOptions => { distributedCacheOptions.UseStackExchangeRedisCache();//使用分布式Redis缓存 });
- 新建
User
类,用于接收用户信息
public class User { public string Name { get; set; } public int Age { get; set; } }
- 如何使用
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的存储结果后,发现它们实际的存储与我们心目中的结果好像是有点出入,它们分别是:
- 缓存Key不同 (与我们设置的Key不完全一致)
- 结构不同 (实际存储的为Hash类型)
- 内容不同 (内容经过压缩)
缓存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 | 数据 | 存储用户设置的缓存数据 |
内容压缩规则
- 当存储值类型为以下类型时,不对数据进行压缩:
- Byte
- SByte
- UInt16
- UInt32
- UInt64
- Int16
- Int32
- Int64
- Double
- Single
- Decimal
- 当存储值类型为字符串时,对数据进行压缩
- 当存储值类型不满足以上条件时,对数据进行序列化并进行压缩
分布式Redis缓存示例
分布式缓存注册
方案一. 通过本地配置文件注册
- 修改
appsettings.json
文件
{ "RedisConfig":{ "Servers":[ { "Host":"localhost", "Port":6379 } ], "DefaultDatabase":3, "ConnectionPoolSize":10 } }
- 注册分布式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格式化处理 }; }); });
方案三. 通过选项模式注册
- 通过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 }; });
- 注册分布式Redis缓存
builder.Services.AddDistributedCache(distributedCacheOptions => { distributedCacheOptions.UseStackExchangeRedisCache(); });
方案四. 通过指定Configuration
注册
- 在Redis缓存的配置存储到本地
appsettings.json
文件
{ "RedisConfig":{ "Servers":[ { "Host": "localhost", "Port": 6379 } ], "DefaultDatabase": 3, "ConnectionPoolSize": 10 } }
- 指定
Configuration
注册分布式Redis缓存
var builder = WebApplication.CreateBuilder(args); //注册分布式缓存 builder.Services.AddDistributedCache(distributedCacheOptions => { // 使用存储Redis配置的Configuration distributedCacheOptions.UseStackExchangeRedisCache(builder.Configuration.GetSection("RedisConfig")); });
方案五. 将配置存储到Dcc上,并通过Configuration提供的手动映射功能,实现选项模式
- 使用Dcc,并手动指定映射
builder.AddMasaConfiguration(configurationBuilder => { configurationBuilder.UseDcc();//使用Dcc 扩展Configuration能力,支持远程配置 configurationBuilder.UseMasaOptions(options => { //通过手动映射RedisConfigurationOptions的配置,实现选项模式 options.MappingConfigurationApi<RedisConfigurationOptions>("{替换为Dcc中配置所属的AppId}", "{替换为Redis配置的对象名称}"); }); });
- 注册分布式Redis缓存
builder.Services.AddDistributedCache(distributedCacheOptions => { distributedCacheOptions.UseStackExchangeRedisCache(); });
方案三、四、五的本质都是通过支持选项模式来注册分布式Redis缓存
修改缓存Key映射规则
修改缓存Key映射规则十分简单,我们在配置时更改CacheKeyType为对应的规则即可,但当 CacheKeyType = 3 需要注意,它需要额外提供类型名与别名的对应关系,完整例子如下:
- 修改
appsettings.json
, 将CacheKeyType的值改为 3
{ "RedisConfig":{ "Servers":[ { "Host":"localhost", "Port":6379 } ], "DefaultDatabase":3, "ConnectionPoolSize":10, "GlobalCacheOptions": { "CacheKeyType": 3 //CacheKeyType为3时启用别名格式化缓存Key,可节省缓存Key的键长度 } } }
- 注册分布式缓存并配置类型名与别名的对应关系
builder.Services.AddDistributedCache(distributedCacheOptions => { distributedCacheOptions.UseStackExchangeRedisCache(); }, typeAliasOptions => { typeAliasOptions.GetAllTypeAliasFunc = () => new Dictionary<string, string>() { { "String", "s" }//当类型为String时,格式化后的Key为 s:key }; });
通过指定类型与别名的对应关系,从而使得最终形成较短的缓存Key,以达到节省存储空间的目的,缓存Key生成规则可查看
多级缓存示例
多级缓存注册
方案一. 通过本地配置文件注册
- 修改
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 } }
- 添加多级缓存并使用分布式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,否则返回falseGetKeys<T>
、GetKeysAsync<T>
: 根据key pattern 得到符合规则的所有缓存KeyGetByKeyPattern<T>
、GetByKeyPatternAsync<T>
: 根据key pattern 得到符合规则的所有缓存Key、Value集合HashIncrementAsync
: 将指定的缓存Key的值增加Value,并返回增长后的结果HashDecrementAsync
: 将指定的缓存Key的值减少Value,并返回减少后的结果- 支持设置最小的Value,避免减少后的值低于设置的最小值,执行失败则返回: -1
KeyExpire<T>
、KeyExpireAsync<T>
: 设置缓存Key的生命周期
以下方法不执行缓存Key格式化, 应传入缓存完整Key:
Remove
、RemoveAsync
: 将指定的缓存Key (缓存Key集合) 从缓存中移除Refresh
、RefreshAsync
: 刷新指定的缓存Key (缓存Key集合) 的生命周期- 适用于未被删除、绝对过期时间没有到,但相对过期时间快到的缓存
Exists
、ExistsAsync
: 如果在缓存中找到,则返回true,否则返回falseGetKeys
、GetKeysAsync
: 根据key pattern 得到符合规则的所有缓存Key- 例: 传入User*,可得到缓存中以User开头的所有缓存Key
KeyExpire
、KeyExpireAsync
: 设置缓存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为空字符串时,可直接使用
IDistributedCacheClient
或IMultilevelCacheClient
, 默认注册不指定Name时,则其Name为空字符串,可不通过Factory创建
总结
Masa Framework
提供了分布式缓存以及多级缓存的实现,其中有几个优秀的功能:
- 多级缓存提供了缓存更新后同步更新内存缓存功能
- 当我们的服务是多副本时,不必担心会缓存更新后其它副本由于内存缓存未过期,导致获取到过期的缓存数据,大大提升我们的用户体验
- 支持滑动过期以及绝对过期混合使用
- 避免无用的缓存长时间被持久化,但对于热点数据又可以避免打到Redis或者数据库
- 配置支持热更新,配置更新后同步生效,无需重启项目
- 缓存Key支持格式化,可根据当前缓存值类型与传入缓存Key结合形成新的缓存Key,提高了开发效率以及代码可读性
- 比如获取用户id为1的数据,可通过
Client.Get<User>("1")
,而无需:Client.Get<User>("User.1")
- 比如获取用户id为1的数据,可通过
本章源码
Assignment16
https://github.com/zhenlei520/MasaFramework.Practice
开源地址
MASA.Framework:https://github.com/masastack/MASA.Framework
如果你对我们的 MASA Framework 感兴趣,无论是代码贡献、使用、提 Issue,欢迎联系我们
- WeChat:MasaStackTechOps
- QQ:7424099

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
Linus 认为是时候移除对 i486 CPU 的支持
近日,Linus Torvalds 在邮件列表回应了对从内核移除英特尔 i486 处理器支持的想法。 Linux 内核十年前移除对 i386 的支持后,i486 一直是内核主线对 x86 架构支持的最低版本。Linus 说道:“我们早在 2012 年就取消了对 i386 支持,也许 2022 年是时候取消支持 i486 了?” Linus 认为大家应该“咬紧牙关”,让 x86 架构 32 位 CPU 的最低支持版本提高到"cmpxchg8b",即奔腾及更高版本。他表示,大多数(甚至是所有)发行版已经启用了 X86_PAE,这使得 X86_CMPXCHG64 成为基本要求的一部分。 事实上,早在一年前社区就已经开始讨论从内核移除i486 支持。当时有一名开发者表示自己还在使用 i486,并声称仍然有一些实际用途。但整体来看,在 i486 上运行现代发行版/内核的 Linux 用户极其罕见。所以 Linus 坚持赞成让内核移除 i486 支持的想法。他认为 i486 硬件已经没有什么意义,虽然这些硬件仍然存在,但从内核开发的角度来看,这些工作没什么价值。 Linus 指出,既然 i486 ...
- 下一篇
到底为什么我们需要 Clickhouse?
Clickhouse 是现在最流行的 OLAP 数据库之一,虽然名声如雷贯耳,但在我们心目中总有一个疑问,到底为什么我们需要 Clickhouse,是哪些优点让字节、腾讯这些大公司都选择它作为最推荐的 OLAP 数据库,这篇文章将试图带我们找到答案。 一、什么是 Clickhouse? Clickhouse 是一个开源使用列式存储的 OLAP 数据库,最初由 Yandex 公司开发,现在从 Yandex 拆分出来并成立了独立的 Clickhouse Inc,其功能类似于 Google Analytics。它的目标是处理数万亿行和数 PB 的数据,并快速执行分析查询。 Clickhouse 等 OLAP 数据库通常用于回答诸如"昨天有多少人访问掘金?昨天有多少人访问 CSDN"之类的业务问题。如果使用传统的 OLTP 数据库来处理,可能需要几分钟甚至几个小时。而如果使用 OLAP 数据库,我们几毫秒就得到结果。OLTP 和 OLAP 之间的巨大速度差异是因为使用的底层存储结构不同,OLTP 数据库通常使用行式存储,OLAP 则通常使用列式存储。 什么是列式存储? 假设我们有如下数据: 时...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- CentOS8安装MyCat,轻松搞定数据库的读写分离、垂直分库、水平分库
- CentOS8编译安装MySQL8.0.19
- CentOS6,CentOS7官方镜像安装Oracle11G
- CentOS7,8上快速安装Gitea,搭建Git服务器
- SpringBoot2整合Thymeleaf,官方推荐html解决方案
- MySQL8.0.19开启GTID主从同步CentOS8
- SpringBoot2更换Tomcat为Jetty,小型站点的福音
- Red5直播服务器,属于Java语言的直播服务器
- CentOS6,7,8上安装Nginx,支持https2.0的开启