之前看到过有人说没找到EasyCaching的相关介绍,这也是为什么要写这篇博客的原因,下面就先简单介绍一下EasyCaching。
什么是EasyCaching
EasyCaching,这个名字就很大程度上解释了它是做什么的,easy和caching放在一起,其最终的目的就是为了让我们大家在操作缓存的时候更加的方便。
它的发展大概经历了这几个比较重要的时间节点:
18年3月,在茶叔的帮助下进入了NCC
19年1月,镇汐提了很多改进意见
19年3月,NopCommerce引入EasyCaching
19年4月,列入awesome-dotnet-core(自己提pr过去的,有点小自恋。)
在EasyCaching出来之前,大部分人应该会对CacheManager比较熟悉,因为两者的定位和功能都差不多,所以偶尔会听到有朋友拿这两个去对比。
为了大家可以更好的进行对比,下面就重点介绍EasyCaching现有的功能了。
EasyCaching的主要功能
EasyCaching主要提供了下面的几个功能:
·统一的抽象缓存接口
·多种常用的缓存Provider(InMemory,Redis,Memcached,SQLite)
·为分布式缓存的数据序列化提供了多种选择
·二级缓存
·缓存的AOP操作(able, put,evict)
·多实例支持
·支持Diagnostics
·Redis的特殊Provider
当然除了这8个还有一些比较小的就不在这里列出来说明了。
下面就分别来介绍一下上面的这8个功能。
统一的抽象缓存接口
缓存,本身也可以算作是一个数据源,也是包含了一堆CURD的操作,所以会有一个统一的抽象接口。面向接口编程,虽然EasyCaching提供了一些简单的实现,不一定能满足您的需要,但是呢,只要你愿意,完全可以一言不合就实现自己的provider。
对于缓存操作,目前提供了下面几个,基本都会有同步和异步的操作。
TrySet/TrySetAsync
Set/SetAsync
SetAll/SetAllAsync
Get/GetAsync(with data retriever)
Get/GetAsync(without data retriever)
GetByPrefix/GetByPrefixAsync
GetAll/GetAllAsync
Remove/RemoveAsync
RemoveByPrefix/RemoveByPrefixAsync
RemoveAll/RemoveAllAsync
Flush/FlushAsync
GetCount
GetExpiration/GetExpirationAsync
Refresh/RefreshAsync(这个后面会被废弃,直接用set就可以了)
从名字的定义,应该就可以知道它们做了什么,这里就不继续展开了。
多种常用的缓存Provider
我们会把这些provider分为两大类,一类是本地缓存,一类是分布式缓存。
目前的实现有下面五个:
·本地缓存,InMemory,SQLite
·分布式缓存,StackExchange.Redis,csredis,EnyimMemcachedCore
它们的用法都是十分简单的。下面以InMemory这个Provider为例来说明。
首先是通过nuget安装对应的包。
dotnet add package EasyCaching.InMemory
其次是添加配置:
public void ConfigureServices(IServiceCollection services)
{
// 添加EasyCaching
services.AddEasyCaching(option =>
{
// 使用InMemory最简单的配置
option.UseInMemory("default");
//// 使用InMemory自定义的配置
//option.UseInMemory(options =>
//{
// // DBConfig这个是每种Provider的特有配置
// options.DBConfig = new InMemoryCachingOptions
// {
// // InMemory的过期扫描频率,默认值是60秒
// ExpirationScanFrequency = 60,
// // InMemory的最大缓存数量, 默认值是10000
// SizeLimit = 100
// };
// // 预防缓存在同一时间全部失效,可以为每个key的过期时间添加一个随机的秒数,默认值是120秒
// options.MaxRdSecond = 120;
// // 是否开启日志,默认值是false
// options.EnableLogging = false;
// // 互斥锁的存活时间, 默认值是5000毫秒
// options.LockMs = 5000;
// // 没有获取到互斥锁时的休眠时间,默认值是300毫秒
// options.SleepMs = 300;
// }, "m2");
//// 读取配置文件
//option.UseInMemory(Configuration, "m3");
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
// 如果使用的是Memcached或SQLite,还需要下面这个做一些初始化的操作
app.UseEasyCaching();
}
配置文件的示例:
"easycaching": {
"inmemory": {
"MaxRdSecond": 120,
"EnableLogging": false,
"LockMs": 5000,
"SleepMs": 300,
"DBConfig":{
"SizeLimit": 10000,
"ExpirationScanFrequency": 60
}
}
}
关于配置,这里有必要说明一点,那就是MaxRdSecond的值,因为这个把老猫子大哥坑了一次,所以要拎出来特别说一下,这个值的作用是预防在同一时刻出现大批量缓存同时失效,为每个key原有的过期时间上面加了一个随机的秒数,尽可能的分散它们的过期时间,如果您的应用场景不需要这个,可以将其设置为0。
最后的话就是使用了。
[Route("api/[controller]")]
public class ValuesController : Controller
{
// 单个provider的时候可以直接用IEasyCachingProvider
private readonly IEasyCachingProvider _provider;
public ValuesController(IEasyCachingProvider provider)
{
this._provider = provider;
}
// GET api/values/sync
[HttpGet]
[Route("sync")]
public string Get()
{
var res1 = _provider.Get("demo", () => "456", TimeSpan.FromMinutes(1));
var res2 = _provider.Get<string>("demo");
_provider.Set("demo", "123", TimeSpan.FromMinutes(1));
_provider.Remove("demo");
// others..
return "sync";
}
// GET api/values/async
[HttpGet]
[Route("async")]
public async Task<string> GetAsync(string str)
{
var res1 = await _provider.GetAsync("demo", async () => await Task.FromResult("456"), TimeSpan.FromMinutes(1));
var res2 = await _provider.GetAsync<string>("demo");
await _provider.SetAsync("demo", "123", TimeSpan.FromMinutes(1));
await _provider.RemoveAsync("demo");
// others..
return "async";
}
}
还有一个要注意的地方是,如果用的get方法是带有查询的,它在没有命中缓存的情况下去数据库查询前,会有一个加锁操作,避免一个key在同一时刻去查了n次数据库,这个锁的生存时间和休眠时间是由配置中的LockMs和SleepMs决定的。
分布式缓存的序列化选择
对于分布式缓存的操作,我们不可避免的会遇到序列化的问题.
目前这个主要是针对redis和memcached的。当然,对于序列化,都会有一个默认的实现是基于BinaryFormatter,因为这个不依赖于第三方的类库,如果没有指定其它的,就会使用这个去进行序列化的操作了。
除了这个默认的实现,还提供了三种额外的选择。Newtonsoft.Json,MessagePack和Protobuf。下面以在Redis的provider使用MessagePack为例,来看看它的用法。
services.AddEasyCaching(option=>
{
// 使用redis
option.UseRedis(config =>
{
config.DBConfig.Endpoints.Add(new ServerEndPoint("127.0.0.1", 6379));
}, "redis1")
// 使用MessagePack替换BinaryFormatter
.WithMessagePack()
//// 使用Newtonsoft.Json替换BinaryFormatter
//.WithJson()
//// 使用Protobuf替换BinaryFormatter
//.WithProtobuf()
;
});
不过这里需要注意的是,目前这些Serializer并不会跟着Provider走,意思就是不能说这个provider用messagepack,那个provider用json,只能有一种Serializer,可能这一个后面需要加强。
本文内容不用于商业目的,如涉及知识产权问题,请权利人联系51Testing小编(021-64471599-8017),我们将立即处理