前言

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

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

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

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

什么是EasyCaching

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的一个案例。

二级缓存

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

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

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

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

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

首先是添加nuget包。

dotnet add package EasyCaching.InMemorydotnet add package EasyCaching.Redisdotnet add package EasyCaching.HybridCachedotnet 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 

一篇短文带您了解一下EasyCaching相关推荐

  1. Python是什么?一篇短文带你了解Python

    计算机程序设计语言 共2个含义 Python是一种计算机程序设计语言,由吉多·范罗苏姆创造,第一版发布于1991年,可以视之为一种改良的LISP.Python的设计哲学强调代码的可读性和简洁的语法.相 ...

  2. 36篇博文带你学完opencv :python+opencv进阶版学习笔记目录

    基础版学习笔记传送门 36篇博文带你学完opencv :python3+opencv学习笔记汇总目录(基础版) 进阶版笔记 项目 opencv进阶学习笔记1: 调用摄像头用法大全(打开摄像头,打开摄像 ...

  3. 用计算机弹奏曲子童年,5.这首曲子使我想起了我的童年.十.书面表达现在.计算机游戏非常盛行.这是一件好事还是一件坏事?请你用英语写一篇短文来陈述自己的观点.并说明理由.字数:80-120....

    5.这首曲子使我想起了我的童年.十.书面表达现在.计算机游戏非常盛行.这是一件好事还是一件坏事?请你用英语写一篇短文来陈述自己的观点.并说明理由.字数:80-120.[查看更多] 题目列表(包括答案和 ...

  4. 设计模式一网打尽,40余篇文章带你领略设计模式之美

    文章末尾附带GitHub开源下载地址. 该文章的最新版本已迁移至个人博客[比特飞],单击链接 设计模式一网打尽,40余篇文章带你领略设计模式之美 | .Net中文网 访问. 设计模式概述 20世纪80 ...

  5. 乐鑫esp8266学习rtos3.0笔记第4篇:带你捋一捋微信公众号 airkiss 配网 esp8266 并绑定设备的过程,移植并成功实现在 esp8266 rtos3.1 sdk。(附带demo)

    本系列博客学习由非官方人员 半颗心脏 潜心所力所写,仅仅做个人技术交流分享,不做任何商业用途.如有不对之处,请留言,本人及时更改. 1. Esp8266之 搭建开发环境,开始一个"hello ...

  6. 四篇文章带你快速入门Jetpck(中)之ViewModel,DataBinding

    文章目录 四篇文章带你快速入门Jetpck(中)之ViewModel,DataBinding Jetpack 官方推荐架构 ViewModel 添加依赖 创建ViewModel 初始化ViewMode ...

  7. 什么产品适合抖音广告?本篇文章带你来了解

    到底什么产品适合抖音广告?抖音广告类型的多样化使不计其数的广告主选择在抖音这个热门app上投放产品广告.不断有很多广告主提出疑问,自己的产品到底适不适合在抖音做营销.下面为您介绍适合在抖音做广告的产品 ...

  8. 处理71篇短文突破中考英语词汇

    71篇短文突破中考英语词汇 网上有很多版本 但是我不满意,不满意的几点,我的目的是给我4年级的儿子朗读 1.没有音标 2.没有单词解释 解决第1步:没有音标,参考前面文字英语加音标已经解决: 第2步没 ...

  9. python 统计单词个数和频次 和 70篇短文突破中考英语词汇 实用

    开始学习python,习题需要统计单词个数和频次.百度找到的代码好像都有问题.自己写了一个,调试通过. 环境:python: 3.9.1 64bit :  pycharm: 2020.2  电脑 wi ...

最新文章

  1. Caffe源码中layer文件分析
  2. Spring 中常用的设计模式对比
  3. Media Query在SAP Spartacus里的用途
  4. 一般将来时语法课教案_速看,如何在考场写出一篇脱颖而出的教案
  5. ubuntu下vim语法高亮问题
  6. 抢先试用ReSharper UnitRun™ 1.0
  7. linux ioctl 设备只读,linux – 尝试SSH时设备的ioctl不合适
  8. Python3多进程与多线程区别及使用(1.进程)
  9. 微信小程序开发学习笔记001--认识微信小程序,第一个微信小程序
  10. mysql 深入视图和索引
  11. 程序员风光背后:从零到BAT数据分析师靠的是什么?
  12. 我的网站被黑了,关键词被劫持,总结一下是怎么解决的。
  13. 百面机器学习——python实现二分类逻辑回归
  14. ubuntu 安装dep文件
  15. Laravel压缩图片
  16. 算法学习——图之有权图
  17. Centos7 安装VLC播放器
  18. java毕业生设计选课系统计算机源码+系统+mysql+调试部署+lw
  19. 计算机基础重点考题解析(二)
  20. 不懂技术,怎样制作手机电子书?

热门文章

  1. 如何在Twitter上阻止令人讨厌的“今日热门新闻@yourname”垃圾邮件
  2. icloud 购买存储空间_如何释放iCloud存储空间
  3. 计算机程序设计vb课后题,《VB程序设计》课后题答案
  4. 求连续序列的最大子序列和
  5. Java(C#)基础差异-语法
  6. pandas DataFrame 数据处理常用操作
  7. 清北·NOIP2017济南考前冲刺班 DAY1 morning
  8. bigpipe提升网站响应速度
  9. HUAWEI nova 青春版闪速快充,让追剧不再断电
  10. vim学习日志(5):vim下wimrc的配置,解决中文乱码问题