【问题标题】:Strategy for Caching Previous Searches (ASP.NET)缓存先前搜索的策略 (ASP.NET)
【发布时间】:2010-10-20 09:41:35
【问题描述】:

我有一个 ASP.NET MVC 2 Web 应用程序(.NET 4,C#),用户可以在其中搜索位置

该页面使用自动完成框实现,类似于许多网站。 (谷歌、YouTube 等)

现在,对服务器的 AJAX 调用导致对数据库的存储过程调用。 (虽然效率很高,但可能会导致打字速度慢的人进行大量往返)。

我想知道如何创建一个策略来缓存最近 100 次搜索的结果?

我不能使用 OutputCache,因为调用是通过客户端的 AJAX 进行的。我需要缓存存储过程的输出(查询文本的匹配位置列表)。

换句话说,很多人会搜索“纽约”或“旧金山”,而这些数据只能通过手动管理员更改(例如,我们可以手动使缓存无效)来更改。

那么,我如何缓存最后 100 次搜索?我希望有一个类似 FIFO 的功能,如果缓存已经有 100 次搜索,最旧的会被丢弃,所有内容都会被移下。

我希望代码是这样的:

public ICollection<MatchedLocation> FindLocations(string queryText)
{
    // Check last 100 searches.. How?
    string cacheKey = queryText;
    var matchedLocations = cachedPersistence.Get(cacheKey);

    if (matchedLocations == null)
    {
        // Call db
        matchedLocations = dbPersistence.GetLocations(queryText);

        // Add to cache
        cachedPersistence.Add(cacheKey, matchedLocations);
    }
    else
    {
        // Found in Cache! Awesome!
        return matchedLocations;
    }
}

我认为显而易见的选择是 .NET Queue

但我以前从未使用过它,有什么建议吗?我将如何为 get/set 实现并发,我需要使用完全锁定的单例吗?有没有人为此使用队列?我们还有哪些其他选择?我几乎需要一个自定义队列来限制堆栈中的项目数量。

感谢您的帮助。

【问题讨论】:

    标签: c# asp.net caching queue fifo


    【解决方案1】:

    如果您只想缓存 100,您可以使用 Dictionary 和对应的 Dictionary with last used times。您可以使用读写器锁代替允许多个读取器的完整锁。

    使用下面的代码可能有两个线程可以输入EnterWriteLock 以获得相同的值。惩罚将是两次 db 调用,这可能不是问题。您可以通过另一个TryGetValue 并在必要时锁定(双重锁定)来避免这种情况。

    class Cache
    {
        static readonly Dictionary<string, ICollection<MatchedLocation>> _cache = new Dictionary<string, ICollection<MatchedLocation>>(100);
        static readonly Dictionary<string,DateTime> _cacheTimes = new Dictionary<string, DateTime>(100);
        static readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();
    
        public ICollection<MatchedLocation> FindLocations(string queryText)
        {
            _lock.EnterUpgradeableReadLock();
            try
            {
                ICollection<MatchedLocation> result;
                if (_cache.TryGetValue(queryText, out result))
                {
                    return result;
                }
                else
                {
                    _lock.EnterWriteLock();
                    try
                    {
                        // expire cache items
                        if( _cache.Count > 100)
                        {
                            // could be more efficient http://code.google.com/p/morelinq/ - MinBy
                            string key = _cacheTimes.OrderBy(item => item.Value).First().Key;
                            _cacheTimes.Remove(key);
                            _cache.Remove(key);
                        }
                        // add new item
                        result = dbPersistence.GetLocations(queryText);
                        _cache[queryText] = result;
                        _cacheTimes[queryText] = DateTime.UtcNow;                        
                    }
                    finally
                    {
                        _lock.ExitWriteLock();
                    }
                    return result;
                }
            }
            finally
            {
                _lock.ExitUpgradeableReadLock();
            }
        }
    }
    

    【讨论】:

    • 有趣的方法。我会试试这个,运行一些测试,然后回复你。谢谢
    • 您可能想要添加一个时间到期作业,以便偶尔获取更新的数据库数据。另一种方法是使用 ASP.Net 中的 Cache 对象,并保留一个计数器并使用过期触发器。我认为这完全取决于您想要对缓存进行多少控制,以及需要缓存多少数据才能使解决方案运行起来。有趣的阅​​读social.msdn.microsoft.com/Forums/en-US/velocity/thread/… 也谈到了速度。
    • 也可以看看这个小项目 - code.msdn.microsoft.com/Release/…
    猜你喜欢
    • 2011-11-12
    • 2010-10-26
    • 1970-01-01
    • 2023-03-04
    • 2011-04-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-08-16
    相关资源
    最近更新 更多