专注于互联网--专注于架构

最新标签
网站地图
文章索引
Rss订阅

首页 »编程思想 » 缓存服务器:SAF 中缓存Cache服务的实现 »正文

缓存服务器:SAF 中缓存Cache服务的实现

来源: 发布时间:星期六, 2009年3月28日 浏览:0次 评论:0
  本文举例源代码或素材下载

  设计思想

  我们经常需要在应用中缓存Cache些常用数据供全局使用以提升性能如果需要缓存Cache对象类型和数目是固定我们可能会直接将其声明为;如果我们需要缓存Cache对象类型和数目是不定我们可能会借助 Hashtable来实现但是Hashtable有个缺陷:它没有层次结构它总是以键/值形式来存储数据个Key对应个Value如果我们想获取相关联组数据就会比较困难了

  NOTE:如果你从事Asp.Net开发提起缓存Cache你可能首先会想到Output Cache、数据源缓存Cache或者是基于.Web.Caching.Cache对象缓存Cache实际上缓存Cache就是把对象(数据)存储在内存中不用每次需要对象服务时候都重新创建对象(相对耗时)将对象声明为那么对象将在其所属类被载入AppDo这样对象生命周期和AppDo同样长从而起到缓存Cache

   感兴趣朋友可以做个测试:在站点下新建个Default.aspx文件在后置代码中添加如下代码:

public Test {
      public DateTime a = DateTime.Now;
      public DateTime b = DateTime.Now;
}

protected void Page_Load(object sender, EventArgs e) {
      Test t = Test;
      Label1.Text = Test.a. + "<br />"; //Label1为页面上个LabelControl控件
      Label1.Text t.b.;      
}


  结果是只要站点不重启(代码也不修改)那么a值是恒定不变即使将页面关了重新打开也可见a只在Test类加载到AppDo中进行了而b在每次刷新时都会改变每次请求页面都会在创建Test类型例子时重新对a进行

  NOTE:声明为静态()个特例是声明为const这是const天生就是但它局限性是对象类型必须为诸如或者简单类型除此以外声明为const对象将不再是变量而是个常量例如const a = "abc"; 相当于给类型串"abc"起了个别名叫a因此const必须在声明时就赋值

  XML文档结构是树形具有标准层次结构XPath用于从Xml文档中选择个或多个结点比如 "/BookStore/Book"选择Book结点下所有子结点

  SAF 中缓存Cache服务通过个在内存中动态构造Xml文档树作为桥梁将 静态()缓存Cache 和 XPath 这两个技术结合了起来支持使用XPath语法来获取Hashtable中对象其中静态缓存Cache进行实际数据缓存CacheXPath用于获取数据对象角度来看即是HashtableKey支持了XPath语法可以将原本“平板式”Hashtable想象成为个“树形结构”结点包含了缓存Cache数据我们通过标准XPath到达结点(当然这只是个假象)并获取数据通过这种方式就可以使用XPath来次获取Hashtable中多个相关数据对象

  而实际上是如何实现这过程呢?我们步步来看:

  首先在内存中动态构建个 Xml文档它只包含个根结点可以任意命名这里将它命名为了Cache

  提供个Xpath路径:获取对象(数据)前首先要存储对象存对象自然要先提供个路径(这里称为“路径”它是个XPath实际上也就相当于Hashtable中键Key)

  根据上步提供路径以Cache为根结点逐层深入地创建XmlNode结点

  生成个GUID在叶结点上添加个Key属性为这个Key属性赋值为GUID

  在Hashtable中存储对象其中HashtableKey即为上步生成GUID而Value为要存储对象

  使用这种方式Hashtable实际Key即动态生成GUID对员来说是透明员在存储/获取对象时只需要提供XPath表达式就可以下面这幅图介绍说明了它们的间关系:

