概念

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

功能

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

入门

分布式缓存

  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<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.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
  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支持三种规则:

枚举 值 描述
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<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映射规则

源码解读

IDistributedCacheClient (分布式缓存客户端)

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:

  • Remove、RemoveAsync: 将指定的缓存Key (缓存Key集合) 从缓存中移除
  • Refresh、RefreshAsync: 刷新指定的缓存Key (缓存Key集合) 的生命周期
    • 适用于未被删除、绝对过期时间没有到,但相对过期时间快到的缓存
  • Exists、ExistsAsync: 如果在缓存中找到,则返回true,否则返回false
  • GetKeys、GetKeysAsync: 根据key pattern 得到符合规则的所有缓存Key
    • 例: 传入User*,可得到缓存中以User开头的所有缓存Key
  • KeyExpire、KeyExpireAsync: 设置缓存Key的生命周期

IMultilevelCacheClient (多级缓存客户端)

  • 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集合) 的生命周期
    • 适用于未被删除、绝对过期时间没有到,但相对过期时间快到的缓存 (延长滑动过期时间)

IDistributedCacheClientFactory (分布式缓存工厂)

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

IMultilevelCacheClientFactory (多级缓存工厂)

  • 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")

本章源码

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,欢迎联系我们

MasaFramework -- 缓存入门与设计的更多相关文章

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

    概念 什么是缓存,在项目中,为了提高数据的读取速度,我们会对不经常变更但访问频繁的数据做缓存处理,我们常用的缓存有: 本地缓存 内存缓存:IMemoryCache 分布式缓存 Redis: Stack ...

  2. Memcached缓存入门篇

    Asp.Net中使用Couchbase——Memcached缓存入门篇 前言 本文的主要目的就是简单的进行使用Memcached.这是Memchahed的官网http://memcached.org/ ...

  3. 《深入理解mybatis原理4》 MyBatis缓存机制的设计与实现

    <深入理解mybatis原理> MyBatis缓存机制的设计与实现 本文主要讲解MyBatis非常棒的缓存机制的设计原理,给读者们介绍一下MyBatis的缓存机制的轮廓,然后会分别针对缓存 ...

  4. ehcache缓存入门学习

    ehcache缓存入门学习 1,概念 特性 EhCache 是一个纯Java的进程内缓存框架,具有快速.精干等特点,是Hibernate中默认的CacheProvider. 主要的特性有:1. 快速2 ...

  5. ASP.NET MVC 3:缓存功能的设计问题

    今天这一篇文章我来谈一谈在MVC 3项目中的缓存功能,以及针对缓存的一些设计上的考量,给大家参考参考. 为什么需要讨论缓存?缓存是一个中大型系统所必须考虑的问题.为了避免每次请求都去访问后台的资源(例 ...

  6. 《深入理解mybatis原理》 MyBatis缓存机制的设计与实现

    本文主要讲解MyBatis非常棒的缓存机制的设计原理,给读者们介绍一下MyBatis的缓存机制的轮廓,然后会分别针对缓存机制中的方方面面展开讨论. MyBatis将数据缓存设计成两级结构,分为一级缓存 ...

  7. 零元学Expression Blend 4 - Chapter 6 如何置入Photoshop档案以及入门动画设计

    原文:零元学Expression Blend 4 - Chapter 6 如何置入Photoshop档案以及入门动画设计 本章将教大家如何把Photoshop档案置入Expression Blend ...

  8. iOS缓存类的设计

    使用执行速度缓存的程序可以大大提高程序,设计一个简单的缓存类并不需要太复杂的逻辑. 只需要一个简单的3接口. 存款对象 以一个对象 删除对象 阅读对象 watermark/2/text/aHR0cDo ...

  9. ASP.NET MVC:缓存功能的设计及问题

    这是非常详尽的asp.net mvc中的outputcache 的使用文章. [原文:陈希章 http://www.cnblogs.com/chenxizhang/archive/2011/12/14 ...

随机推荐

  1. 无痕模式下 this.StorageManager.setItem) 本地存储丢失

    在无痕模式下,存的this.StorageManager.setItem("recharge", JSON.stringify(recharge))本地存储会丢失,所以我们改成使用 ...

  2. react实战 系列 —— React 的数据流和生命周期

    其他章节请看: react实战 系列 数据流和生命周期 如何处理 React 中的数据,组件之间如何通信,数据在 React 中如何流动? 常用的 React 生命周期方法以及开源项目 spug 中使 ...

  3. Excel 笔记目录

    前言 Excel 是微软(Microsoft)公司推出的 Office 办公系列软件的一个重要组成部分,主要用于电子表格处理,可以高效地完成各种表格和图表的设计,进行复杂的数据计算和分析. 一句科普 ...

  4. JavaSciprt 笔记目录

    JavaScript 基础知识(一):对象以及原型 JavaScript 基础知识(二):闭包 JavaScript 异步编程(一):认识异步编程 JavaScript 异步编程(二):Promise

  5. 你言我语 By Twikoo

    主要做了两件事: 一是前端魔改 二是首页调用(替代原 bber) 注明:以下样式.功能代码基于 Twikoo v1.6.4 前端魔改 "管理面板"按钮同步隐藏输入框.先到twiko ...

  6. 痞子衡嵌入式:MCUXpresso IDE下设置代码编译优化等级的几种方法

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家分享的是MCUXpresso IDE下设置代码编译优化等级的几种方法. 最近公司芯片设计团队正在开发一款全新的基于 Cortex-M33 内核的 ...

  7. C#基础_枚举

    一.在学习枚举之前,首先来听听枚举的优点. 1.枚举能够使代码更加清晰,它允许使用描述性的名称表示整数值. 2.枚举使代码更易于维护,有助于确保给变量指定合法的.期望的值. 3.枚举使代码更易输入. ...

  8. SpringMVC 02: SpringMVC响应get和post请求 + 5种获取前端数据的方式

    响应get和post请求 SpringMVC中使用@RequestMapping注解完成对get请求和post请求的响应 项目结构和配置文件与SpringMVC博客集中的"SpringMVC ...

  9. 第九十九篇:JS闭包

    好家伙,总是要来的,去面对那些晦涩难懂的原理,它就在那里,等着我去搞定它 首先我要去补充一些最基本的概念, 1.什么是内存? 新华字典永远的神, 但这个解释显然不够   去看看百度百科: 内存: CP ...

  10. spring项目中starter包的原理,以及自定义starter包的使用

    MAVEN项目中starter的原理 一.原始方式 我们最早配置spring应用的时候,必须要经历的步骤:1.pom文件中引入相关的jar包,包括spring,redis,jdbc等等 2.通过pro ...