从 MemoryCache 实例中删除大量项目的推荐方法是什么?
基于围绕 this question 的讨论似乎首选方法是对整个应用程序使用单个缓存,并使用命名空间作为键,以允许在同一实例中缓存多个逻辑类型的项目。
但是,使用单个缓存实例会带来缓存中大量项目过期(删除)的问题。特别是在某种逻辑类型的所有项目都必须过期的情况下。
目前我找到的唯一解决方案是基于 answer to this question但从性能的角度来看,这确实不是很好,因为您必须枚举缓存中的所有键,并测试命名空间,这可能非常耗时!
我目前想到的唯一解决方法是为缓存中的所有对象创建一个带有版本号的瘦包装器,并且每当访问对象时,如果缓存的版本与版本号不匹配,则丢弃它当前版本。因此,每当我需要清除某种类型的所有项目时,我都会提高当前版本号,使所有缓存的项目无效。
上面的解决方法看起来相当可靠。但我忍不住想知道是否有更直接的方法来完成同样的任务?
这是我当前的实现:
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++;
}
}
最佳答案
我建议从 MemoryCache 实例中删除大量项目的方法是使用 ChangeMonitor ,特别是CacheEntryChangeMonitor .
Provides a base class that represents a ChangeMonitor type that can be implemented in order to monitor changes to cache entries.
因此,它允许我们处理缓存项之间的依赖关系。
一个非常基本的例子是
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 方法。 Invalidate 这里的意思是=从缓存中删除。 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 年)。您真的需要更快的东西吗?请注意,父级在最后被删除。该解决方案的优点:
- 无效的项目将从缓存中删除
- 您接近缓存(更少的调用堆栈、更少的转换、更少的间接)
- 删除回调允许您在需要时自动重新加载缓存项
- 如果缓存破坏器过期,则回调将在另一个线程上进行,这不会影响 ASP.NET 请求。
我发现您的实现是相关的,并将在以后记住它。您的选择应该基于您的场景:项目数量、缓存项目大小、命中率、依赖项数量……此外,保留太多数据会导致缓存速度通常很慢,并且会增加驱逐的可能性。
关于.net - 如何使 .NET MemoryCache 中的许多项目过期,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19017760/