如何将动态更改数据存储到服务器缓存中?

时间:2010-04-07 17:07:30

标签: c# asp.net caching

编辑:本网站的用途:其名为Utopiapimp.com。它是名为utopia-game.com的游戏的第三方实用程序。该网站目前拥有超过12k用户,我运行该网站。游戏是完全基于文本的,并将始终保持这种状态。用户从游戏中复制并粘贴整页文本,并将复制的信息粘贴到我的网站中。我针对粘贴的数据运行一系列正则表达式并将其分解。然后,我将基于该一个粘贴将5个值到30个以上的值插入到DB中。然后,我接受这些值并对它们运行查询,以非常简单易懂的方式显示信息。游戏是基于团队的,每个团队有25个用户。因此每个团队都是一个组,每行都是一个用户信息。用户可以一次更新所有25行或仅更新一行。我需要将内容存储到缓存中,因为该网站几乎每分钟都会执行超过1,000次查询。

所以这是交易。想象一下,我有一个excel EDIT (Excel只是一个如何设想的例子,我实际上并不使用excel)电子表格有100列和5000行。每行有两个唯一标识符。一个用于自行,一个用于组合25行一个。该行中大约有10列几乎不会改变,其他90列将始终在变化。我们可以说有些甚至会在几秒钟内改变,具体取决于行的更新速度。也可以从组中添加和删除行,但不能从数据库中删除。这些行来自数据库中的大约4个查询,以显示数据库中的最新和更新数据。因此,每次更新数据库中的某些内容时,我也希望更新该行。如果行或组在12个小时内未更新,则将从Cache中取出。一旦用户通过数据库查询再次呼叫该组。它们将被放入缓存中。

以上是我想要的。这就是愿望。

在Reality中,我仍然拥有所有行,但是我将它们存储在Cache中的方式目前已被破坏。我将每一行存储在一个类中,该类通过一个巨大的列表存储在服务器缓存中。当我去更新/删除/插入列表或行中的项目时,大部分时间它都有效,但有时它会因为缓存已更改而引发错误。我希望能够锁定缓存,就像数据库或多或少地抛出对某一行的锁定一样。我有12个小时后删除东西的DateTime标记,但这几乎总是会中断,因为其他用户正在更新组中的相同25行或只是缓存已更改。

这是我如何向Cache添加项目的一个示例,这个示例显示我只拉了10个很少更改的列。此示例全部删除12小时后未更新的行:

DateTime dt = DateTime.UtcNow;
    if (HttpContext.Current.Cache["GetRows"] != null)
    {
        List<RowIdentifiers> pis = (List<RowIdentifiers>)HttpContext.Current.Cache["GetRows"];
        var ch = (from xx in pis
                  where xx.groupID == groupID 
                  where xx.rowID== rowID
                  select xx).ToList();
        if (ch.Count() == 0)
        {
            var ck = GetInGroupNotCached(rowID, groupID, dt); //Pulling the group from the DB
            for (int i = 0; i < ck.Count(); i++)
                pis.Add(ck[i]);
            pis.RemoveAll((x) => x.updateDateTime < dt.AddHours(-12));
            HttpContext.Current.Cache["GetRows"] = pis;
            return ck;
        }
        else
            return ch;
    }
    else
    {
        var pis = GetInGroupNotCached(rowID, groupID, dt);//Pulling the group from the DB
        HttpContext.Current.Cache["GetRows"] = pis;
        return pis;
    }

最后一点,我从缓存中删除了项目,因此缓存实际上并没有变大。

要重新发布这个问题,有什么更好的办法吗?也许以及如何把锁放在缓存上?我可以比这更好吗?我只是希望它在删除或添加行时停止破坏。

编辑:代码SQLCacheDependency不适用于Remus评论中发布的LINQ。它适用于全表选择,但我想从行中选择某些列。我不想选择整行,所以我不能使用Remus的想法。

以下代码示例均无效。

