一文了解什么是EasyCaching(上)

发表于:2021-11-09 09:40

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:catcherwong    来源:掘金

  之前看到过有人说没找到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),我们将立即处理
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号