SAF 中缓存Cache服务<img src='/icons/25450de.gif' />实现

  这里还需要再介绍说明 3点:

  我们使用Hashtable存储对象可以直接将Hashtable声明为也可以将Hashtable声明为instance但是将Hashtable所属对象声明为这里应用了Singleton模式先将对Hashtable操作封装成个类然后在这个类上应用Singleton模式确保了这个类只有个(这个类所维护Hashtable例子自然也只有个了)很明显这个类包含了主要逻辑我们将的命名为Cache

  使用Hashtable好处是可以存储任何类型对象缺点是丧失了类型安全有时候我们可能会想使用个泛型集合类来取代Hashtable比如Dictionary<T key, T value>所以这里又引入了Strategy模式创建了个ICacheStrategy接口这个接口包括 3个思路方法分别用于添加、获取、删除对象

  用Xpath获取结点时可以是基于当前结点相对路径;也可以是基于根结点绝对路径在本文范例使用是绝对路径显然这样更加方便

  类型接口

  我们先看下类型组织然后再看实现

  ICacheStrategy用于定义如何添加、获取、删除欲进行缓存Cache对象实际上在接口实体类中要明确使用何种类型来存储对象是Dictionary还是Hashtable或者其他

public erface ICacheStrategy {  
    void AddItem( key, object obj);// 添加对象
    object GetItem( key);      // 获取对象
    void RemoveItem( key); // 删除对象
}


  接下来是Cache类这个类包含了主要逻辑包括 动态构建XML文档、将Xml文档映射到Hashtable 等

public Cache {
    void AddItem( xpath, object obj);
    object GetItem( xpath);
    object GetList( xpath);
    void RemoveItem( xpath);
}


  仅从接口上看这个类似乎和ICacheStrategy没有太大分别实际上这个类保存了个对于ICacheStrategy类型例子引用最后实际工作都委托给了ICacheStrategy去完成而在此的前各个思路方法工作主要是由 Xml结点到Hashtable映射(这里说是Hashtable它是作者提供个默认实现当然也可以是其他)

  类型实现

  我们首先看DefaultCacheStrategy它实现了ICacheStrategy接口并使用Hashtable存储对象

public DefaultCacheStrategy : ICacheStrategy {
    private Hashtable objectStore;

    public DefaultCacheStrategy {
       objectStore = Hashtable;
    }

    public void AddItem( key, object obj) {
       objectStore.Add(key, obj);
    }

    public object GetItem( key) {
       objectStore[key];
    }

    public void RemoveItem( key) {
       objectStore.Remove(key);
    }
}


  接下来我们步步地看Cache类实现下面是Cache类字段以及构造(注意为私有)

public Cache {
    private XmlElement rootMap;             // 动态构建 Xml文档 根结点
    private ICacheStrategy cacheStrategy;   // 保存对ICacheStrategy引用
    public readonly Cache Instance = Cache;  // 实现Singleton模式
    private XmlDocument doc = XmlDocument;   // 构建 Xml文档

    // 私有构造用来实现Singleton模式
    private Cache {
       // 这里应用了Strategy模式
       // 改进:可以将使用何种Strategy定义到app.config中然后使用反射来动态创建类型
       cacheStrategy = DefaultCacheStrategy;

       // 创建文档根结点用于映射 实际数据存储(例如Hashtable) 和 Xml文档
       rootMap = doc.CreateElement("Cache");
      
       // 添加根结点
       doc.AppendChild(rootMap);
    }
    // 略...
}


  Cache类还包含两个私有思路方法PreparePath用于对输入Xpath进行格式化使其以构造中创建根节点("Cache")作为根结点(这样做是可以使你在添加/获取对象时免去写根结点麻烦);CreateNode 用于根据XPath逐层深入地创建Xml结点