var ck = (from xx in db.GetInGroupNotCached
              where xx.rowID== rowID
              select new {                 
                  xx.Item,
                  xx.AnotherItem,
                  xx.AnotherItem,
                  }).CacheSql(db, "Item:" + rowID.ToString()).ToList();


var ck = (from xx in db.GetInGroupNotCached
              where xx.rowID== rowID
              select new ClassExample {              
                Item=  xx.Item,
                 AnotherItem= xx.AnotherItem,
                 AnotherItemm = xx.AnotherItemm,
                  }).CacheSql(db, "Item:" + rowID.ToString()).ToList();

4 个答案:

答案 0 :(得分:5)

我真的怀疑你的缓存解决方案实际上是有用的。 List<T>无法编入索引,因此列表中的查找始终是O(n)操作。

假设您已经分析了您的应用程序并且知道数据库是您的瓶颈,那么您可以这样做:

在数据库中,您可以为数据创建索引,对它们的查找通常会显示O(log(n))。您应该为包含静态数据的查询创建覆盖索引。保持频繁更改的数据不被索引,因为这会因为必要的索引更新而减慢插入和更新。您可以阅读SQL Server索引here。获取SQL Server Profiler并检查哪些是最慢的查询以及原因。正确的索引可以为您带来巨大的性能提升(例如,假设每组有25个人,GroupId上的索引将减少从全表扫描O(n)到O(n / 25)的索引查找的查找时间

通常,人们编写次优的SQL(返回不必要的列,选择N + 1,笛卡儿联接)。你也应该检查一下。

在实现缓存之前,我会确保您的数据库确实是导致性能问题的罪魁祸首。过早优化是所有邪恶的根源,缓存 难以正确行事。缓存频繁更改的数据不是缓存的目的。

答案 1 :(得分:4)

答案 2 :(得分:2)

您将整个数据库作为列表存储在内存中,并作为列表遍历为每个请求重新查询它。坦率地说,我怀疑这个'缓存'实际上比运行SQL查询更快。遍历列表永远不会打败数据库...

您应该做的是缓存特定的查询结果。与rowID和groupID的结果集一样,缓存由两个参数键入。要进行刷新,请依赖查询通知周围的内置缓存失效基础架构,请参阅此文章The Mysterious Notification以了解其工作原理。使用ASP.Net项目,您所要做的就是利用SqlCacheDependency

答案 3 :(得分:1)

我不太确定这是个好主意,如果你能设法加快与数据库的沟通,你可能会有更好的解决方案。

希望我理解你的要求。 它很快变成了很多代码,在这里你拥有它......

这只是一个示例,但它可能是一些建立的东西。我没有考虑到你需要在一段时间后删除行。
我将缓存分成了具有组的组,其中组包含行。 我设计的样本只在第一个set属性被调用时才锁定一行,当只调用get操作时你应该是安全的。
处理行对象时将释放锁定。所以你必须使用using()或调用Dispose()才能使它工作。

这是一个缓存(组)类和一个行类。
在评论// Add code to read from database...

之后添加数据库
public class GroupCache : SimpleCache<RowObject, int>
{
    private static readonly object GroupCacheObjectLock = new object();

    public GroupCache(int groupId)
    {
        GroupId = groupId;
    }
    public int GroupId { get; private set; }

    public static GroupCache GetGroupCache(int groupId)
    {
        lock (GroupCacheObjectLock)
        {
            if (HttpContext.Current.Cache["Group-" + groupId] == null)
            {
                HttpContext.Current.Cache["Group-" + groupId] 
                    = new GroupCache(groupId);
            }
        }
        return HttpContext.Current.Cache["Group-" + groupId];
    }

    public override RowObject CreateItem(int id, 
            SimpleCache<RowObject, int> cache)
    {
        return new RowObject(id, GroupId, this);
    }

}

public class RowObject : SimpleCacheItem<RowObject, int>
{
    private string _property1;

    public RowObject(int rowId, int groupId, SimpleCache<RowObject, int> cache)
        : base(rowId, cache)
    {
        // Add code to read from database...
    }

    public string Property1
    {
        get { return _property1; }
        set
        {
            if (!AcquireLock(-1)) return;
            _property1 = value;
#if DEBUG
            Trace.WriteLine(string.Format("Thread id: {0}, value = {1}", 
                Thread.CurrentThread.ManagedThreadId, value));
#endif
        }
    }
}

这是一个单元测试,主要是为了展示如何使用这些类。

[TestFixture]
public class GroupCacheTest
{
    private int _threadFinishedCount;
    private void MultiThreadTestWorker(object obj)
    {
        for (int n = 0; n < 10; n++)
        {
            for (int m = 0; m < 25; m++)
            {
                using (RowObject row 
                    = GroupCache.GetGroupCache(n).GetCachedItem(m))
                {
                    row.Property1 = string.Format("{0} {1} {2}", obj, n, m);
                    Thread.Sleep(3);
                }
            }
        }
        Interlocked.Increment(ref _threadFinishedCount);
    }
    [Test]
    public void MultiThreadTest()
    {
        _threadFinishedCount = 1;
        for (int i = 0; i < 20; i++)
        {
            ThreadPool.QueueUserWorkItem(MultiThreadTestWorker, "Test-" + i);
        }
        while (_threadFinishedCount < 10)
            Thread.Sleep(100);
    }
}

以下是基类。

public abstract class SimpleCacheItem<T, TKey> : IDisposable where T : class
{
    private readonly SimpleCache<T, TKey> _cache;

    protected SimpleCacheItem(TKey id, SimpleCache<T, TKey> cache)
    {
        Id = id;
        _cache = cache;
    }

    protected TKey Id { get; private set; }

    #region IDisposable Members

    public virtual void Dispose()
    {
        if (_cache == null) return;
        _cache.ReleaseLock(Id);
    }

    #endregion

    protected bool AcquireLock(int timeout)
    {
        return _cache.AcquireLock(Id, -1);
    }
}

public abstract class SimpleCache<T, TKey> where T : class
{
    private static readonly object CacheItemLockSyncLock = new object();
    private static readonly object CacheItemStoreSyncLock = new object();
    private readonly Dictionary<TKey, int> _cacheItemLock;
    private readonly Dictionary<TKey, T> _cacheItemStore;

    public abstract T CreateItem(TKey id, SimpleCache<T, TKey> cache);

    public T GetCachedItem(TKey id)
    {
        T item;
        lock (CacheItemStoreSyncLock)
        {
            if (!_cacheItemStore.TryGetValue(id, out item))
            {
                item = CreateItem(id, this);
                _cacheItemStore.Add(id, item);
            }
        }
        return item;
    }

    public void ReleaseLock(TKey id)
    {
        lock (CacheItemLockSyncLock)
        {
            if (_cacheItemLock.ContainsKey(id))
            {
                _cacheItemLock.Remove(id);
            }
        }
#if DEBUG
        Trace.WriteLine(string.Format("Thread id: {0} lock released", 
        Thread.CurrentThread.ManagedThreadId));
#endif
    }

    public bool AcquireLock(TKey id, int timeOut)
    {
        var timer = new Stopwatch();
        timer.Start();
        while (timeOut < 0 || timeOut < timer.ElapsedMilliseconds)
        {
            lock (CacheItemLockSyncLock)
            {
                int threadId;
                if (!_cacheItemLock.TryGetValue(id, out threadId))
                {
                    _cacheItemLock.Add(id, 
                        Thread.CurrentThread.ManagedThreadId);
#if DEBUG
                    Trace.WriteLine(string.Format(
                        "Thread id: {0}, lock acquired after {1} ms", 
                        Thread.CurrentThread.ManagedThreadId, 
                        timer.ElapsedMilliseconds));
#endif
                    return true;
                }
                if (threadId == Thread.CurrentThread.ManagedThreadId) 
                    return true;
            }
            Thread.Sleep(15);
        }
        return false;
    }
}