如何从.NET MemoryCache中过期许多项目

Mik*_*scu 5 .net caching memorycache

从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++;                
  }
}
Run Code Online (Sandbox Code Playgroud)

Cyb*_*axs 9

建议从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
Run Code Online (Sandbox Code Playgroud)

至于大多数情况,您还可以创建自定义缓存实现或派生的更改监视器类型.

未经测试,但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
Run Code Online (Sandbox Code Playgroud)

因此,需要225毫秒才能删除我30万件中的25%(再次在我的笔记本电脑上,3年前).你真的需要更快的东西吗?请注意,父项最后会被删除.此解决方案的优点:

  • 无效项目将从缓存中删除
  • 你接近缓存(更少的callstack,更少的演员,更少的间接)
  • remove callback允许您根据需要自动重新加载缓存项
  • 如果缓存破解程序到期,则回调在另一个不会影响asp.net请求的线程上.

我发现你的实现是相关的,并将在以后记住它.您的选择应该基于您的场景:项目数量,缓存项目的大小,命中率,依赖项数量......还要保留太多数据,缓存通常很慢并且可能增加驱逐的可能性.