// 根据 XPath 创建个结点
private XmlNode CreateNode( xpath) {

    xpathArray = xpath.Split('/');
    nodePath = "";

    // 父节点
    XmlNode parentNode = (XmlNode)rootMap; 

    // 逐层深入 XPath 各层级如果结点不存在则创建
    // 比如 /DvdStore/Dvd/NoOneLivesForever
    for ( i = 1; i < xpathArray.Length; i) {
      XmlNode node = rootMap.SelectSingleNode(nodePath + "/" + xpathArray[i]);

       (node null) {
           XmlElement Element = rootMap.OwnerDocument.CreateElement(xpathArray[i]);   // 创建结点
           parentNode.AppendChild(Element);
       }

       // 创建新路径更新父节点进入下
       nodePath = nodePath + "/" + xpathArray[i];
       parentNode = rootMap.SelectSingleNode(nodePath);
    }

    parentNode;
}

// 构建 XPath使其以 /Cache 为根结点并清除多于"/"
private PrepareXPath( xpath) {
    xpathArray = xpath.Split('/');
    xpath = "/Cache";     // 这里名称需和构造中创建根结点名称对应
    foreach ( s in xpathArray) {
       (s != "") {
           xpath "/" + s;
       }
    }
    xpath;
}


  AddItem思路方法用于向缓存Cache中添加对象包括了下面几个步骤:

  根据输入XPath判断到达 叶结点 路径是否已经存在如果不存在上面CreateNode思路方法逐层创建结点

  生成GUID在组结点下创建 XmlNode 叶结点为叶结点添加属性Key并将值设为GUID

  将对象保存至实际位置默认实现是个Hashtable通过ICacheStrategy.AddItem思路方法来完成并将HashtableKey设置为GUID

  NOTE: 为了介绍说明方便这里有个我对类结点命名--“组结点”假设有XPath路径:/Cache/BookStore/Book/Title那么/Cache/BookStore/Book即为“组结点”称其为“组结点”其下可包含多个叶结点比如 /Cache/BookStore/Book/Author 包含了叶结点 Author;而/Cache/BookStore/Book/Title 中Title为叶结点GUID存储在叶结点属性中需要注意 组结点 和 叶结点是相对对于路径 /Cache/BookStore/Book 来说组结点就是“/Cache/BookStore”而 Book是它叶结点

  下面是AddItem思路方法完整代码:

// 添加对象对象实际上还是添加到ICacheStrategy指定存储位置
// 动态创建 Xml 结点仅保存了对象Id(key)用于映射两者间关系
public virtual void AddItem( xpath, object obj) {

    // 获取 Xpath例如 /Cache/BookStore/Book/Title
    Xpath = PrepareXPath(xpath);

    separator = Xpath.LastIndexOf("/");

    // 获取组结点层叠顺序 例如 /Cache/BookStore/Book
    group = Xpath.Sub(0, separator);

    // 获取叶结点名称例如 Title
    element = Xpath.Sub(separator + 1);

    // 获取组结点
    XmlNode groupNode = rootMap.SelectSingleNode(group);

    // 如果组结点不存在创建的
    (groupNode null) {
       lock (this) {
           groupNode = CreateNode(group);
       }
    }

    // 创建个唯 key 用来映射 Xml 和对象主键
    key = Guid.NewGuid.;

    // 创建个新结点
    XmlElement objectElement = rootMap.OwnerDocument.CreateElement(element);
   
    // 创建结点属性 key
    XmlAttribute objectAttribute = rootMap.OwnerDocument.CreateAttribute("key");

    // 设置属性值为 刚才生成 Guid
    objectAttribute.Value = key;

    // 将属性添加到结点
    objectElement.Attributes.Append(objectAttribute);

    // 将结点添加到 groupNode 下面(groupNode为Xpath层次部分)
    groupNode.AppendChild(objectElement);

    // 将 key 和 对象添加到实际存储位置比如Hashtable
    cacheStrategy.AddItem(key, obj);
}


  RemoveItem则用于从缓存Cache中删除对象它也包含了两个步骤:1、先从Xml文档树中删除结点;2、再从实际存储位置(Hashtable)中删除对象这里需要注意是:如果XPath指定个叶结点那么直接删除该结点;如果XPath指定是组结点那么需要删除组结点下所有结点代码如下:

// 根据 XPath 删除对象
public virtual void RemoveItem( xpath) {

    xpath = PrepareXPath(xpath);
    XmlNode result = rootMap.SelectSingleNode(xpath);

    key;           // 对象Id

    // 如果 result 是个组结点(含有子结点)
    (result.HasChildNodes) {

       // 选择所有包含有key属性结点
       XmlNodeList nodeList = result.SelectNodes("descendant::*[@key]");
      
       foreach (XmlNode node in nodeList) {

           key = node.Attributes["key"].Value;

           // 从 Xml 文档中删除结点
           node.ParentNode.RemoveChild(node);

           // 从实际存储中删除结点
           cacheStrategy.RemoveItem(key);
       }
    } {      // 如果 result 是个叶结点(不含子结点)

       key = result.Attributes["key"].Value;
       result.ParentNode.RemoveChild(result);
       cacheStrategy.RemoveItem(key);
    }
}


  最后两个思路方法GetItem和GetList分别用于从缓存Cache中获取单个或者多个对象值得注意是当使用GetList思路方法时Xpath应该为到达个组结点路径

// 根据 XPath 获取对象
// 先根据Xpath获得对象Key然后再根据Key获取实际对象
public virtual object GetItem( xpath) {

    object obj = null;
    xpath = PrepareXPath(xpath);
    XmlNode node = rootMap.SelectSingleNode(xpath);

    (node != null) {
       // 获取对象Key
       key = node.Attributes["key"].Value;

       // 获取实际对象
       obj = cacheStrategy.GetItem(key);
    }
    obj;
}

// 获取组对象此时xpath为个组结点
public virtual object GetList( xpath) {
    xpath = PrepareXPath(xpath);

    XmlNode group = rootMap.SelectSingleNode(xpath);

    // 获取该结点下所有子结点(使用[@key]确保子结点定包含key属性)
    XmlNodeList results = group.SelectNodes(xpath + "/*[@key]");

    ArrayList objects = ArrayList;

    key;

    foreach (XmlNode result in results) {
       key = result.Attributes["key"].Value;
       Object obj = cacheStrategy.GetItem(key);
       objects.Add(obj);
    }

    (object)objects.ToArray(typeof(object));
}


  至此SAF 缓存Cache服务设计和代码实现都完成了现在我们来看看如何使用它

  测试

void Main( args) {

    CacheService.Cache cache = CacheService.Cache.Instance;

    // 添加对象到缓存Cache中
    cache.AddItem("/WebApplication/Users/Xin", "customer xin");
    cache.AddItem("/WebApplication/Users/Jimmy", "customer jimmy");
    cache.AddItem("/WebApplication/Users/Steve", "customer other");
    cache.AddItem("/WebApplication/GlobalData", "1/1/2008");
    cache.AddItem("/Version", "v10120080401");
    cache.AddItem("/Site", "TraceFact.Net");

    // 获取所有User
    object objects = cache.GetList("/WebApplication/Users");
    foreach (object obj in objects) {
       Console.WriteLine("Customer in cache: {0}", obj.);
    }

    // 删除所有WebApplication下所有子孙结点
    cache.RemoveItem("/WebApplication");

    // 获取单个对象
    time = ()cache.GetItem("/WebApplication/GlobalData");
    name = ()cache.GetItem("/WebApplication/Users/Xin");

    Console.WriteLine("Time: {0}", time);// 输出为空WebApplication下所有结点已删除
    Console.WriteLine("User: {0}", name);// 输出为空, WebApplication下所有结点已删除
   

    // 获取根目录下所有叶结点
    objects = cache.GetList("/");
    foreach (object obj in objects) {
       Console.WriteLine("Object: {0}", obj.);
    }

    Console.ReadLine;
}




  输出结果为:

Customer in cache: customer xin
Customer in cache: customer jimmy
Customer in cache: customer other
Time:
User:
Object: v10120080401
Object: Trace




0

相关文章

读者评论

发表评论

  • 昵称:
  • 内容: