在.Net中使用ObjectCache缓存对象,并具有到期时间

时间:2015-09-05 14:14:03

标签: c# .net wcf caching asmx

我陷入困境。 我的代码如下:

更新:它不是关于如何使用数据缓存,我已经在使用它和它的工作,它关于扩展它所以方法不会在到期时间之间调用和从外部源获取新数据

object = (string)this.GetDataFromCache(cache, cacheKey);

if(String.IsNullOrEmpty(object))
{
  // get the data. It takes 100ms
  SetDataIntoCache(cache, cacheKey, object, DateTime.Now.AddMilliseconds(500));
}

因此,如果项目过期,用户点击缓存并从中获取数据,并从服务中获取数据并保存以防万一,t 问题是,当有待处理请求时(请求正在进行)服务发送另一个请求,因为该对象已过期。在最后,应该有最多2-3个呼叫/秒,并且每秒有10-20个呼叫到外部服务。

有没有最佳方法可以做到这一点,除了创建自己的带有数组和时间戳的自定义类之外,请求时间之间没有冲突?

btw缓存的保存代码是 -

private void SetDataIntoCache(ObjectCache cacheStore, string cacheKey, object target, DateTime slidingExpirationDuration)
{
  CacheItemPolicy cacheItemPolicy = new CacheItemPolicy();

  cacheItemPolicy.AbsoluteExpiration = slidingExpirationDuration;
  cacheStore.Add(cacheKey, target, cacheItemPolicy);
}

4 个答案:

答案 0 :(得分:5)

使用Double-checked locking模式:

var cachedItem = (string)this.GetDataFromCache(cache, cacheKey);
if (String.IsNullOrEmpty(object)) { // if no cache yet, or is expired
   lock (_lock) { // we lock only in this case
      // you have to make one more check, another thread might have put item in cache already
      cachedItem = (string)this.GetDataFromCache(cache, cacheKey); 
      if (String.IsNullOrEmpty(object)) {
          //get the data. take 100ms
          SetDataIntoCache(cache, cacheKey, cachedItem, DateTime.Now.AddMilliseconds(500));
      }
   }
}

这样,虽然缓存中有一个项目(因此,尚未过期),但所有请求都将在没有锁定的情况下完成。但是如果还没有缓存条目,或者它已过期 - 只有一个线程将获取数据并将其放入缓存中。 确保你理解这种模式,因为在.NET中实现它时会有一些注意事项。

如评论中所述,没有必要使用一个“全局”锁定对象来保护每个缓存访问。假设您的代码中有两个方法,并且每个方法都使用它自己的缓存键缓存对象(但仍然使用相同的缓存)。然后你必须使用两个单独的锁对象,因为如果你将使用一个“全局”锁对象,对一个方法的调用将不必等待对另一个方法的调用,而它们永远不会使用相同的缓存键。

答案 1 :(得分:2)

我调整了Micro Caching in .NET的解决方案,用于System.Runtime.Caching.ObjectCache MvcSiteMapProvider。完整实施具有ICacheProvider界面,允许在System.Runtime.CachingSystem.Web.Caching之间进行交换,但这是一个可以满足您需求的缩减版本。

此模式最引人注目的特性是它使用轻量级的延迟锁来确保数据在缓存过期后仅从数据源加载一次,无论尝试加载多少并发线程数据。

using System;
using System.Runtime.Caching;
using System.Threading;

public interface IMicroCache<T>
{
    bool Contains(string key);
    T GetOrAdd(string key, Func<T> loadFunction, Func<CacheItemPolicy> getCacheItemPolicyFunction);
    void Remove(string key);
}

