当前位置: 首页 > 图文教程 > 网络编程 > ASP.NET > 无缝的缓存读取:双存储缓存策略

ASP.NET
使用函数传递参数来执行相应的数据库操作
如何实现在窗体和窗体之间进行传递数据
ASP.NET中文显示之两种解决方法
ASP.NET、JSP及PHP之间的抉择
ASP.NET 2.0发送电子邮件中存在的问题
谈谈HtmlControl与WebControl的区别与用途
从ASP.NET 1.1升级到ASP.NET 2.0要考虑的Cookie问题
通过系统配置来提高ASP.NET应用程序的稳定性
妙用ASP2.0中的URL映射改变网址
AJAX实现web页面中级联菜单的设计
ASP.NET跨页面传值技巧总结
再议ASP.NET DataGrid控件中的“添加新行”功能
Geometry 对象浅析
重构CollapsibleSplitter
如何利用.NET Framework使用RSS feed
ASP.NET获取IP与MAC地址的方法
在ASP.NET 2.0中使用样式、主题和皮肤
ASP.NET中为GridView添加删除提示框
ASP.NET 2.0,无刷新页面新境界
看看一个.net版对话框控件

ASP.NET 中的 无缝的缓存读取:双存储缓存策略


出处:互联网   整理: 软晨网(RuanChen.com)   发布: 2009-10-11   浏览: 91 ::
收藏到网摘: n/a

原文:http://www.cnblogs.com/chsword/archive/2009/04/01/1427627.html

最近在做一个WEB的数据统计的优化,但是由于数据量大,执行一次SQL统计要比较长的时间(一般700ms算是正常)。

正常的做法只要加个缓存就好了。

但是同时业务要求此数据最多1分钟就要更新,而且这一分种内数据可能会有较多变化(而且原系统不太易扩展)。

也就是说缓存1分钟就要失效重新统计,而且用户访问这页还很是频繁,如果使用一般缓存那么用户体验很差而且很容易造成超时。

看到以上需求,第一个进入我大脑的就是从前做游戏时接触到的DDraw的双缓冲显示方式。

在第一帧显示的同时,正在计算第二帧,这样读取和计算就可以分开了,也就避免了读取时计算,提高了用户体验。

我想当然我们也可以将这种方式用于缓存的策略中,但这样用空间换取时间的方式还是得权衡的,因为并不是所有时候都值得这么做,但这里我觉得这样做应该是最好的方式了。

注:为了可以好好演示,本篇中的缓存都以IEnumerable的形式来存储,当然这个文中原理也可以应用在WebCache中。

这里我使用以下数据结构做为存储单元:

namespace CHCache {
    /// <summary>
    /// 缓存介质
    /// </summary>
    public class Medium {
        /// <summary>
        /// 主要存储介质
        /// </summary>
        public object Primary { get; set; }
        /// <summary>
        /// 次要存储介质
        /// </summary>
        public object Secondary { get; set; }
        /// <summary>
        /// 是否正在使用主要存储
        /// </summary>
        public bool IsPrimary { get; set; }
        /// <summary>
        /// 是否正在更新
        /// </summary>
        public bool IsUpdating { get; set; }
        /// <summary>
        /// 是否更新完成
        /// </summary>
        public bool IsUpdated { get; set; }
    }
}
  

有了这个数据结构我们就可以将数据实现两份存储。再利用一些读写策略就可以实现上面我们讲的缓存方式。转

整个的缓存我们使用如下缓存类来控制:

/*
 * http://www.cnblogs.com/chsword/
 * chsword
 * Date: 2009-3-31
 * Time: 17:00
 *
 */
