一篇短文带您了解一下EasyCaching

前言

从2017年11月11号在Github创建EasyCaching这个仓库,到现在也已经将近一年半的时间了,基本都是在下班之后和假期在完善这个项目。

由于EasyCaching目前只有英文的文档托管在Read the Docs上面,当初选的MkDocs现在还不支持多语言,所以这个中文的要等它支持之后才会有计划。

之前在群里有看到过有人说没找到EasyCaching的相关介绍,这也是为什么要写这篇博客的原因。

下面就先简单介绍一下EasyCaching。

什么是EasyCaching

640?wx_fmt=png

EasyCaching,这个名字就很大程度上解释了它是做什么的,easy和caching放在一起,其最终的目的就是为了让我们大家在操作缓存的时候更加的方便。

它的发展大概经历了这几个比较重要的时间节点:

  1. 18年3月,在茶叔的帮助下进入了NCC

  2. 19年1月,镇汐大大提了很多改进意见

  3. 19年3月,NopCommerce引入EasyCaching (可以看这个 commit记录)

  4. 19年4月,列入awesome-dotnet-core(自己提pr过去的,有点小自恋。。)

在EasyCaching出来之前,大部分人应该会对CacheManager比较熟悉,因为两者的定位和功能都差不多,所以偶尔会听到有朋友拿这两个去对比。

为了大家可以更好的进行对比,下面就重点介绍EasyCaching现有的功能了。

EasyCaching的主要功能

EasyCaching主要提供了下面的几个功能

  1. 统一的抽象缓存接口

  2. 多种常用的缓存Provider(InMemory,Redis,Memcached,SQLite)

  3. 为分布式缓存的数据序列化提供了多种选择

  4. 二级缓存

  5. 缓存的AOP操作(able, put,evict)

  6. 多实例支持

  7. 支持Diagnostics

  8. 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)
{

services.AddEasyCaching(option =>
{

option.UseInMemory("default");
});
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{

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
{

private readonly IEasyCachingProvider _provider;

public ValuesController(IEasyCachingProvider provider)
{
this._provider = provider;
}


[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");


return "sync";
}


[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");


return "async";
}
}

还有一个要注意的地方是,如果用的get方法是带有查询的,它在没有命中缓存的情况下去数据库查询前,会有一个加锁操作,避免一个key在同一时刻去查了n次数据库,这个锁的生存时间和休眠时间是由配置中的LockMsSleepMs决定的。

分布式缓存的序列化选择

对于分布式缓存的操作,我们不可避免的会遇到序列化的问题.

目前这个主要是针对redis和memcached的。当然,对于序列化,都会有一个默认的实现是基于BinaryFormatter,因为这个不依赖于第三方的类库,如果没有指定其它的,就会使用这个去进行序列化的操作了。

除了这个默认的实现,还提供了三种额外的选择。Newtonsoft.Json,MessagePack和Protobuf。下面以在Redis的provider使用MessagePack为例,来看看它的用法。

services.AddEasyCaching(option=> 
{

option.UseRedis(config =>
{
config.DBConfig.Endpoints.Add(new ServerEndPoint("127.0.0.1", 6379));
}, "redis1")

.WithMessagePack();
});

不过这里需要注意的是,目前这些Serializer并不会跟着Provider走,意思就是不能说这个provider用messagepack,那个provider用json,只能有一种Serializer,可能这一个后面需要加强。

多实例支持

可能有人会问多实例是什么意思,这里的多实例主要是指,在同一个项目中,同时使用多个provider,包括多个同一类型的provider或着是不同类型的provider。

这样说可能不太清晰,再来举一个虚构的小例子,可能大家就会更清晰了。

现在我们的商品缓存在redis集群一中,用户信息在redis集群二中,商品评论缓存在mecached集群中,一些简单的配置信息在应用服务器的本地缓存中。

在这种情况下,我们想简单的通过IEasyCachingProvider来直接操作这么多不同的缓存,显然是没办法做到的!

这个时候想同时操作这么多不同的缓存,就要借助IEasyCachingProviderFactory来指定使用那个provider。

这个工厂是通过provider的名字来获取要使用的provider。

下面来看个例子。

我们先添加两个不同名字的InMemory缓存

services.AddEasyCaching(option =>
{

option.UseInMemory("m1");


config.UseInMemory(options =>
{
options.DBConfig = new InMemoryCachingOptions
{
SizeLimit = 100
};
}, "m2");
});

使用的时候

[Route("api/[controller]")]  
public class ValuesController : Controller
{
private readonly IEasyCachingProviderFactory _factory;

public ValuesController(IEasyCachingProviderFactory factory)
{
this._factory = factory;
}


[HttpGet]
[Route("")]
public string Get()
{

var provider_1 = _factory.GetCachingProvider("m1");

var provider_2 = _factory.GetCachingProvider("m2");




return $"multi instances";
}
}

上面这个例子中,provider_1和provider_2是不会互相干扰对方的,因为它们是不同的provider!

直观感觉,有点类似区域(region)的概念,可以这样去理解,但是严格意义上它并不是区域。

缓存的AOP操作

说起AOP,可能大家第一印象会是记录日志操作,把参数打一下,结果打一下。

其实这个在缓存操作中同样有简化的作用。

一般情况下,我们可能是这样操作缓存的。

public async Task<Product> GetProductAsync(int id)  
{
string cacheKey = $"product:{id}";

var val = await _cache.GetAsync<Product>(cacheKey);

if(val.HasValue)
return val.Value;

var product = await _db.GetProductAsync(id);

if(product != null)
_cache.Set<Product>(cacheKey, product, expiration);

return val;
}

如果使用缓存的地方很多,那么我们可能就会觉得烦锁。

我们同样可以使用AOP来简化这一操作。

public interface IProductService 
{
[EasyCachingAble(Expiration = 10)]
Task<Product> GetProductAsync(int id);
}

public class ProductService : IProductService
{
public Task<Product> GetProductAsync(int id)
{
return Task.FromResult(new Product { ... });
}
}

可以看到,我们只要在接口的定义上面加上一个Attribute标识一下就可以了。

当然,只加Attribute,不加配置,它也是不会生效的。下面以EasyCaching.Interceptor.AspectCore为例,添加相应的配置。

public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddScoped<IProductService, ProductService>();

services.AddEasyCaching(options =>
{
options.UseInMemory("m1");
});

return services.ConfigureAspectCoreInterceptor(options =>
{


options.CacheProviderName = "m1";
});
}

这两步就可以让你在调用方法的时候优先取缓存,没有缓存的时候会去执行方法。

下面再来说一下三个Attritebute的一些参数。

首先是三个通用配置

CacheKeyPrefix指定生成缓存键的前缀,正常情况下是用在修改和删除的缓存上
CacheProviderName可以指定特殊的provider名字
IsHightAvailability缓存相关操作出现异常时,是否还能继续执行业务方法

EasyCachingAble和EasyCachingPut还有一个同名和配置。

EasyCachingEvict有两个特殊的配置。

IsAll这个要搭配CacheKeyPrefix来用,就是删除这个前缀的所有key
IsBefore在业务方法执行之前删除缓存还是执行之后

支持Diagnostics

为了方便接入第三方的APM,提供了Diagnostics的支持,便于实现追踪。

下图是我司接入Jaeger的一个案例。

640?wx_fmt=png

二级缓存

二级缓存,多级缓存,其实在缓存的小世界中还算是一个比较重要的东西!

一个最为头疼的问题就是不同级的缓存如何做到近似实时的同步。

在EasyCaching中,二级缓存的实现逻辑大致就是下面的这张图。

640?wx_fmt=png

如果某个服务器上面的本地缓存被修改了,就会通过缓存总线去通知其他服务器把对应的本地缓存移除掉

下面来看一个简单的使用例子。

首先是添加nuget包。

dotnet add package EasyCaching.InMemory
dotnet add package EasyCaching.Redis
dotnet add package EasyCaching.HybridCache
dotnet add package EasyCaching.Bus.Redis

其次是添加配置。

services.AddEasyCaching(option =>
{

option.UseInMemory("m1");
option.UseRedis(config =>
{
config.DBConfig.Endpoints.Add(new Core.Configurations.ServerEndPoint("127.0.0.1", 6379));
config.DBConfig.Database = 5;
}, "myredis");


option.UseHybrid(config =>
{
config.EnableLogging = false;

config.TopicName = "test_topic";

config.LocalCacheProviderName = "m1";

config.DistributedCacheProviderName = "myredis";
});


option.WithRedisBus(config =>
{
config.Endpoints.Add(new Core.Configurations.ServerEndPoint("127.0.0.1", 6379));
config.Database = 6;
});
});

最后就是使用了。

[Route("api/[controller]")]  
public class ValuesController : Controller
{
private readonly IHybridCachingProvider _provider;

public ValuesController(IHybridCachingProvider provider)
{
this._provider = provider;
}


[HttpGet]
[Route("")]
public string Get()
{
_provider.Set(cacheKey, "val", TimeSpan.FromSeconds(30));

return $"hybrid";
}
}

如果觉得不清楚,可以再看看这个完整的例子EasyCachingHybridDemo。

Redis的特殊Provider

大家都知道redis支持多种数据结构,还有一些原子递增递减的操作等等。为了支持这些操作,EasyCaching提供了一个独立的接口,IRedisCachingProvider。

这个接口,目前也只支持了百分之六七十常用的一些操作,还有一些可能用的少的就没加进去。

同样的,这个接口也是支持多实例的,也可以通过IEasyCachingProviderFactory来获取不同的provider实例。

在注入的时候,不需要额外的操作,和添加Redis是一样的。不同的是,在使用的时候,不再是用IEasyCachingProvider,而是要用IRedisCachingProvider

下面是一个简单的使用例子。

[Route("api/mredis")]
public class MultiRedisController : Controller
{
private readonly IRedisCachingProvider _redis1;
private readonly IRedisCachingProvider _redis2;

public MultiRedisController(IEasyCachingProviderFactory factory)
{
this._redis1 = factory.GetRedisProvider("redis1");
this._redis2 = factory.GetRedisProvider("redis2");
}


[HttpGet]
public string Get()
{
_redis1.StringSet("keyredis1", "val");

var res1 = _redis1.StringGet("keyredis1");
var res2 = _redis2.StringGet("keyredis1");

return $"redis1 cached value: {res1}, redis2 cached value : {res2}";
}
}

除了这些基础功能,还有一些扩展性的功能,在这里要非常感谢yrinleung,他把EasyCaching和WebApiClient,CAP等项目结合起来了。感兴趣的可以看看这个项目EasyCaching.Extensions。

写在最后

以上就是EasyCaching目前支持的一些功能特性,如果大家在使用的过程中有遇到问题的话,希望可以积极的反馈,帮助EasyCaching变得越来越好。

如果您对这个项目有兴趣,可以在Github上点个Star,也可以加入我们一起进行开发和维护。

前段时间开了一个Issue用来记录正在使用EasyCaching的相关用户和案例,如果您正在使用EasyCaching,并且不介意透露您的相关信息,可以在这个Issue上面回复。

原文地址:https://www.cnblogs.com/catcher1994/p/10806607.html

.NET社区新闻,深度好文,欢迎访问公众号文章汇总 http://www.csharpkit.com 
640?wx_fmt=jpeg

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/316041.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[开源]OSharpNS - .net core 快速开发框架 - 简介

OSharpNS全称OSharp Framework with .NetStandard2.0&#xff0c;是一个基于.NetStandard2.0开发的一个.NetCore快速开发框架。这个框架使用最新稳定版的.NetCore SDK&#xff08;当前是.NET Core 2.2&#xff09;&#xff0c;对 AspNetCore 的配置、依赖注入、日志、缓存、实体…

Ozon Tech Challenge 2020 (Div.1 + Div.2, Rated, T-shirts + prizes!)

Ozon Tech Challenge 2020 (Div.1 Div.2, Rated, T-shirts prizes!) 题号题目知识点AKuroni and the GiftsBKuroni and Simple StringsCKuroni and Impossible CalculationDKuroni and the Celebration思维EKuroni and the Score Distribution构造FKuroni and the Punishmen…

Web 版 VS Code (Visual Studio Online) 即将来临!

今天&#xff08;北京时间 2019 年 5 月 7 日&#xff09;&#xff0c;在微软Build 2019开发者大会上&#xff0c;微软宣布了 Web 版本的 VS Code - Visual Studio Online。相信读者们对Web 版 VS Code 期待已久了。对 VS Code 熟悉的朋友应该知道&#xff0c;VS Code 是基于 E…

CF1305D Kuroni and the Celebration

CF1305D Kuroni and the Celebration 题意&#xff1a; 给你一棵有 n 个节点的树。对于每次询问&#xff0c;提交两个点&#xff0c;评测机会返回这两个点的 LCA。求树根。 询问格式为 ? u v&#xff0c;此时评测机会返回 u 和 v 的 LCA。 提交格式为 ! x&#xff0c;表示…

CF1305F Kuroni and the Punishment

CF1305F Kuroni and the Punishment 题意&#xff1a; 给定 n 个数。每次可以选择将一个数 1 或 -1 。求至少多少次操作使得整个序列都是正数且全部元素的 gcd>1 。 n<2e5,ai<1012n<2e5,a_{i}<10^{12}n<2e5,ai​<1012 题解&#xff1a; 首先不难想到&…

2019年了,C#发展的怎么样了呢?

C# 8.0我估计大多数程序员对于C# 5.0之后的改进都没有什么太多的认知&#xff0c;的确从C# 5.0开始C#已经没什么太多东西可以从其他语言借鉴&#xff0c;Anders的重心也开始逐步倾斜到TypeScript&#xff0c;所以从5.0引入async之后C#语言发展速度开始减缓了。C#6引入了大量的语…

微软宣布新命令行工具 Windows Terminal 和 WSL2

微软在 Build 2019 已经宣布推出名为Windows Terminal的新款命令行工具 , 这款工具可以访问多种环境的中心位置。例如可以直接访问PowerShell、CMD.EXE、Windows Linux子系统等等&#xff0c;开发者使用此工具可以简化工作。这款新工具支持多选项卡同时连接多个环境或服务器&a…

CF1305E Kuroni and the Score Distribution

CF1305E Kuroni and the Score Distribution 题意&#xff1a; 题解&#xff1a; 一开始想这个题&#xff0c;想法是一开始顺着填1&#xff0c;2&#xff0c;3…然后多删少补 如果1&#xff0c;2&#xff0c;3&#xff0c;4…这样顺延的填&#xff0c;对于akka_{k}kak​k可以…

微软 Build 2019:Windows 10 隐退,IE 重生,Azure 成主角

019 年 5 月 6 日&#xff0c;一年一度的微软 Build 开发者大会正式到来。作为本次大会最为重磅的 Keynote 环节&#xff0c;微软 CEO Satya Nadella 的开场演讲无疑展示了这家巨头对于未来科技发展趋势的洞察和判断&#xff0c;并且也能够让人更加清晰地看到微软在 Azure 云计…

牛客练习赛89

牛客练习赛89 题号题目知识点A牛牛吃米粒贪心B牛牛嚯可乐搜索C牛牛吃豆人思维D牛牛种小树思维完全背包E牛牛小数点思维推导F牛牛防疫情最小割

普通人看Build 2019 值得留心的亮点有哪些?

一场Keynote下来&#xff0c;是不是对纳德拉那套“Azure&#xff0c;azure&#xff0c;azure”&#xff0c;云云云的套路感觉有些乏味呢&#xff1f;当然这也不是你的问题&#xff0c;原本Build大会就是为开发者而设的&#xff0c;将近两个小时下来没看到什么能让自己眼前一亮的…

牛牛种小树

牛牛种小树 题意&#xff1a; 他打算用他得到的米粒去构造一棵有n个节点的树&#xff0c;并使得它的价值最大。 设f(d)表示树上度数为d的一个点能够获取的最大价值。则这棵树的价值为∑i1nf(di)\sum_{i1}^nf(d_{i})∑i1n​f(di​),其中did_{i}di​表示第i个点的度数 题解&am…

Build 2019:微软正式宣布 .NET 5

今天&#xff0c;我们宣布 .NET Core 3.0 之后的下一个版本将是 .NET 5 。这将是 .NET 系列的下一个重要版本。将来只会有一个 .NET &#xff0c;您将能够使用它来开发 Windows&#xff0c;Linux&#xff0c;macOS&#xff0c;iOS&#xff0c;Android&#xff0c;tvOS&#xff…

LGV定理

老早就听说&#xff0c;一直没学&#xff0c;今天遇到一个LGV比较裸的题&#xff0c;特地学习一下 选自oi-wiki 定义&#xff1a; e(u,v)表示u到v这条路径上所有边的边权之积(路径计数时&#xff0c;可以将边权都设为1),很多路径统计问题就是用到这一点 引理&#xff1a; 答案…

2019牛客多校Monotonic Matrix

Monotonic Matrix 题意&#xff1a; 问有多少个n * m的矩阵A满足一下情况&#xff1a;答案mod 1e97 矩阵A的所有元素∈{0,1,2}Ai,j<Ai1,jA_{i,j}<A_{i1,j}Ai,j​<Ai1,j​Ai,j<Ai,j1A_{i,j}<A_{i,j1}Ai,j​<Ai,j1​ 题解&#xff1a; 我们先看看这个式子…

WPF框架教程 | 从0到1:使用Caliburn.Micro(WPF和MVVM)开发简单的计算器

之前时间一直在使用Caliburn.Micro这种应用了MVVM模式的WPF框架做开发&#xff0c;是时候总结一下了。Caliburn.Micro(https://blog.csdn.net/lzuacm/article/details/78886436)是一个轻量级的WPF框架&#xff0c;简化了WPF中的不少用法&#xff0c;推荐做WPF开发时优先使用。真…

微软全都要!Win10引入真Linux内核

继将 Bash shell、原生 OpenSSH、WSL 引入 Windows&#xff0c;以及在微软商店提供 Ubuntu、SUSE Linux 和 Fedora 等发行版&#xff0c;正在举办的 Microsoft Build 2019 大会上&#xff0c;微软又宣布了一个重大的决定 —— 将完整的 Linux 内核引入 Windows 10。按照微软的说…

牛客练习赛89--牛牛防疫情

牛牛防疫情 题意&#xff1a; 牛牛用卖烤串赚的钱买了一款游戏&#xff0c;这款游戏的地图是一个 n*n 的网格&#xff0c;其中有 m 个地区存在感染源&#xff08;红色&#xff09;&#xff0c;其余地区为安全区&#xff08;白色&#xff09;。已知一个感染源可同时将与其相邻…

Kafka基本知识整理

首先Kafka是一个分布式消息队列中间件&#xff0c;Apache顶级项目&#xff0c;https://kafka.apache.org/ 高性能、持久化、多副本备份、横向扩展。生产者Producer往队列里发送消息&#xff0c;消费者Consumer从队列里消费消息&#xff0c;然后进行业务逻辑。应用场景主要有&…

牛客练习赛89——牛牛小数点(未解决)

牛牛小数点 题意&#xff1a; 题解&#xff1a; 本题先说结论&#xff1a; 对于一个数x2a∗5b∗px2^a*5^b*px2a∗5b∗p 如果p1,也就是质因子只有2和5&#xff0c;则x是不循环小数&#xff0c;即f(x)0如果p!1,则x是循环的&#xff0c;且循环开始于小数点后第1max{p2,p5p_{2},…