5

MemoryCache インスタンスから多数の項目を削除するための推奨される方法は何ですか?

この質問に関する議論に基づいて、アプリケーション全体に単一のキャッシュを使用し、キーに名前空間を使用して、複数の論理タイプのアイテムを同じインスタンスにキャッシュできるようにすることが推奨されるアプローチのようです。

ただし、単一のキャッシュ インスタンスを使用すると、キャッシュから多数のアイテムを期限切れにする (削除する) という問題が残ります。特に、特定の論理タイプのすべてのアイテムを期限切れにする必要がある場合。

現時点で私が見つけた唯一の解決策は、この質問への回答に基づいていましたが、キャッシュ内のすべてのキーを列挙し、名前空間をテストする必要があるため、パフォーマンスの観点からはあまり良くありません。時間がかかる!

現時点で思いついた唯一の回避策は、キャッシュ内のすべてのオブジェクトにバージョン番号を付けた薄いラッパーを作成し、オブジェクトがアクセスされるたびに、キャッシュされたバージョンが現在のバージョンと一致しない場合は破棄することです。したがって、特定のタイプのすべてのアイテムをクリアする必要があるときはいつでも、現在のバージョン番号を上げて、キャッシュされたすべてのアイテムを無効にします。

上記の回避策はかなりしっかりしているようです。しかし、同じことを達成するためのより簡単な方法がないのではないかと思わずにはいられません。

これは私の現在の実装です:

private class MemCacheWrapper<TItemType> 
              where TItemType : class
{            
  private int _version;
  private Guid _guid;
  private System.Runtime.Caching.ObjectCache _cache;

  private class ThinWrapper
  {
     public ThinWrapper(TItemType item, int version)
     {
        Item = item;
        Version = version;
     }

     public TItemType Item { get; set; }
     public int Version { get; set; }
  }

  public MemCacheWrapper()
  {
      _cache = System.Runtime.Caching.MemoryCache.Default;
      _version = 0;
      _guid = Guid.NewGuid();
  }

  public TItemType Get(int index)
  {                
     string key = string.Format("{0}_{1}", _guid, index);

     var lvi = _cache.Get(key) as ThinWrapper;

     if (lvi == null || lvi.Version != _version)
     {
         return null;
     }

     return lvi.Item;
  }

  public void Put(int index, TItemType item)
  {                
     string key = string.Format("{0}_{1}", _guid, index);

     var cip = new System.Runtime.Caching.CacheItemPolicy();
     cip.SlidingExpiration.Add(TimeSpan.FromSeconds(30));

     _cache.Set(key, new ThinWrapper(item, _version), cip);
  }

  public void Clear()
  {
     _version++;                
  }
}
4

4 に答える 4

11

MemoryCache インスタンスから多数のアイテムを削除するために私が推奨する方法は、 ChangeMonitor、特にCacheEntryChangeMonitorを使用することです。

キャッシュ エントリへの変更を監視するために実装できる ChangeMonitor 型を表す基本クラスを提供します。

そのため、キャッシュ アイテム間の依存関係を処理できます。

非常に基本的な例は

    var cache = MemoryCache.Default;
    cache.Add("mycachebreakerkey", "mycachebreakerkey", DateTime.Now.AddSeconds(15));

    CacheItemPolicy policy = new CacheItemPolicy();
    policy.ChangeMonitors.Add(cache.CreateCacheEntryChangeMonitor(new string[] { "mycachebreakerkey" }));
    // just to debug removal
    policy.RemovedCallback = args => { Debug.WriteLine(args.CacheItem.Key + "-->" + args.RemovedReason); };
    cache.Add("cacheKey", "cacheKey", policy);

    // after 15 seconds mycachebreakerkey will expire
    // dependent item "cacheKey" will also be removed

ほとんどの場合、カスタム キャッシュ実装または派生変更監視タイプを作成することもできます。

テストされていませんが、CreateCacheEntryChangeMonitor は、MemoryCache 間の依存関係を作成できることを示唆しています。

編集

ChangeMonitor は、ランタイム キャッシュ内のコンテンツを無効にする .net の方法です。ここでの無効化とは、キャッシュから削除することを意味します。ファイルの変更を監視するために、SqlDependency またはいくつかの asp.net コンポーネントによって使用されます。したがって、このソリューションはスケーラブルだと思います。

これは、私のラップトップで実行する非常に単純なベンチマークです。

        const int NbItems = 300000;

        var watcher = Stopwatch.StartNew();
        var cache = MemoryCache.Default;

        var breakerticks = 0L;
        var allticks = new List<long>();

        cache.Add("mycachebreakerkey", "mycachebreakerkey", new CacheItemPolicy() { RemovedCallback = args => { breakerticks = watcher.ElapsedTicks; } });

        foreach (var i in Enumerable.Range(1, NbItems))
        {
            CacheItemPolicy policy = new CacheItemPolicy();
            if (i % 4 == 0)
                policy.ChangeMonitors.Add(cache.CreateCacheEntryChangeMonitor(new string[] { "mycachebreakerkeyone" }));
            policy.RemovedCallback = args => { allticks.Add(watcher.ElapsedTicks); };// just to debug removal
            cache.Add("cacheKey" + i.ToString(), "cacheKey", policy);
        }

        cache.Remove("mycachebreakerkey");
        Trace.WriteLine("Breaker removal=>" + TimeSpan.FromTicks(breakerticks).TotalMilliseconds);
        Trace.WriteLine("Start removal=>" + TimeSpan.FromTicks(allticks.Min()).TotalMilliseconds);
        Trace.WriteLine("End removal=>" + TimeSpan.FromTicks(allticks.Max()).TotalMilliseconds);
        Trace.WriteLine(cache.GetCount());

        // Trace
        // Breaker removal: 225,8062 ms
        // Start removal: 0,251 ms
        // End removal: 225,7688 ms
        // 225000 items

したがって、300,000 個のアイテムの 25% を削除するのに 225 ミリ秒かかります (これも 3 歳のラップトップで)。本当にもっと速いものが必要ですか? 親は最後に削除されることに注意してください。このソリューションの利点:

  • 無効化されたアイテムはキャッシュから削除されます
  • キャッシュに近い(コールスタックが少ない、キャストが少ない、間接性が少ない)
  • remove コールバックを使用すると、必要に応じてキャッシュ アイテムを自動リロードできます
  • キャッシュブレーカーが期限切れになった場合、コールバックは asp.net リクエストに影響を与えない別のスレッドにあります。

あなたの実装は適切だと思いますので、後で覚えておきます。選択は、シナリオに基づいて行う必要があります。アイテムの数、キャッシュ アイテムのサイズ、ヒット率、依存関係の数などです。また、あまりにも多くのデータを保持すると、キャッシュは一般的に遅くなり、エビクションの可能性が高くなる可能性があります。

于 2013-12-17T14:44:01.183 に答える
2

この投稿、具体的には、Thomas F. Abrahamが投稿した回答を確認してください。キャッシュ全体または名前付きサブセットをクリアできるソリューションがあります。

ここで重要なことは次のとおりです。

// Cache objects are obligated to remove entry upon change notification.
base.OnChanged(null);

私はこれを自分で実装しましたが、すべてうまくいくようです。

于 2014-04-03T10:51:25.687 に答える