using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading;
namespace CHCache {
    /// <summary>
    /// 双存储的类
    /// </summary>
    public class DictionaryCache : IEnumerable {
        /// <summary>
        /// 在此缓存构造时初始化字典对象
        /// </summary>
        public DictionaryCache()
        {
            Store = new Dictionary<string, Medium>();
        }
        public void Add(string key,Func<object> func)
        {
            if (Store.ContainsKey(key)) {//修改,如果已经存在,再次添加时则采用其它线程
                var elem = Store[key];
                if (elem.IsUpdating)return;  //正在写入未命中
                var th = new ThreadHelper(elem, func);//ThreadHelper将在下文提及,是向其它线程传参用的
                var td = new Thread(th.Doit);
                td.Start();
            }
            else {//首次添加时可能也要读取,所以要本线程执行
                Console.WriteLine("Begin first write");
                Store.Add(key, new Medium {IsPrimary = true, Primary =  func()});
                Console.WriteLine("End first write");
            }

        }
        /// <summary>
        /// 读取时所用的索引
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public object this[string key] {
            get {
                if (!Store.ContainsKey(key))return null;
                var elem = Store[key];
                if (elem.IsUpdated) {//如果其它线程更新完毕,则将主次转置
                    elem.IsUpdated = false;
                    elem.IsPrimary = !elem.IsPrimary;
                }
                var ret = elem.IsPrimary ? elem.Primary : elem.Secondary;
                var b = elem.IsPrimary ? " from 1" : " form 2";
                return ret + b;
            }
        }
        Dictionary<string, Medium> Store { get; set; }
        public IEnumerator GetEnumerator() {
            return ((IEnumerable)Store).GetEnumerator();
        }
    }

这里我只实现了插入一个缓存,以及读取的方法。

我读取缓存单元的逻辑是这样的:

从2个不同缓存读取当然是很容易了,但是比较复杂的就是向缓存写入的过程:

这里读取数据以及写入缓存时我使用了一个委托,在其它线程中仅在需要执行时才会执行。

这里除了首次写入缓存占用主线程时间(读取要等待)以外,其它时间都可以无延时的读取,实现了无缝的缓存。

但我们在委托中要操作缓存的元素Medium,所以要传递参数进其它线程,所以我这里使用了一个辅助类来传递参数进入其它线程:

using System;
namespace CHCache {
    /// <summary>
    /// 一个线程Helper,用于帮助多抛出线程时传递参数
    /// </summary>
    public class ThreadHelper {
        Func<object> Fun { get; set; }
        Medium Medium { get; set; }
        /// <summary>
        /// 通过构造函数来传递参数
        /// </summary>
        /// <param name="m">缓存单元</param>
        /// <param name="fun">读取数据的委托</param>
        public ThreadHelper(Medium m,Func<object> fun) {
            Medium = m;
            Fun = fun;
        }
        /// <summary>
        /// 线程入口,ThreadStart委托所对应的方法
        /// </summary>
        public void Doit()
        {
            Medium.IsUpdating = true;
            if (Medium.IsPrimary) {
                Console.WriteLine("Begin write to 2.");
                var ret = Fun.Invoke();
                Medium.Secondary = ret;
                Console.WriteLine("End write to 2.");
            }
            else {
                Console.WriteLine("Begin write to 1.");
                var ret = Fun.Invoke();
                Medium.Primary = ret;
                Console.WriteLine("End write to 1.");
            }
            Medium.IsUpdated = true;
            Medium.IsUpdating = false;
        }
    }
}

这样我们就实现了在另个线程读取数据的过程,这样就在任何时候读取数据时都会无延时直接读取了。

最后我们写一个主函数来测试一下效果

/*
 * http://www.cnblogs.com/chsword/
 * chsword
 * Date: 2009-3-31
 * Time: 16:53
 */
using System;
using System.Threading;
namespace CHCache
{
    class Program
    {
        public static void Main(string[] args)
        {
            var cache = new DictionaryCache();
            Console.WriteLine("Init...4s,you can press the CTRL+C to close the console window.");
            while (true)
            {
                cache.Add("1", GetValue);
                Thread.Sleep(1000);
                Console.WriteLine(cache["1"]);
            }
        }
        /// <summary>
        /// 获取数据的方法,假设是从数据库读取的,费时约4秒
        /// </summary>
        /// <returns></returns>
        static object GetValue()
        {
            Thread.Sleep(4000);
            return DateTime.Now;
        }
    }
}

得到如下数据:

这样就实现了平滑的读取缓存数据而没有任何等待时间

当然这里还有些问题,比如说传递不同参数时的解决方法,但是由于我仅是在一个统计时需要这种缓存提高性能,所以暂没有考虑通用的传参方式。

如果大家对这个话题感兴趣,欢迎讨论。

示例下载:CHCache.rar(点击下载)