使用HttpRuntime.Cache时出现问题

4

我将使用以下 .net 代码将对象添加到缓存中:

public static void Add<T>(string key, T dataToCache)
{
    try
    {
        ApplicationLog.Instance.WriteInfoFormat("Inserting item with key {0} into Cache...", key);

        HttpRuntime.Cache.Insert(
            key,
            dataToCache,
            null,
            DateTime.Now.AddDays(7),
            System.Web.Caching.Cache.NoSlidingExpiration);
    }

    catch (Exception ex)
    {
        ApplicationLog.Instance.WriteException(ex);             
    }
}

这是我从缓存中检索值的代码:

public static T Get<T>(string key) 
{   
    try
    {                
        if (Exists(key))
        {
            ApplicationLog.Instance.WriteInfoFormat("Retrieving item with key {0} from Cache...", key);

            return (T)HttpRuntime.Cache[key];
        }
        else
        {
            ApplicationLog.Instance.WriteInfoFormat("Item with key {0} does not exist in Cache.", key);
            return default(T); 
        }
    }
    catch(Exception ex)
    {
        ApplicationLog.Instance.WriteException(ex);
        return default(T); 
    }
}


public static bool Exists(string key)
{
    bool retVal = false;
    try
    {
        retVal= HttpRuntime.Cache[key] != null;
    }
    catch (Exception ex)
    {
        ApplicationLog.Instance.WriteException(ex);
    }
    return retVal; 
}

但是我发现大约每2分钟左右,缓存对象的值都会被设置为null,导致需要再次从数据库中获取该值。
我错过了什么吗?

我认为利用你的缓存的代码更相关。例如,你是否多次向缓存中添加相同的键? - womp
1
因为他正在使用插入操作,如果该项已经在缓存中,则只会更新该项。 - colithium
你的ASP.NET配置是否为默认设置?例如,是否设置了在分配一定量的内存或经过一定时间后重新启动ASP.NET?还有你的应用程序池设置如何?基本上,你确定HttpRuntime没有被重新启动吗? - Richard Anthony Freeman-Hein
请注意,您的Get()方法存在潜在的竞态条件,因为缓存可能会在Exists()调用和HttpRuntime.Cache[key]之间更新。请参见此处的最后一点:https://dev59.com/tWXWa4cB1Zd3GeqPSPtd#11432864 - Dunc
3个回答

8
首先,您的访问未同步,这是一个很大的问题。从HttpRuntime Cache中读取是线程安全的,因此您应该在每个缓存操作中将读取项作为第一步尝试读取。
在检查是否存在和实际检索项目之间,可能会发生很多事情(例如,您的项目可能不再存在)。您应该获取要查找的项目的句柄,如果不存在,则通过从持久数据存储中获取来提供线程安全插入。
因此,如果数据不存在,则您的Add逻辑将进入Get IF。提供单独的添加逻辑并没有根本性的错误,并且您应该衡量多次命中数据库的成本与阻止特定数据进一步请求之间的比较。
T GetT(string key)
{
    T item = (cache.Get(key) as T);
    if (item == null)
    {
        lock (yourSyncRoot)
        {
            // double check it here
            item = (cache.Get(key) as T);
            if (item != null)
                return item;

            item = GetMyItemFromMyPersistentStore(key); // db?
            if (item == null)
                return null;

            string[] dependencyKeys = {your, dependency, keys};

            cache.Insert(key, item, new CacheDependency(null, dependencyKeys), 
                         absoluteExpiration, slidingExpiration, priority, null);
        }
    }
    return item;
}

根据您的过期策略,您将在内存中获取数据并提供快速同步的访问,但正如我所说,要根据您的需求进行调整和测量。在更新项目并将其正确保存到持久存储后,在您的业务逻辑中,只需从缓存中删除它,下一次调用您的Get将再次获取它。


1
通过向 Get 方法中添加一个 lambda 并将其作为扩展方法来改进它,静态泛型 T Get<T>(this Cache cache, String key, Func<T> retrieveFunc)。 - Andrew Theken
5
“Cache access is thread-safe” 的意思是,对该对象的单个操作具有原子性,在被多个线程访问时不会导致失败。但是,即使对于支持线程安全的集合,对该对象的多个操作仍需要通过锁进行同步。在这里,我们有一个“Get”和一个“Insert”,如果get没有找到一个对象,那么这一对操作必须加锁,否则两个或更多不同的线程可能会通过“Get”,找不到对象,然后创建并“Insert”它。如果我们只想让一个线程创建和插入该对象,则必须锁定操作配对。这是常见的模式。 - Keith Robertson

6
当你说每两分钟插入的值被设置为null,这是指你感兴趣的项还是缓存中的每个项?
我问这个问题是因为缓存只存在于应用程序运行的时间。如果应用程序重新启动,缓存就会消失。如果每两分钟所有内容都消失了,那么这就可以解释这种行为。在这种情况下,你有一个不同的问题:为什么应用程序每两分钟重新启动。
如果只有一些项,则可能是内存问题。缓存会响应低内存而自动清理。我相信有一种方法可以为插入的值设置优先级。但是只有在内存不足时才会出现这个问题。
如果这仍然不能解决您的问题,有一种方法可以发现为什么某个项目正在被删除。这在这里中有解释。

感谢您的输入...是的,在缓存中,每个项目的值都设置为null。 - user74042
你能检查一下日志,确认一下是否应用程序正在重新启动,并找出原因吗?一旦你找到了原因,就可以解决问题或者提出另一个问题了。 - colithium

2

可能是因为内存不足,缓存会在内存变得稀缺时自动清除缓存中的项目。如果您希望在清除另一个项目之前清除某个项目,则可以使用可选参数设置缓存中项目的优先级。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接