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

kaw*_*fan 10 .net c# 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));
}
Run Code Online (Sandbox Code Playgroud)

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

有没有最佳的方法来做到这一点所以请求时间之间没有冲突,然后创建自己的自定义类与数组和时间戳等?

顺便说一下缓存的保存代码是 -

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

  cacheItemPolicy.AbsoluteExpiration = slidingExpirationDuration;
  cacheStore.Add(cacheKey, target, cacheItemPolicy);
}
Run Code Online (Sandbox Code Playgroud)

Nig*_*888 6

我已经改编了.NET 中的微缓存解决方案,以便与MvcSiteMapProviderSystem.Runtime.Caching.ObjectCache一起使用。完整的实现有一个允许在和之间交换的接口,但这是一个应该满足您的需求的精简版本。ICacheProviderSystem.Runtime.CachingSystem.Web.Caching

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

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

用法

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

注意:正如前面提到的,通过将需要 100 毫秒才能检索的值仅缓存 500 毫秒,您可能不会获得任何好处。您很可能应该选择更长的时间段来将项目保留在缓存中。数据源中的项目真的如此不稳定,以至于可以如此快速地更改吗?如果是这样,也许您应该考虑使用 aChangeMonitor来使任何陈旧数据无效,这样您就不会花费太多的 CPU 时间来加载缓存。然后您可以将缓存时间更改为分钟而不是毫秒。


Evk*_*Evk 5

使用双重检查锁定模式:

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

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

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

  • 重要的是要强调`_lock`定义并发的粒度; 通常,相同的缓存对象包含一系列不相关和非竞争数据,因此可能需要确保锁粒度反映数据. (2认同)