EasyCaching: A simple and efficient. NET cache package

EasyCaching: A simple and efficient. NET cache package

EasyCaching, the name largely explains what it does. The ultimate purpose of easy and caching is to make it more convenient for all of us to operate caching.

最后更新 11/5/2023 11:14 PM
Catcher Wong
预计阅读 16 分钟
分类
.NET
标签
.NET C#

preface

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

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

I have previously seen someone in the group saying that they couldn't find a relevant introduction to EasyCaching, which is why I wrote this blog.

Here is a brief introduction to EasyCaching.

What is EasyCaching

img

EasyCaching, the name largely explains what it does. The ultimate purpose of easy and caching is to make it more convenient for all of us to operate caching.

Its development probably went through these important time nodes:

  1. In March 2018, he entered the NCC with the help of Uncle Tea
  2. In January 2019, Zhen Xi put forward many suggestions for improvement
  3. 19 年 3 月,NopCommerce 引入 EasyCaching (可以看这个 commit 记录)
  4. 19 年 4 月,列入awesome-dotnet-core(自己提 pr 过去的,有点小自恋。。)

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

In order for everyone to better compare, the following will focus on the existing functions of EasyCaching.

Main functions of EasyCaching

EasyCaching mainly provides the following functions

  1. Unified abstract cache interface
  2. Various commonly used caching providers (InMemory, Redis, Memcached, SQLite)
  3. Provides multiple options for data serialization for distributed caching
  4. second level cache
  5. cached AOP operations (able, put, evict)
  6. Multiple instance support
  7. Support Diagnostics
  8. Special Provider for Redis

Of course, in addition to these eight, there are also some smaller ones that will not be listed here for explanation.

The following is a brief introduction to the above eight functions.

Unified abstract cache interface

Cache itself can also be regarded as a data source and contains a bunch of CURD operations, so there will be a unified abstract interface. For interface-oriented programming, although EasyCaching provides some simple implementations that may not meet your needs, as long as you want, you can implement your own provider without a word of disagreement.

For caching operations, the following are currently provided, and basically there are synchronous and asynchronous operations.

  • 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 (this will be obsolete later, just use set directly)

From the definition of their names, we should know what they did, so we will not continue here.

Various commonly used caching providers

We will divide these providers into two categories, one is local caching and the other is distributed caching.

There are five current implementations

  • Local Cache, InMemory, SQLite
  • Distributed caching, StackExchange. Redis, csredis, EnyimMemcachedCore

Their usage is very simple. The following is an example of InMemory as a provider.

The first is to install the corresponding package through nuget.

dotnet add package EasyCaching.InMemory

The second is to add configuration

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();
}

Example of configuration files

"easycaching": {
    "inmemory": {
        "MaxRdSecond": 120,
        "EnableLogging": false,
        "LockMs": 5000,
        "SleepMs": 300,
        "DBConfig":{
            "SizeLimit": 10000,
            "ExpirationScanFrequency": 60
        }
    }
}

关于配置,这里有必要说明一点,那就是MaxRdSecond的值,因为这个把老猫子大哥坑了一次,所以要拎出来特别说一下,这个值的作用是预防在同一时刻出现大批量缓存同时失效,为每个 key 原有的过期时间上面加了一个随机的秒数,尽可能的分散它们的过期时间,如果您的应用场景不需要这个,可以将其设置为 0。

The last word is use.

[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 次数据库,这个锁的生存时间和休眠时间是由配置中的LockMsSleepMs决定的。

Serialization options for distributed caching

For distributed caching operations, we will inevitably encounter serialization problems.

Currently, this is mainly for redis and memcached. Of course, for serialization, there will always be a default implementation based on ** BinaryFormatter **, because this does not rely on third-party class libraries. If no other one is specified, this will be used for serialization operations.

In addition to this default implementation, three additional options are available. Newtonsoft.Json, MessagePack and Protobuf. Let's take an example of using MessagePack on Redis's provider to see its usage.

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()
    ;
});

However, what should be noted here is that at present, these Serializers will not follow providers, which means that one provider cannot use messagepack and that provider uses json. There can only be one Serializer, and this one may need to be strengthened later.

Multiple instance support

Some people may ask what multiple instances mean. The term multiple instances here mainly means that multiple providers are used at the same time in the same project, including multiple providers of the same type or different types.

It may not be clear to say it this way, but if you give a small imaginary example, you may have a clearer understanding.

Now our products are cached in redis cluster 1, user information is in redis cluster 2, product reviews are cached in mecached cluster, and some simple configuration information is cached in the local cache of the application server.

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

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

This factory obtains the provider to use through the provider's ** name **.

Here is an example.

Let's first add two InMemory caches with different names

services.AddEasyCaching(option =>
{
    // 指定当前provider的名字为m1
    option.UseInMemory("m1");

    // 指定当前provider的名字为m2
    config.UseInMemory(options =>
    {
        options.DBConfig = new InMemoryCachingOptions
        {
            SizeLimit = 100
        };
    }, "m2");
});

