EasyCaching-シンプルで効率的な. NETキャッシュパッケージ

EasyCaching-シンプルで効率的な. NETキャッシュパッケージ

EasyCaching、名前はそれが何をするかを大きく説明しています。easyとcachingを一緒に置き、その究極の目的は、キャッシュを操作するときに私たち全員をより便利にすることです。

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

前のページ

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

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

以前、グループの誰かがEasyCachingの紹介を見つけられなかったと言っているのを見たことがあります。

EasyCachingを簡単に紹介します。

Easycachingとは

img

EasyCaching、名前はそれが何をするかを大きく説明しています。easyとcachingを一緒に置き、その究極の目的は、キャッシュを操作するときに私たち全員をより便利にすることです。

その開発は、おそらく以下の重要な時期を経てきました。

  1. 3 月18日、ティーおじさんの助けを借りてNCCに入社。
  2. 1 月19日、多くの改良が行われた。
  3. 19 年 3 月,NopCommerce 引入 EasyCaching (可以看这个 commit 记录)
  4. 19 年 4 月,列入awesome-dotnet-core(自己提 pr 过去的,有点小自恋。。)

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

より良い比較をするために、EasyCachingの既存の機能を強調します。

EasyCachingの主な機能

EasyCachingは以下の機能を提供する。

  1. 統合抽象キャッシュインターフェイス
  2. 多くの一般的なキャッシュプロバイダInMemory、Redis、Memcached、SQLite
  3. 分散キャッシュのデータ直列化にの选択肢を提供
  4. セカンダリキャッシュ
  5. キャッシュされたAOP 操作able、put、evict
  6. 複数のインスタンスのサポート
  7. 診断診断のサポート
  8. Redisの特別プロバイダ

もちろん、これらの8つの他にも、ここでは説明しません。

以下に、上記の8つの機能を紹介します。

統合抽象キャッシュインターフェイス

キャッシュ自体はデータソースとみなすことができ、CURD操作の束を含むため、統一された抽象インターフェイスがあります。インターフェースプログラミング指向では、EasyCachingはいくつかの簡単な実装を提供しますが、あなたが望むなら、独自のプロバイダを実装することができます。

キャッシュ操作については、現在以下のものが提供されており、基本的には同期操作と非同期操作があります。

  • 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を使用してください)

名前の定義から、彼らが何をしているかがわかるはずで、ここでは展開しません。

複数の一般的なキャッシュ·プロバイダ

これらのプロバイダをローカルキャッシュと分散キャッシュの2つのカテゴリに分けます。

現在の実績は以下の5つ。

  • ローカルキャッシュ、In Memory、SQLite
  • 分散キャッシュ,StackExchange.Redis,csredis,EnyimMemcachedCore

それらの使用は非常に簡単です。以下ではInMemoryというを例にする.

まず、対応するパッケージを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 次数据库,这个锁的生存时间和休眠时间是由配置中的LockMsSleepMs决定的。

分散キャッシュのシリアル化選択

分散キャッシュの操作に対して、我々は必然的に直列化の問題に遭遇する。

これは主にRedisとmemcachedのためです。もちろん、シリアライズのデフォルト実装は BinaryFormatter に基づいています。これはサードパーティ製のライブラリに依存しないため、他のものが指定されていない場合はこれをシリアライズに使用します。

このデフォルトの実装に加えて、3つの追加オプションが提供されます。Newtonsoft. Json、Message Pack、Protobuf。RedisのプロバイダでMessage Packを使用した例を見てみましょう。

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はプロバイダに従わないことに注意することが重要です。つまり、このプロバイダはmessagepackを使用し、そのプロバイダはJSONを使用し、Serializerは1種類しかありません。

複数のインスタンスのサポート

マルチインスタンスとは何かと思うかもしれませんが、ここでのマルチインスタンスとは、同じタイプの複数のプロバイダまたは異なるタイプのプロバイダを含む、同じプロジェクト内で複数のプロバイダを同時に使用することを意味します。

これはあまり明確ではないかもしれませんが、架空の小さな例を挙げると、誰もがより明確になるかもしれません。

これで、商品キャッシュはRedisクラスタ1に、ユーザー情報はRedisクラスタ2に、商品レビューはメカドクラスタに、いくつかの簡単な設定情報はアプリケーションサーバーのローカルキャッシュにあります。

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

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