public class MicroCache<T> : IMicroCache<T>
{
    public MicroCache(ObjectCache objectCache)
    {
        if (objectCache == null)
            throw new ArgumentNullException("objectCache");

        this.cache = objectCache;
    }
    private readonly ObjectCache cache;
    private ReaderWriterLockSlim synclock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);

    public bool Contains(string key)
    {
        synclock.EnterReadLock();
        try
        {
            return this.cache.Contains(key);
        }
        finally
        {
            synclock.ExitReadLock();
        }
    }

    public T GetOrAdd(string key, Func<T> loadFunction, Func<CacheItemPolicy> getCacheItemPolicyFunction)
    {
        LazyLock<T> lazy;
        bool success;

        synclock.EnterReadLock();
        try
        {
            success = this.TryGetValue(key, out lazy);
        }
        finally
        {
            synclock.ExitReadLock();
        }

        if (!success)
        {
            synclock.EnterWriteLock();
            try
            {
                if (!this.TryGetValue(key, out lazy))
                {
                    lazy = new LazyLock<T>();
                    var policy = getCacheItemPolicyFunction();
                    this.cache.Add(key, lazy, policy);
                }
            }
            finally
            {
                synclock.ExitWriteLock();
            }
        }

        return lazy.Get(loadFunction);
    }

    public void Remove(string key)
    {
        synclock.EnterWriteLock();
        try
        {
            this.cache.Remove(key);
        }
        finally
        {
            synclock.ExitWriteLock();
        }
    }


    private bool TryGetValue(string key, out LazyLock<T> value)
    {
        value = (LazyLock<T>)this.cache.Get(key);
        if (value != null)
        {
            return true;
        }
        return false;
    }

    private sealed class LazyLock<T>
    {
        private volatile bool got;
        private T value;

        public T Get(Func<T> activator)
        {
            if (!got)
            {
                if (activator == null)
                {
                    return default(T);
                }

                lock (this)
                {
                    if (!got)
                    {
                        value = activator();

                        got = true;
                    }
                }
            }

            return value;
        }
    }
}

用法

// Load the cache as a static singleton so all of the threads
// use the same instance.
private static IMicroCache<string> stringCache = 
    new MicroCache<string>(System.Runtime.Caching.MemoryCache.Default);

public string GetData(string key)
{
    return stringCache.GetOrAdd(
        key,
        () => LoadData(key),
        () => LoadCacheItemPolicy(key));
}

private string LoadData(string key)
{
    // Load data from persistent source here

    return "some loaded string";
}

private CacheItemPolicy LoadCacheItemPolicy(string key)
{
    var policy = new CacheItemPolicy();

    // This ensures the cache will survive application
    // pool restarts in ASP.NET/MVC
    policy.Priority = CacheItemPriority.NotRemovable;

    policy.AbsoluteExpiration = DateTimeOffset.Now.AddMinutes(1);

    // Load Dependencies
    // policy.ChangeMonitors.Add(new HostFileChangeMonitor(new string[] { fileName }));

    return policy;
}
  

注意:如前所述,您可能无法通过缓存仅需500毫秒来检索500毫秒的值。您最有可能选择较长的时间段来保存缓存中的项目。这些项目是否真的在数据源中易变,它们可以快速更改?如果是这样,也许您应该考虑使用ChangeMonitor来使任何陈旧数据无效,这样您就不会花费太多的CPU时间来加载缓存。然后,您可以将缓存时间更改为分钟而不是毫秒。

答案 2 :(得分:0)

你必须使用锁定以确保在缓存过期而另一个线程从远程/慢速服务获取请求时不发送请求,它看起来像这样(有更好的实现,更容易使用,但他们需要单独的课程):

private static readonly object _Lock = new object();

...

object = (string)this.GetDataFromCache(cache, cacheKey);

if(object == null)
{
   lock(_Lock)
   {
        object = (string)this.GetDataFromCache(cache, cacheKey);
        if(String.IsNullOrEmpty(object))
        {
           get the data // take 100ms
           SetDataIntoCache(cache, cacheKey, object, DateTime.Now.AddMilliseconds(500));
        }
   }
}

return object;

此外,您希望确保您的服务不返回null,因为它将假定不存在缓存并将尝试获取每个请求的数据。这就是为什么更高级的实现通常使用类似CacheObject的东西,它支持空值存储。

答案 3 :(得分:0)

顺便说一下,500毫秒的缓存时间太短,你只需添加/删除缓存就会占用大量的CPU周期,最终会在任何其他请求获得缓存优势之前过早删除缓存。您应该分析您的代码,看看它是否真正有益。

请记住,缓存在锁定,散列和许多其他移动数据方面有很多代码,这会花费大量CPU周期并记住,尽管CPU周期很小,但在多线程,多连接服务器,CPU中还有很多其他事情要做。

原始答案https://stackoverflow.com/a/16446943/85597

private string GetDataFromCache(
            ObjectCache cache, 
            string key, 
            Func<string> valueFactory)
{
    var newValue = new Lazy<string>(valueFactory);            

    //The line below returns existing item or adds 
    // the new value if it doesn't exist
    var value = cache.AddOrGetExisting(key, newValue, DateTimeOffset.Now.AddMilliseconds(500)) as Lazy<string>;
    // Lazy<T> handles the locking itself
    return (value ?? newValue).Value;
}


// usage...


object = this.GetDataFromCache(cache, cacheKey, () => {

      // get the data...

      // this method will be called only once..

      // Lazy will automatically do necessary locking
      return data;
});