【问题标题】:Most Efficient Way Of Clearing Cache Using ASP.NET使用 ASP.NET 清除缓存的最有效方法
【发布时间】:2011-09-06 22:44:15
【问题描述】:

我正在构建一个由 ASP.NET/Umbraco 驱动的网站,该网站是通过实体框架驱动的非常自定义的数据,我们不得不缓存相当多的数据查询(例如按关键字搜索),因为它是一个繁忙的网站。

但是当用户创建一个新的数据条目时,我需要清除所有缓存的查询(搜索等),以便新条目在结果中可用。

所以在我的创建、删除和更新方法中,我调用了以下方法:

public static void ClearCacheItems()
{
    var enumerator = HttpContext.Current.Cache.GetEnumerator();

    while (enumerator.MoveNext())
    {
        HttpContext.Current.Cache.Remove(enumerator.Key.ToString());
    }
}

这真的很糟糕吗?我看不到我应该如何清除缓存的项目?

【问题讨论】:

    标签: c# asp.net entity-framework caching cache-control


    【解决方案1】:

    Cache 类的设计者很容易向它添加Clear 方法。但他们没有,这是设计使然 - 因此您的代码糟糕

    如果集合被修改,一个问题是枚举通过集合的线程含义。这会引发错误。

    您真的不需要清除整个缓存。如果服务器需要内存,它将清除它。缓存访问是按键(出于某种原因),因此您需要知道您要访问的内容。因此,如果您需要删除和项目,请一键完成。

    更新

    我的建议是以清除缓存非常容易的方式设计缓存。例如,按 ID 对缓存项进行分组(创建一个类来保存相关的缓存结果)并使用 ID 作为键。每当与该 ID 相关的内容发生更改时,请清除该 ID 的缓存。 简单,轻松。

    【讨论】:

      【解决方案2】:

      您使用的方法实际上是清除缓存的正确方法,您的代码中只有一个小“错误”。只要原始集合保持不变,枚举器才有效。因此,虽然代码可能大部分时间都可以工作,但在某些情况下可能会出现小错误。最好是使用下面的代码,它的作用基本相同,但不直接使用枚举器。

      List<string> keys = new List<string>();
      IDictionaryEnumerator enumerator = Cache.GetEnumerator();
      
      while (enumerator.MoveNext())
        keys.Add(enumerator.Key.ToString());
      
      for (int i = 0; i < keys.Count; i++)
        Cache.Remove(keys[i]);
      

      【讨论】:

      • 您的代码实际上并没有解决枚举可能更改的集合的问题。您可能已经缩短了可能发生这种情况的窗口,但keys.Add 与直接调用Remove 存在相同的问题,即在您枚举时集合可能会发生变化。
      • 是的,没有一个循环改变它用来枚举的集合。您首先收集一个集合中的所有键(并制作它们的副本),然后使用这些键从另一个集合中删除值。
      • 啊,我想问题是枚举正在使用缓存的另一个线程上发生变化,因此您仍然容易受到这些类型的枚举更改的影响。 http 缓存上的 MSDN 说该类是线程安全的,所以它可能不是问题。它没有具体提到枚举器是否是线程安全的(就像它在 concurrentdictionary 上一样)
      【解决方案3】:

      您是否考虑过使用缓存依赖项Here's the MSDN explanation 和一些花絮:

      在存储在 ASP.NET 应用程序的 Cache 对象中的项与文件、缓存键、其中一个数组或另一个 CacheDependency 对象之间建立依赖关系。 CacheDependency 类监控依赖关系,当其中任何一个发生变化时,缓存的项都会被自动移除。

      // Insert the cache item.
      CacheDependency dep = new CacheDependency(fileName, dt);
      cache.Insert("key", "value", dep);
      
      // Check whether CacheDependency.HasChanged is true.
      if (dep.HasChanged)
        Response.Write("<p>The dependency has changed.");  
      else Response.Write("<p>The dependency has not changed.");
      

      this very enthusiastic fellow 对此做了更多解释。

      【讨论】:

        【解决方案4】:

        是的,永远不要遍历缓存并删除项目。它会给你一个痛苦的世界(我最近经历过这个)。因为你会得到一个错误“集合已修改,枚举可能无法执行”。

        我还有一个用于搜索的缓存,并且我没有设置过期时间 - 我在需要时手动失效。

        诀窍是将数据保存在“桶”中。这个“桶”通常是父标识符,或者如果你想进入数据库术语,外键。

        因此,如果您需要清除给定“客户”的所有“订单”,缓存键应该是 CustomerId。就我而言,我缓存了一个ReadOnlyCollection&lt;T&gt;。所以我不需要循环,只需将其删除,然后添加新副本即可。

        另外,为了双重安全 - 我使用 ReaderWriterLockSlim 来使缓存无效。当我知道它需要失效时,我首先调用数据库,获取写锁,刷新并刷新相关缓存,然后关闭写锁。

        这样就无缝了。

        【讨论】:

        【解决方案5】:

        只为一个特定的功能域清除整个 ASP.NET 缓存似乎有点矫枉过正。

        您可以创建一个中间对象,并将所有缓存的查询存储在其中。这个对象可能只是一个字典对象的包装器。所有查询都应使用此对象,而不是直接使用 ASP.NET 缓存。

        然后在需要时将此对象添加到 ASP.NET 缓存中。当您需要清除查询时,只需获取此对象并清除底层字典即可。这是一个示例实现:

            public sealed class IntermediateCache<T>
            {
                private Dictionary<string, T> _dictionary = new Dictionary<string, T>();
        
                private IntermediateCache()
                {
                }
        
                public static IntermediateCache<T> Current
                {
                    get
                    {
                        string key = "IntermediateCache|" + typeof(T).FullName;
                        IntermediateCache<T> current = HttpContext.Current.Cache[key] as IntermediateCache<T>;
                        if (current == null)
                        {
                            current = new IntermediateCache<T>();
                            HttpContext.Current.Cache[key] = current;
                        }
                        return current;
                    }
                }
        
                public T Get(string key, T defaultValue)
                {
                    if (key == null)
                        throw new ArgumentNullException("key");
        
                    T value;
                    if (_dictionary.TryGetValue(key, out value))
                        return value;
        
                    return defaultValue;
                }
        
                public void Set(string key, T value)
                {
                    if (key == null)
                        throw new ArgumentNullException("key");
        
                    _dictionary[key] = value;
                }
        
                public void Clear()
                {
                    _dictionary.Clear();
                }
            }
        

        如果我的查询是这样表示的:

            public class MyQueryObject
            {
               ....
            }
        

        然后,我会像这样使用“区域”缓存:

        // put something in this intermediate cache
        IntermediateCache<MyQueryObject>.Current.Set("myKey", myObj);
        
        // clear this cache
        IntermediateCache<MyQueryObject>.Current.Clear();
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2018-10-21
          • 2021-07-07
          • 2014-02-13
          • 2014-02-22
          • 2011-08-01
          • 2021-06-08
          • 2022-01-19
          相关资源
          最近更新 更多