when using

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

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

    // GET api/values
    [HttpGet]
    [Route("")]
    public string Get()
    {
        // 获取名字为m1的provider
        var provider_1 = _factory.GetCachingProvider("m1");
        // 获取名字为m2的provider
        var provider_2 = _factory.GetCachingProvider("m2");

        // provider_1.xxx
        // provider_2.xxx

        return $"multi instances";
    }
}

In the above example, provider_1 and provider_2 will not interfere with each other because they are different providers!

Intuitive feeling, it is a bit similar to the concept of a region, which can be understood in this way, but in the strict sense it is not a region.

cached AOP operations

Speaking of AOP, your first impression may be to record logging operations, type parameters, and type results.

In fact, this also has a simplified effect in caching operations.

In general, this is how we operate caching.

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;
}

If we use caching in a lot of places, then we may find it annoying.

We can also use AOP to simplify this operation.

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 { ... });
    }
}

As you can see, we just need to add an attribute to the interface definition.

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

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

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

    return services.ConfigureAspectCoreInterceptor(options =>
    {
        // 可以在这里指定你要用那个provider
        // 或者在Attribute上面指定
        options.CacheProviderName = "m1";
    });
}

These two steps allow you to take the cache first when calling a method, and execute the method when there is no cache.

Let's talk about some of the parameters of the three attributes.

The first are three common configurations

configuration name description
CacheKeyPrefix Specifies the prefix for the generated cache key, which is normally used on modified and deleted caches
CacheProviderName Special provider names can be specified
IsHightAvailability Can we continue to execute business methods when exceptions occur in cache-related operations

EasyCachingAbility and EasyCachingPut also have a configuration with the same name.

configuration name description
Expiration Expiration time of key, in seconds

EasyCachingEvict has two special configurations.

configuration name description
IsAll This should be used with CacheKeyPrefix, which means deleting all keys of this prefix.
IsBefore Delete cache before or after business method execution

Support Diagnostics

In order to facilitate access to third-party APM, Diagnostics support is provided to facilitate tracking.

The figure below shows a case of our company accessing Jaeger.

img

second level cache

Second-level caching and multi-level caching are actually a relatively important thing in the small world of caching!

One of the most troublesome issues is how to synchronize caches at different levels in near-real time.

In EasyCaching, the implementation logic of L2 caching is roughly the following figure.

img

If the local cache on a certain server is modified, it will notify other servers through the cache bus to remove the corresponding local cache **.

Let's take a simple example of usage.

The first is to add the nuget package.

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

The second is to add configuration.

services.AddEasyCaching(option =>
{
    // 添加两个基本的provider
    option.UseInMemory("m1");
    option.UseRedis(config =>
    {
        config.DBConfig.Endpoints.Add(new Core.Configurations.ServerEndPoint("127.0.0.1", 6379));
        config.DBConfig.Database = 5;
    }, "myredis");

    //  使用hybird
    option.UseHybrid(config =>
    {
        config.EnableLogging = false;
        // 缓存总线的订阅主题
        config.TopicName = "test_topic";
        // 本地缓存的名字
        config.LocalCacheProviderName = "m1";
        // 分布式缓存的名字
        config.DistributedCacheProviderName = "myredis";
    });

    // 使用redis作为缓存总线
    option.WithRedisBus(config =>
    {
        config.Endpoints.Add(new Core.Configurations.ServerEndPoint("127.0.0.1", 6379));
        config.Database = 6;
    });
});

Finally, it was used.

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

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

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

        return $"hybrid";
    }
}

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

Special Provider for Redis

Everyone knows that redis supports multiple data structures, as well as some atomic increment and decrement operations, etc. To support these operations, EasyCaching provides a separate interface, IRedisCachingProvider.

This interface currently only supports sixty to seventy percent of commonly used operations, and there are still some that may be less used and have not been added.

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

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

The following is a simple example of use.

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

    // GET api/mredis
    [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

written in the end

The above are some of the features currently supported by EasyCaching. If you encounter problems while using it, I hope you can provide positive feedback to help EasyCaching become better and better.

If you are interested in this project, you can click on a Star on GitHub, or you can join us for development and maintenance.

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

img

img

If you think this article is not bad or has something to gain, you can click the [Recommended] button in the lower right corner, because your support is my greatest motivation to continue writing and sharing!

作者:Catcher Wong ( 黄文清 )

Source: http://www.example.com

Statement: The copyright of this article belongs to the author and the blog park. It is welcome to reprint it. However, this statement must be retained without the author's consent, and a link to the original text must be given in an obvious position on the article page. Otherwise, the right to pursue legal responsibility is reserved. If you find an error in your blog, or have better suggestions or ideas, please contact me in time!! If you want to communicate with me privately, you can send a private message or add me on WeChat.

Keep Exploring

延伸阅读

更多文章
同分类 / 同标签 4/22/2026

Support for. NET by operating system versions (250707 update)

Use virtual machines and test machines to test the support of each version of the operating system for. NET. After installing the operating system, it is passed by measuring the corresponding running time of the installation and being able to run the Stardust Agent.

继续阅读
同分类 / 同标签 2/7/2026

Summary of experience in using AOT

From the very beginning of project creation, you should develop a good habit of conducting AOT release testing in a timely manner whenever new features are added or newer syntax is used.

继续阅读