この工場は、使用するプロバイダを提供者の ** 名前 ** で取得します。

以下に例を示す。

2つの異なる名前のInMemoryキャッシュを追加します。

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

    // 指定当前provider的名字为m2
    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;
    }

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

上の例では、provider_1とprovider_2は異なるプロバイダであるため、互いに干渉しません。

直感的には、少し領域(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,不加配置,它也是不会生效的。下面以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";
    });
}

これらの2つのステップにより、メソッドを呼び出すときにキャッシュを優先し、キャッシュがないときにメソッドを実行できます。

以下は、3つの属性のパラメータの一部です。

1つ目は、3つの共通構成

コンフィギュレーション名の設定 説明書の作成
CacheKeyPrefix 生成されたキャッシュキーのプレフィックスを指定します。通常は変更および削除されたキャッシュに使用されます。
CacheProviderName 特定のプロバイダ名を指定できます。
IsHightAvailability キャッシュ関連操作に例外が発生した場合でも、ビジネスメソッドを続行できるかどうか

EasyCachingAbleとEasyCachingPutには同じ名前の設定もあります。

コンフィギュレーション名の設定 説明書の作成
Expiration キーの有効期限(秒単位)

EasyCachingEvictには2つの特別な設定があります。

コンフィギュレーション名の設定 説明書の作成
IsAll これはCacheKeyPrefixで使用され、プレフィックスのすべてのキーを削除します。
IsBefore キャッシュを削除するには、ビジネス·メソッドの実行前か実行後か

診断診断のサポート

サードパーティ製APMへのアクセスを容易にするために、診断サポートが提供され、追跡が容易になります。

以下は、Jaegerとの契約の例です。

img

セカンダリキャッシュ

セカンダリキャッシュ、マルチレベルキャッシュは、実際にはキャッシュの小さな世界では比較的重要なものです!

最大の頭痛の種の1つは、異なるレベルのキャッシュをほぼリアルタイムで同期する方法です。

EasyCachingでは、セカンダリキャッシュの実装ロジックは以下のようになります。

img

あるサーバのローカルキャッシュが変更された場合、キャッシュバスを介して他のサーバにローカルキャッシュを削除するように通知します。

以下に簡単な使用例を示す。

まず、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 =>
{
    // 添加两个基本的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;
    });
});

最後に使用されます。

[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

Redisの特別プロバイダ

Redisは様々なデータ構造をサポートしており、アトミックなインクリメントやデクリメント演算などもある。これらの操作をサポートするために、EasyCachingは独立したインターフェイスIRedis Caching Providerを提供します。

このインターフェースは、現在、一般的に使用される操作の60 ~ 70%しかサポートしておらず、使用する可能性のあるものは追加されません。

同样的,这个接口也是支持多实例的,也可以通过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");
    }

    // 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

最後に書いた。

上記はEasyCachingが現在サポートしている機能の一部です。使用中に問題がある場合は、EasyCachingがますます良くなるのを助けるために肯定的なフィードバックを期待しています。

このプロジェクトに興味がある場合は、GitHubでStarをクリックするか、開発とメンテナンスに参加してください。

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

img

img

この記事が良いと思う場合は、右下の [推奨] ボタンをクリックすることができます。あなたのサポートは、私が書き続け、共有するための最大の動機です!

作者:Catcher Wong ( 黄文清 )

出典http//catcher1994.cnblogs.com/

注意事項:この記事の著作権は著者とブログパークに帰属し、転載を歓迎しますが、著者の同意なしにこの声明を保持し、記事ページの明白な場所に元のリンクを与えなければなりません。あなたのブログで間違いを見つけた場合、またはより良い提案、アイデアがある場合は、私に連絡してください!!個人的に連絡したい場合は、個人的にメールまたはWeChatを追加してください。

Keep Exploring

延伸阅读

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

バージョン別の. NETサポート状況(250 7 0 7更新)

仮想マシンとテストマシンを使用して、各バージョンのオペレーティングシステムの. NETサポートをテストします。オペレーティングシステムのインストール後、対応するランタイムを測定し、スターダストエージェントをパスとして実行できます。

继续阅读
同分类 / 同标签 2026/02/07

AOTの使用経験

プロジェクトの最初から、新しい機能が追加されたり、新しい構文が使用されたりするたびに、AOTリリーステストを行うという良い習慣を身につける必要があります。

继续阅读