概念

什么是缓存,在我的项目中,为了进步数据的读取速度,咱们会对不常常变更但拜访频繁的数据做缓存解决,咱们罕用的缓存有:

  • 本地缓存

    • 内存缓存:IMemoryCache
  • 分布式缓存

    • Redis: StackExchange.Redis

性能

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

  • IDistributedCacheClient: 分布式缓存

    • Masa.Contrib.Caching.Distributed.StackExchangeRedis: 基于StackExchange.Redis实现的分布式缓存
  • IMultilevelCacheClient: 多级缓存

    • Masa.Contrib.Caching.MultilevelCache: 基于内存缓存以及分布式缓存实现的多级缓存,反对监控缓存变更,分布式缓存更新后相应的内存缓存也会同步更新,防止命中过期的内存缓存导致获取谬误的数据,同时也尽可能的将多个正本的内存缓存放弃同步

入门

  • 前提条件:装置.NET 6.0

分布式缓存

  1. 新建ASP.NET Core 空我的项目Assignment.DistributedCache,并装置Masa.Contrib.Caching.Distributed.StackExchangeRedis
dotnet new web -o Assignment.DistributedCachecd Assignment.DistributedCachedotnet 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.MultilevelCachedotnet 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反对三种规定:

枚举形容
None1不做解决,传入的Key即为理论的缓存Key
TypeName2理论的缓存Key = $"{GetTypeName(T)}.{传入缓存Key}" (默认)
TypeAlias3依据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:

  • RemoveRemoveAsync: 将指定的缓存Key (缓存Key汇合) 从缓存中移除
  • RefreshRefreshAsync: 刷新指定的缓存Key (缓存Key汇合) 的生命周期

    • 实用于未被删除、相对过期工夫没有到,但绝对过期工夫快到的缓存
  • ExistsExistsAsync: 如果在缓存中找到,则返回true,否则返回false
  • GetKeysGetKeysAsync: 依据key pattern 失去合乎规定的所有缓存Key

    • 例: 传入User*,可失去缓存中以User结尾的所有缓存Key
  • KeyExpireKeyExpireAsync: 设置缓存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为空字符串时,可间接应用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...

开源地址

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

如果你对咱们的 MASA Framework 感兴趣,无论是代码奉献、应用、提 Issue,欢送分割咱们

  • WeChat:MasaStackTechOps
  • QQ:7424099