随笔 - 196  文章 - 43  评论 - 4287 

        作为一个社区类型软件,大并发支持和高效稳定运行永远是“硬道理”,而有效安全的使用
缓存恰恰能起到事倍功半的效果。而.NET本身所提供的缓存机制又显得过于“单薄”,比如说订
制不太灵活方便, 缓存对象之间层次感不强, 使用时缺乏统一的管理等等。
   
         Discuz!NT缓存产生背景:
         在去年五月份我加入Discuz!NT项目组时,发现这个项目当时还未使用缓存机制。主要原因
是项目还处于起步阶段,很多东西还只是有想法,但未付诸实施,或还没找到合适的方案, 而
缓存就是其中一个到底该不该使用,如果使用的该到底能多大程度缓解数据库压力以及开发成本
的东西。
        我当时正好有一个比较好的“原型”(从一本书上看到的源码),也就是今天Discuz!NT所
使用的缓存机制的雏形,但当时它在功能上还很不健全且存在一些“致命的” BUG, 但实现简
单的缓存数据对象还是绰绰有余的,于是我通过一个简单的测试用例(缓存数据表和StringBuilder
对象)和雪人一起讨论并分析后得到一些数据,基本上肯定了使用缓存解决对数据库象中经常访
问但又不经常更新的数据进行缓存的使用方案,同时也要求这个缓存机制要使用起来尽可能的简
单,同时功能扩展要非常方便。
         因此本人就在这个“原型”的基本上进行了一段时间的功能扩展和BUG修正才得到今天大家
所看到的这部分代码。


         现在将Discuz!NT的缓存架构说明如下,先请大家看一下Discuz!NT架构图:

    
    
  
    
    
         其实这个构架说白了就是一个标准的“策略”模式,为了对比方便,我把策略模式的结构
图放在下面:


         看到了吧,里面的DNTCache就是“策略”模式的应用场景,而DefaultCache , ForumCache
,RssCache等等就是相应的具体策略,每一种策略都会对.net所提供的缓存机制进行一番“订制”
,以实现不同的用途。比如系统DefaultCache在对象到期时提供数据再次加载机制,而ForumCache
而不使用这种机制,另外还有缓存的到期时间几种策略也各不相同,这都是根据具体的应用场景
"量身订制"的。


         说到这里,您所要做的就是下载一份源码按上图索骥就可以把整个缓存机制搞清楚。

         下面对缓存设计所采用的几种技术做一下简要说明。包括XML,XPATH ,"单件模式" 以及跨
web园共享数据。


         首先请看一下代码:(xml xpath)

 

 1 //要存取的xpath格式路径
 2 //要缓存的对象
 3 public virtual void AddObject(string xpath, object o ,string[] files)
 4 {
 5  
 6  //整理XPATH表达式信息
 7  string newXpath = PrepareXpath(xpath);
 8  int separator = newXpath.LastIndexOf("/");
 9  //找到相关的组名
10  string group = newXpath.Substring(0,separator  );
11  //找到相关的对象
12  string element = newXpath.Substring(separator + 1);
13    
14  XmlNode groupNode = objectXmlMap.SelectSingleNode(group);
15  //建立对象的唯一键值, 用以映射XML和缓存对象的键
16  string objectId="";
17 
18  XmlNode node = objectXmlMap.SelectSingleNode(PrepareXpath(xpath));
19  if ( node != null)
20  {
21   objectId = node.Attributes["objectId"].Value;
22  }
23  if(objectId=="")
24  {
25   groupNode = CreateNode(group);
26   objectId= Guid.NewGuid().ToString();
27   //建立新元素和一个属性 for this perticular object
28   XmlElement objectElement = objectXmlMap.OwnerDocument.CreateElement(element);
29   XmlAttribute objectAttribute =objectXmlMap.OwnerDocument.CreateAttribute("objectId");
30   objectAttribute.Value = objectId;
31   objectElement.Attributes.Append(objectAttribute);
32   //为XML文档建立新元素
33   groupNode.AppendChild(objectElement);
34  }
35  else
36  {
37   //建立新元素和一个属性 for this perticular object
38   XmlElement objectElement = objectXmlMap.OwnerDocument.CreateElement(element);
39   XmlAttribute objectAttribute =objectXmlMap.OwnerDocument.CreateAttribute("objectId");
40   objectAttribute.Value = objectId;
41   objectElement.Attributes.Append(objectAttribute);
42   //为XML文档建立新元素
43   groupNode.ReplaceChild(objectElement,node);
44  }
45  //向缓存加入新的对象
46  cs.AddObjectWithFileChange(objectId,o,files);
47  
48 }
49 

 

         为什么要用XML, 主要是为了使用XML中的层次化功能以及相关的结点添加,替换,移除,
还有就是当希望对缓存的结构信息进行“持久化”操作时会很方便等。
         XPATH 便于能过层次表达式(hierarchical expression) 对XML文件进行查找搜索。
        通过上面或其它的类似代码,就可以构建起一个xml树来管理已加入到系统的缓存对象了。


   
         使用"单件模式"模式生成全局唯一的“应用场景”,因为缓存这种东西通常在存储共享
数据时它的效果最好,编码也最容易实现和管理,同时项目本身基本上就是对经常访问但不
经常改变的数据库数据(可看成是共享数据)进行缓存,所以使用单件模式就顺理成章了。
        请看如下代码:

public static DNTCache GetCacheService()
{
   
 
if (instance == null)
 {
  
lock (lockHelper)
  {
   
if (instance == null)
   {
    instance 
= new DNTCache();
   }
  }
 }

 
//检查并移除相应的缓存项
 
//注:此处代码为即将发布的2.0版本中的代码类,如果您想了解其中
        
//的代码可参见开源版本中的Discuz.Forum.cachefactory.cs文件中
 
//相应函数
 instance=CachesFileMonitor.CheckAndRemoveCache(instance);

 
return instance;
}

 

        小插曲:

         1.项目到了beta版时出现了无法跨web园共享数据的问题。它的表现是这样的,当你在IIS
服务的应用程序池中设置2个或以上的WEB园时,这时你在后台更新缓存时,就是出现缓存
“隔三差五”数据不更新或轮换更新的情况。说白了,就是只有一个应用进程中的数据缓存
被更新,而其余的进程中所有数据还没事人似的保留原有的面貌。这个问题主要是因为static
的数据实例(也就是上面所有的单体代码中的对象)虽然而当前进程中“唯一”,但在其它进程
中却各自都有一个造成的。一开始我也很惊讶,为什么微软不能像提供“全局”钩子那样的技术
一样提供一种跨WEB园来共享数据的技术或关键字呢,不过一转念也猜出了一二分,必定多WEB园
是一种让程序(WEB)跑起来更加安全,稳定快速的“解决方案”。 因为谁都不好说自己的程序
一点BUG没有,即有真有这样的代码,但当遇上运行环境这个因素后,也会表现得有些难以控制。
但微软通过web园这个技术就会把运行在几个不同进程下的程序相互隔离,使其谁也不影响到谁,
即使其中一个进程down了,而其它进程依就会继续正常 "工作" 。因此程序中的对象实例和所有
资源每个进程中都会保存一份,完全相同。而如果引用共享机制就有可能出现当进程共享的数据
或程序对象出现问题时,所有进程就可能都玩完了, 因此就需要进程隔离。

         说是这么说,但总也要想个办法解决当时面临的问题吧。记得在豪杰工作期间,一次老梁
给我们开会,其中的一段话我至今还记忆犹新,他说CPU访问内存的速度和访问硬盘的速度在某些
情况下是相近的,如果我没理解的话比如说“虚拟缓存”或最新频繁访问的硬盘区段,这些地方
的代码或文件会有比较高的运行和访问效率。因此,我想到了使用文件标志关联的方法来解决这
个多进程问题。接着就顺理成章的使用了文件修改日期这个属性进行在多进程下缓存是否更新的
依据了,大家可以到开源下载包中的config文件夹下把一个cache.config的文件,对应最新的数
据项再回过头来看如下代码就会一清二楚了:

 

public static DNTCache CheckAndRemoveCache(DNTCache instance)//
 {
      
//当程序运行中cache.config发生变化时则对缓存对象做删除的操作
      cachefilenewchange = System.IO.File.GetLastWriteTime(path);
      
if (cachefileoldchange != cachefilenewchange)
      {
                
lock (cachelockHelper)
                {
                    
if (cachefileoldchange != cachefilenewchange)
                    {
                        
//当有要清除的项时
                        DataSet dsSrc = new DataSet();
                        dsSrc.ReadXml(path);
                        
foreach (DataRow dr in dsSrc.Tables[0].Rows)
                        {
                            
if (dr["xpath"].ToString().Trim() != "")
                            {
                                DateTime removedatetime 
= DateTime.Now;
                                
try
                                {
                                    removedatetime 
= Convert.ToDateTime(dr["removedatetime"].ToString().Trim());
                                }
                                
catch {;}

                                
if (removedatetime > cachefilenewchange.AddSeconds(-2))
                                {
                                    
string xpath = dr["xpath"].ToString().Trim();
                                    instance.RemoveObject(xpath, 
false);
                                }
                            }
                        }

                        cachefileoldchange 
= cachefilenewchange;

                        dsSrc.Dispose();
                    }
                }
      }
      
return instance;
}


         2.另外需要说明的是在4月份时缓存机制出现了一些问题,比如缓存数据丢失以及在.net2下
的死循环的问题,后来在雪人的建议下采用每个缓存都有缓存标志来解决数据丢失的问题。也就
是如下的代码段:

 1 //添加时
 2 public virtual void AddObject(string xpath, DataTable dt)  
 3 {
 4     lock(lockHelper)
 5     {
 6  if(dt.Rows.Count>0)
 7  {
 8   AddObject(xpath+"flag", CacheFlag.CacheHaveData);
 9  }
10  else
11  {
12   AddObject(xpath+"flag", CacheFlag.CacheNoData);
13  }
14  AddObject(xpath, (object) dt);
15     }
16 }
17 
18 
19 //获取时
20 public virtual object RetrieveObject(string xpath)
21 {
22  try
23  {
24   object cacheObject = RetrieveOriginObject(xpath);
25   CacheFlag cf = (CacheFlag) RetrieveOriginObject(xpath+"flag");
26    
27   //当标志位中有数据时
28   if(cf ==CacheFlag.CacheHaveData)  
29   {
30                   string otype = cacheObject.GetType().Name.ToString();
31 
32              //当缓存类型是数据表类型时
33      if(otype.IndexOf("Table")>0)  
34             {
35    System.Data.DataTable dt = cacheObject as DataTable;
36                  if ((dt == null|| (dt.Rows.Count == 0))
37                         {
38                             return null;
39                         }
40                         else 
41                         {
42                             return cacheObject;
43                         }
44      }
45          
46 }
47 

 

         而死循环的问题主要是因为.net2下的缓存回调加载机制和程序本身的一个BUG造成的,目前
已修正, 大家请放心使用。


         目前已开发但还未使用的功能:
         1.一键多值:请看DNTCache代码段中的AddMultiObjects(string xpath,object[] objValue)
,获取时使用object[] RetrieveObjectList(string xpath)方法返回即可,这样就可以用一个xpath
来存取一组对象了。
        它的实现代码也相对简单,这里就不多说了,只把代码贴在此处。

public virtual bool AddMultiObjects(string xpath,object[] objValue)
{   
 
lock(lockHelper)
 {
  
//RemoveMultiObjects(xpath);
  if (xpath != null && xpath != "" && xpath.Length != 0 && objValue != null)
  {
   
   
for (int i = 0; i < objValue.Length; i++)
   {
    AddObject(xpath 
+ "/Multi/_" + i.ToString(),objValue[i]); 
   }
  
   
return true;
  }
  
return false;
 }
}

         2.批量移除缓存
          它主要是利用XML有按路径层次存储的特点才这样做的,主要是去掉位于当前路径下的所有
子结点的缓存数据。
         它的函数声明如下:RemoveObject(string xpath, bool writeconfig)
         它的实现代码也相对简单,这里就不多说了, 只把代码贴在此处。

 1 public virtual void RemoveObject(string xpath, bool writeconfig)
 2 {
 3  lock(lockHelper)
 4  {
 5   try
 6   {
 7    if(writeconfig)
 8    {
 9                          CachesFileMonitor.UpdateCacheItem(xpath);
10    }
11 
12    XmlNode result = objectXmlMap.SelectSingleNode(PrepareXpath(xpath));
13    //检查路径是否指向一个组或一个被缓存的实例元素
14    if (result.HasChildNodes)
15    {
16     //删除所有对象和子结点的信息
17     XmlNodeList objects = result.SelectNodes("*[@objectId]");
18     string objectId = "";
19     foreach (XmlNode node in objects)
20     {
21      objectId = node.Attributes["objectId"].Value;
22      node.ParentNode.RemoveChild(node);
23      //删除对象
24      cs.RemoveObject(objectId);
25     }
26    }
27    else
28    {
29     //删除元素结点和相关的对象
30     string objectId = result.Attributes["objectId"].Value;
31     result.ParentNode.RemoveChild(result);
32     cs.RemoveObject(objectId);
33    }
34 
35    //检查并移除相应的缓存项
36   }
37   catch
38   {    //如出错误表明当前路径不存在
39   }
40  }
41 }
42 
43 

    
         已开发出来,但却去掉了的功能。
         在正式版出现之前,后台管理中有记录缓存日志的功能,它的实现方式是基于"访问者"模式实现的
(大家应该可以在项目中找到这个类LogVisitor)。但因为后来不少站长反映日志表操作的过于频繁导
致日志记录急剧增加,而把这部分功能拿下了。我在这里说出来就是想给大家提个醒,对于新功能或新
技术的追求要非常谨慎,要不就会出现您费尽千辛万苦开发的功能,最后却没人买帐就郁闷了。

         最后需要说明的就是,为什么要先把这块功能先发到园子里来。因为我们产品的Discuz!NT2.0产品
即将发布,而整个产品的架构也出现了不少变化,而由于缓存结构相对稳定,所以变化的不大。这才在
今天发个BLOG讲给大家的,下一篇关于DISCUZ!NT架构的文章要等到正式版发布之后了。到时大家下
载代码之后再对照新代码给大家聊聊这个产品的其它设计思路(按我的理解)。

posted on 2007-08-15 09:13 代震军 阅读(9888) 评论(132)  编辑 收藏 网摘 所属分类: 设计模式VS2005Discuz!NT

评论共2页: 上一页 1 2 
  回复  引用    
2007-10-30 18:33 | 阿瑞2[未注册用户]
支持~~~~,不知道源码怎么样,还没看见!!! 简单才是最稳定最快的 分的层数越多速度越慢 没什么关系了 :)
  回复  引用    
2007-11-06 16:44 | discuz! fans[未注册用户]
public virtual void AddObject(string xpath, string str)
{
lock (lockHelper)
{
if ((str != null) && (str != ""))
{
this.AddObject(xpath + "flag", CacheFlag.CacheHaveData);
}
else
{
this.AddObject(xpath + "flag", CacheFlag.CacheNoData);
}
this.AddObject(xpath, str);
}
}


是不是死循环??????

  回复  引用  查看    
2007-11-08 16:43 | daizhj      
这个地方其实是一个重载, addobject有不下6个重载方法,但大部分最后都会调用AddObject(string xpath, object o)这个方法。如果您有时间可以看一下开源代码中的这一部分就会明白了.绝不会出现死锁那种可怕问题.
因为我们代码在此处是this.AddObject(xpath, (object)str);而不是this.AddObject(xpath, str);
同时感谢您对我们产品的关注:)

  回复  引用    
2007-12-06 13:26 | zxp[未注册用户]
--引用--------------------------------------------------
discuz! fans: public virtual void AddObject(string xpath, string str)
{
lock (lockHelper)
{
if ((str != null) &amp;&amp; (str != &quot;&quot;))
{
this.AddObject(xpath + &quot;flag&quot;, CacheFlag.CacheHaveData);
}
else
{
this.AddObject(xpath + &quot;flag&quot;, CacheFlag.CacheNoData);
}
this.AddObject(xpath, str);
}
}


是不是死循环??????
--------------------------------------------------------\

就是死循环!!!
个人认为应该改成 this.AddObject(xpath, (object)str);

  回复  引用  查看    
2007-12-06 13:51 | daizhj      
@zxp
您所说的(object)转型是我们代码中都已写的,只是reflector反射时没有得出而已.如果您手头上有源码一看便知.
同时感谢您对我们产品的关注和支持:)

  回复  引用    
2007-12-25 16:51 | 111111111111111111111111111[未注册用户]
请问在负载均衡下, cache 有没有什么解决方案
  回复  引用  查看    
2007-12-25 17:28 | 代震军      
目前的设计架构只要再多开发一些代码就会更好的支持负载均衡了。我们会认真考虑这个问题的,必定论坛要能经受住大流量,大并发呀.

同时感谢您对我们产品的支持和关注:)

  回复  引用  查看    
2007-12-26 09:29 | wyifan      
我看到IDataProvider提供了所有的数据访问,请问楼主为什么没有按功能模块分隔开呢,这样岂不是不利于查看和并行开发吗?
  回复  引用  查看    
2007-12-26 09:35 | wyifan      
像如下代码段:
Discuz.Cache.ICacheStrategy ics = new ForumCacheStrategy();
ics.TimeOut = cachetime;
cache.LoadCacheStrategy(ics);
cache.AddObject(cacheKey, dt);
cache.LoadDefaultCacheStrategy();
怎么没有封装起来呢?

  回复  引用  查看    
2007-12-26 09:36 | 代震军      
@wyifan
首先感谢您的回复,您的这个问题很有代表性。相信一上来看到这么多的接口函数,谁都会晕上一阵子,不过因为这些接口函数是论坛,空间和相册所必须的,所以才会这样归在一起实现,当然也可以按功能,按类型进行分离。我会在下周发的一篇文章(讨论MVC和域模型)中讨论一下这个问题,相信到时你会有更多的体会.

  回复  引用  查看    
2007-12-26 09:36 | wyifan      
最后一句cache.LoadDefaultCacheStrategy();是做什么用的?是不是可以不要?
  回复  引用  查看    
2007-12-26 09:39 | 代震军      
@wyifan
这一行是必须的,要不其它缓存的数据程序就会按ForumCacheStrategy所声明的缓存策略来加载了,到时有些数据的存活时间就会按ForumCacheStrategy
来设置了。

  回复  引用  查看    
2007-12-26 09:46 | 代震军      
@wyifan
其实这段代码要实现的就是“策略”(GOF)模式下,动态"相互替换"的思想。主要就是改变当前缓存的处理方式(与DefaultCacheStrategy不同).
如果封装可能会有多此一举的嫌疑,另外就是想给调用缓存策略的程序员或者用户多一些灵活定义的考虑,因为此才没封装。
如果您认为这块不想享受这种“灵活性”,那完全可以自己作进一步的封装(下份源码修改一下)即可,不会出什么问题的:)

  回复  引用    
2007-12-26 09:47 | 111111111111111111111111111[未注册用户]
谢谢搂主的回答,还提供了这么多的文章. 也希望 Discuz!NT 缓存 能够解决好负载均衡下缓存同步的问题. 要好好学习一下
  回复  引用  查看    
2007-12-26 09:49 | wyifan      
哦,明白了,cache是一个单件,谢谢。
不过感觉这样设计有提升的空间,每次使用完一个策略之后,就要还原成缺省的策略

  回复  引用  查看    
2007-12-26 10:33 | 代震军      
@wyifan
其实关于使用完一个策略之前到底该不该还原成"缺省的策略",还是变成别的策略,这块都是可以转换的,甚至将来的负载均衡也可能会考虑这块最终的布署和设置方式.

  回复  引用  查看    
2007-12-26 14:35 | 代震军      
@111111111111111111111111111
我们会努力解决好这个问题的,同时感谢您对我们产品的支持和关注:)

  回复  引用  查看    
2007-12-26 16:59 | 拒绝潜水的鱼      
discuznt2.0 终于来了,发现楼主非常的细心,每个回复都仔细的看,认真的回复,那么楼主的程序思想应该是不错的。实际上的确不错,NT2.0 的性能在NT1的基础上高了好多。我还在努力学习NT的代码及模式中 哈哈!
  回复  引用  查看    
2007-12-26 17:02 | 代震军      
@拒绝潜水的鱼
过奖了,说来惭愧。不过我会继续努力的,希望能给大家带来更多的实惠.

同时感谢您对我们产品的支持和关注:)

  回复  引用  查看    
2007-12-27 16:45 | 暗香浮动      
其实觉得应该把xml清单文件列出的就清楚多了。
  回复  引用  查看    
2007-12-27 17:18 | 代震军      
@暗香浮动
这个问题以前我也考虑过,当时是想在后台加一个列表来显示所有的缓存项(按树形结构显示)这样大家就可以一目了然了。
后来因为别的原因被搁置了。不过以后是会考虑扩充一下这块的管理功能的:)

看来您和我都想到一块去了:)

  回复  引用  查看    
2008-01-14 00:15 | 拒绝潜水的鱼      
想问一下 Discuz.Aggregation是什么意思
  回复  引用  查看    
2008-01-14 09:27 | 代震军      
@拒绝潜水的鱼
这个是我们产品的聚合类项目(Aggregation:聚合)
如果感兴趣,您可以看一下这个链接:
http://www.cnblogs.com/daizhj/archive/2007/09/18/895944.html" target="_new">http://www.cnblogs.com/daizhj/archive/2007/09/18/895944.html

  回复  引用    
2008-02-21 12:09 | 200325074[未注册用户]
请问discuz.forum下的cache类获取缓存的部分,有的加锁或者有的将返回的datetable复制一份,这样做是为了保持数据同步还是什么,而有的部分没有这样做,不明白为什么。

初学,请见谅


加锁,这是为了保护哪部分?

public static string GetTemplateListBoxOptionsCache()
{
lock (lockHelper)
{
Discuz.Cache.DNTCache cache = Discuz.Cache.DNTCache.GetCacheService();
string str = cache.RetrieveObject("/Forum/UI/TemplateListBoxOptions") as string;
if (str != null)
{
return str;
}

StringBuilder sb = new StringBuilder();
DataTable dt = Templates.GetValidTemplateList();
foreach (DataRow dr in dt.Rows)
{
sb.Append("<option value=\"");
sb.Append(dr["templateid"].ToString());
sb.Append("\">");
sb.Append(dr["name"].ToString().Trim());
sb.Append("</option>");
}
cache.AddObject("/Forum/UI/TemplateListBoxOptions", sb.ToString());


dt.Dispose();
return sb.ToString();
}
}


dt.copy()是为什么?

public static string GetSmiliesCache()
{

Discuz.Cache.DNTCache cache = Discuz.Cache.DNTCache.GetCacheService();
string str = cache.RetrieveObject("/Forum/UI/SmiliesList") as string;
if(str != null)
{
return str;
}

StringBuilder builder = new StringBuilder();
DataTable dt = Smilies.GetSmiliesListDataTable();

foreach (DataRow drCate in dt.Copy().Rows)
{
if (drCate["type"].ToString() == "0")
{
builder.AppendFormat("'{0}': [\r\n", drCate["code"].ToString().Trim().Replace("'", "\\'"));
bool flag = false;
foreach (DataRow dr in dt.Rows)
{
........
}
}

}
builder.Remove(builder.Length - 3, 3);

cache.AddObject("/Forum/UI/SmiliesList", builder.ToString());
return builder.ToString();
}

  回复  引用  查看    
2008-02-26 09:23 | 代震军      
主要是解决并发上来时,出现对象引用为空的问题:
特别是下面这句:
Discuz.Cache.DNTCache cache = Discuz.Cache.DNTCache.GetCacheService();

以及使用cache时:
cache.RetrieveObject("/Forum/UI/TemplateListBoxOptions") as string;

  回复  引用    
2008-02-27 11:12 | 200325074[未注册用户]
多谢代大哥,经你提示又google了一把,大体上明白了,这样做主要是有时cache会丢失

但是上面dt.Copy()是做什么用的,dt应该是个临时变量,为什么要copy一下而不直接用呢

  回复  引用    
2008-03-07 20:48 | ff[未注册用户]
请问代大哥,在什么时候进行写入操作?

如果要对论坛外的其它数据(比如网站新闻资料)进行写入缓存和读出,应怎么调用?

  回复  引用  查看    
2008-03-21 11:54 | 代震军      
@ff
您所说的写入操作应该是读数据加载到缓存中吧,如果是这样的话,目前是当节点项值为空时才进行加载,相当于是谁用谁负责判断并加载:)

  回复  引用    
2008-04-02 19:18 | 老柴[未注册用户]
确实不错,我也看了你们的开源代码,有很大的参考价值.谢谢了.
  回复  引用    
2008-04-09 09:05 | 小张2[未注册用户]
有一个问题一直不太明白
<1>\cache\showtopic 这个目录是干什么用的
<2>如果论坛不在根目录,是不是
cache.AddObject("/Forum/ForumList", __forumlist);
这句话就不对了呢?
<3>缓存XML文件在哪儿放着呢?

小弟水平有限,见谅!

  回复  引用  查看    
2008-04-11 10:11 | 代震军      
@小张2
\cache\showtopic是showtopic页面的绑定数据文件,用于缓存指定主题的数据信息,主要是提升效率,减少数据库访问才用的。
缓存XML文件在discuz.web项目下的config/cache.config文件中。
另外这个论坛根目录问题其实与/Forum/ForumList无关,因为这个“/Forum/”只是为了便于管理缓存信息的路径标识而已。千万别与论坛物理路径挂钩呀,呵呵:)

  回复  引用    
2008-04-12 16:34 | sor[未注册用户]
请问下有哪些数据表用了缓存?
  回复  引用    
2008-04-13 18:29 | 小张2[未注册用户]
感谢代震军,没想到你这么快回了,
我看了看《应用框架的设计与实现》
基本明白了,还是慢慢体会吧,

  回复  引用  查看    
2008-04-14 13:15 | 代震军      
@小张2
欢迎交流

  回复  引用    
2008-05-18 23:55 | 黑白之间[未注册用户]

请问如何判定当前页面数据有变动并更新缓存。

如:发布新帖,主题列表是如何更新缓存的。

本人疑问
1.如果在发布新帖同时更新该版块全部主题列表是否程序负担过重。
2.如果在发布新帖同时只更新第一页题列表那其它页的列表如何得知数据被改并更新缓存。



  回复  引用    
2008-05-19 00:01 | 黑白之间[未注册用户]
续#66楼

突然想到是不是只要删除主题列表所以缓存即可。





急需确切答案...

  回复  引用  查看    
2008-05-19 17:04 | 代震军      
@黑白之间
目前的各版块第一页的主题TID数据是存在TOPIC文件夹下的相应的XML文件中,每次新加主题都会更新相应的XML文件.这样就可以通过访问相应的XML文件中的数据来获取当前版块的主题(第一页)的信息了:)
不知道这样解释您是否明白:)

  回复  引用    
2008-05-20 16:15 | 黑白之间[未注册用户]
您说的只是更新当前第一页的主题缓存,但第一页之后的如何判定更新


第一页(为文章列表页非版块首页)


望代兄继续解答





  回复  引用  查看    
2008-05-20 17:08 | 代震军      
@黑白之间
以后(第1页之后)的主题数据为实时从数据库中提取,具我所知是这样子的:)

  回复  引用    
2008-05-21 02:13 | 黑白之间[未注册用户]
以上问题有所领悟。

还有个问题想请教,

cache.AddObject("/xxxxxxx/xxxxxxxx",dt);

是不是非磁盘文件缓存

  回复  引用  查看    
2008-05-21 09:07 | 代震军      
@黑白之间
上面语句最终会去调用.net所提供的缓存加载方法,您可以在我们的源码中找到这个方法的最终调用.

  回复  引用    
2008-05-26 05:30 | 黑白之间[未注册用户]
非常感谢您的解释
  回复  引用  查看    
2008-05-26 09:24 | 代震军      
@黑白之间
您太客气了,希望您能够继续关注和支持我们的产品:)

  回复  引用    
2008-05-27 17:20 | fengfeng[未注册用户]
今天我看了一下开源的代码,DNTCache这个类设计的感觉不爽,从代码的书写上来看,这个类应该是不止一个人设计过,为什么写了那么多AddObject方法呢?有数据的话,就返回相应的数据,没数据就返回null,设计了那么多的方法,真的有必要吗?
  回复  引用    
2008-05-27 22:15 | 学习DZ缓存者[未注册用户]
看了半天的代码,

还没有搞清楚,数据库中表是怎么样关连缓存的


请指点,谢谢

  回复  引用  查看    
2008-05-28 09:19 | 代震军      
@fengfeng
这个问题主要是之前只有一至两个AddObject,后来发现缓存数据会不定期的丢失,为了解决这个问题,我们在缓存数据键中又绑定了另一个缓存键,用于标识当前缓存键有无数,这样才会多数这些AddObject方法,本质就是要获取相应的数据(object)是否为空或length是否为0等.这样当再获取缓存中的数据时,就可以先进行这方面的判断,如果原本就是NULL或空对象(无数据时)就直接返回NULL,见下面方法:
public virtual object RetrieveObject(string xpath)
{
try
{
object cacheObject = RetrieveOriginObject(xpath);

CacheFlag cf = (CacheFlag)RetrieveOriginObject(xpath + "flag");


//当标志位中有数据时
if (cf == CacheFlag.CacheHaveData)
{
string otype = cacheObject.GetType().Name.ToString();

//当缓存类型是数据表类型时
if (otype.IndexOf("Table") > 0)
{
System.Data.DataTable dt = cacheObject as DataTable;
if ((dt == null) || (dt.Rows.Count == 0))
{
return null;
}
else
{
return cacheObject;
}
}

//当缓存类型是CustomEditorButtonInfo[],string[],UserGroupInfo[],ModeratorInfo[],SmiliesInfo[],ForumInfo[]
if (otype.IndexOf("[]") > 0)
{
if (((Array)cacheObject).Length == 0)
{
return null;
}
else
{
return cacheObject;
}
}

//当缓存类型是CustomEditorButtonInfo[],string[],UserGroupInfo[],ModeratorInfo[],SmiliesInfo[],ForumInfo[]
if (otype.IndexOf("Collection") > 0)
{
if (((CollectionBase)cacheObject).Count == 0)
{
return null;
}
else
{
return cacheObject;
}
}

//当缓存类型是字符串类型时
if (otype == "String")
{
if (cacheObject.ToString() == "")
{
return null;
}
else
{
return cacheObject;
}
}

//当缓存类型是数据行
if (otype.IndexOf("Row") >= 0)
{
if (cacheObject == null)
{
return null;
}
else
{
return cacheObject;
}
}

//当缓存类型是int
if (otype.IndexOf("Int") >= 0)
{
if (((int)cacheObject).Equals(null))
{
return null;
}
else
{
return cacheObject;
}
}

//当缓存类型是SortedList
if (otype.IndexOf("SortedList") >= 0)
{
#if NET1
if(((SortedList)cacheObject).Count==0)
#else
if (((Discuz.Common.Generic.SortedList<int, object>)cacheObject).Count == 0)
#endif
{
return null;
}
else
{
return cacheObject;
}
}

//当缓存类型是CollectionBase
if (otype.IndexOf("CollectionBase") >= 0)
{
if (((CollectionBase)cacheObject).Count == 0)
{
return null;
}
else
{
return cacheObject;
}
}
}

return cacheObject;
}
catch
{
return null;
}
}

  回复  引用  查看    
2008-05-28 09:25 | 代震军      
@学习DZ缓存者
在Discuz.Forum.Dll中有很多这样的方法引用,如下:
Discuz.Cache.DNTCache cache = Discuz.Cache.DNTCache.GetCacheService();

ForumInfo[] forumlist = cache.RetrieveObject("/Forum/ForumList") as ForumInfo[];
if (forumlist == null)
{ //下面代码从数据库版块表中获取数据
DataTable dt = DatabaseProvider.GetInstance().GetForumsTable();
if (dt.Rows.Count > 0)
{
forumlist = new ForumInfo[dt.Rows.Count];
//当有数据时初始化版块对象信息

......

}

.....
//将对象绑定到缓存中
cache.AddObject("/Forum/ForumList", forumlist);

  这样就完成了将数据表信息进行缓存的操作.

  类似这个的代码还是许多,希望我这样解释您能够明白:)

  回复  引用    
2008-05-28 12:02 | 学习DZ缓存者[未注册用户]
谢谢 你的回复

我还有两个问题:

1、如果这个表中的数据有增加,删除操作,没有相应关连,之前的缓存那不是,还存在。就是说,当有增加,删除操作,自动清除缓存.

2、是不是对每个缓存的表都要写一个这样的方法
//添加ForumInfo类型数据缓存
public virtual void AddObject(string xpath, ForumInfo[] forumInfoArray)
{
。。。
}

  回复  引用  查看    
2008-05-28 12:09 | 代震军      
@学习DZ缓存者
1 如果有数据的CRUD操作时,如果希望缓存中的数据变化,目前可以用如下方法(变通),
  先移除缓存:RemoveObject()
  然后再重新加载缓存:AddObject()

2 只有在需要用缓存来保存数据表时才会使用缓存,如公告,广告等,这些数据基本上不会经常变化,且会被频繁访问:)

  回复  引用    
2008-05-28 12:39 | 学习DZ缓存者[未注册用户]
谢谢 回复。

基本了解了。

谢谢

  回复  引用  查看    
2008-05-28 13:18 | 代震军      
@学习DZ缓存者
:)

  回复  引用    
2008-05-30 15:42 | 黑白[未注册用户]
cache.RemoveObject("/Forum/Scoreset/CreditsTax");
cache.RemoveObject("/Forum/Scoreset/CreditsTrans");
cache.RemoveObject("/Forum/Scoreset/TransferMinCredits");
cache.RemoveObject("/Forum/Scoreset/ExchangeMinCredits");
cache.RemoveObject("/Forum/Scoreset/MaxIncPerThread");
cache.RemoveObject("/Forum/Scoreset/MaxChargeSpan");

即然是以xml 存的,为什么不用移除"/Forum/Scoreset/" 下所有子节点啦

为什么不使用类似这样的:
cache.RemoveObject("/Forum/Scoreset/");

这不是可以移除Scoreset 下的所有子节点吗?
或 /Forum/ 下所有子节点,




  回复  引用  查看    
2008-05-30 17:20 | 代震军      
@黑白
原型设计是支持这样的功能的,这样就可以对缓存进行批量管理.但这样的操作对运行时的系统耗时可能会多一些,且波及的缓存数据量会很大,所以目前还未进行这方面的使用:)
同时感谢您对我们产品的支持和关注:)

  回复  引用    
2008-06-08 22:47 | 时代[未注册用户]
参照源码,但道理如下格式就该
Discuz.Cache.DNTCache cache = Discuz.Cache.DNTCache.GetCacheService();
cache.AddObject("/Forum/Test/AAAA", "TestString");


在Config中的cache.config写入一条啊? 为什么没有发现,但实际上Cache已经生效了的。 怎么没有写一条xpath呢?

希望知道的指点一下,谢谢。

  回复  引用  查看    
2008-06-10 09:47 | 代震军      
@时代
目前是在移除缓存时才会在XML用建立一条更新记录,以此来同步多WEB园(多进程)的情况.您可以再运行一下removeObect(), 然后再进行AddObject,这样缓存就会发生变化,最终会去更新XML文件:)

  回复  引用    
2008-06-12 15:46 | whykidding[未注册用户]
有点疑问,我看到缓存使用时是这样的
Discuz.Cache.DNTCache cache=Discuz.Cache.DNTCache.GetCacheservice();
Discuz.Cache.ICacheStrategy ics=new ForumCacheStrategy();
ics.TimeOut=cachetime;
cache.LoadCacheStrategy(ics);
cache.AddObject(cacheKey,dt);
cache.LoadDefaultCacheStrategy();
每次都这样用不是很烦吗?
如果不用策略模式,把DNTCache这个类写成抽象类,ICacheStrategy中的几个方法写成虚方法,然后做几个子类ForumDNTCache,DefaultDNTCache,RssDNTCache,用的时候就可以这样写
ForumDNTCache cache=new ForumDNTCache();
cache.AddObject(cacheKey,dt);
不是简单很多吗?(好像还需要增加一个类处理DNTCache中的几个静态变量)
这个场景到底需不需要用策略模式?

  回复  引用  查看    
2008-06-19 13:43 | Leon916      
谢谢博主,我刚好也想设计一个缓存,可以借鉴一下。
另外,“我当时正好有一个比较好的“原型”(从一本书上看到的源码)“能否告诉是那本书?

  回复  引用  查看    
2008-06-19 14:18 | 代震军      
@Leon916
《应用框架的设计与实现-.NET平台》, 是几年前的一本书,不知道目前市面是否还有:)

  回复  引用    
2008-06-19 16:43 | 初学模式[未注册用户]
楼主可否评一下87楼的
  回复  引用    
2008-06-19 17:26 | 初学模式[未注册用户]
87楼说的实际上是个模板方法模式,看起来挺好用
  回复  引用  查看    
2008-06-19 17:30 | 代震军      
@初学模式
其实模式设计是为了简化系统的复杂性,提供可扩展性,提升代码的可读性,你所说的简化的想法是要建立在需求的基础上,当最终的产品是不需要如此的复杂度,直接改造即可,但要预留足够的扩展空间,以备将来的需要.我们目前的产品就是建立在需求的基本上设计这样的架构的,因为空间,相册,主题等的缓存时间(缓存策略)都不相同,因此就为这样的设计提供了前提:)

  回复  引用    
2008-07-02 18:26 | raycn[未注册用户]
请问,当数据变化时的缓存依赖是什么? 如何判断当数据变化时更新缓存中的数据?
  回复  引用  查看    
2008-07-03 09:53 | 代震军      
@raycn
目前的数据变化时会将相应的缓存进行清除(用程序代码),同时也用关联文件的方式来同步多WEB园下的进程数据同步问题:)

  回复  引用  查看    
2008-07-21 23:56 | 傲然林      
Discuz!NT 还有好多地方看不太懂。
  回复  引用  查看    
2008-07-22 09:34 | 代震军      
@傲然林
慢慢来,同时感谢您对我们产品的支持和关注:)

  回复  引用    
2008-08-13 21:44 | 啊嘞嘞[未注册用户]
//声明新的缓存策略接口
Discuz.Cache.ICacheStrategy ics = new SitemapCacheStrategy();
ics.TimeOut = ttl * 60;
cache.LoadCacheStrategy(ics);
cache.AddObject("/Forum/Sitemap/Baidu", sitemapBuilder.ToString());//加入缓存
为什么这里还要加载一次默认缓存策略
cache.LoadDefaultCacheStrategy();

  回复  引用  查看    
2008-08-14 08:59 | 代震军      
@啊嘞嘞
应为之前系统的缓存策略被变成了SitemapCacheStrategy()。如果不
LoadDefaultCacheStrategy的话,当系统别的方法来使用缓存时会按SitemapCacheStrategy()策略中的方法来执行。

  回复  引用    
2008-08-14 10:30 | 啊嘞嘞[未注册用户]
非常感谢 支持Discuz
  回复  引用    
2008-08-26 16:56 | Munez[未注册用户]
无意中下载了Discuz .net 2.0版本,最近在认真研读源代码,发现学到了好多东西,包括设计模式...
  回复  引用  查看    
2008-08-27 08:59 | 代震军      
@Munez
呵呵,谢谢您的关注和支持:)

  回复  引用  查看    
2008-08-27 16:07 | voidarea      
拆箱装箱是不是太频繁?是否可以考虑泛型?
  回复  引用  查看    
2008-08-27 17:59 | 代震军      
@voidarea
主要是我们的产品同时要面向.net1.4322,而它不支持泛型,所以我们很慎重

  回复  引用    
2008-08-28 00:30 | Billzhang[未注册用户]
--引用--------------------------------------------------
代震军: @raycn
<br>目前的数据变化时会将相应的缓存进行清除(用程序代码),同时也用关联文件的方式来同步多WEB园下的进程数据同步问题:)
--------------------------------------------------------
我用discuznt2.0 由于是WEB园。程序会在读取缓存xml文件到dataset中的时候出现当前文件被别的process锁住。虽然nt的代码里有在读的时候会先lock,但在WEB园 时候此lock只在当前进程有效。请问你们设计的时候有考虑这个问题吗?

  回复  引用  查看    
2008-08-28 09:17 | 代震军      
@Billzhang
这个问题可以检查一下看看IIS中的设置是否允许读和写,另外一个就是开启的WEB园如果比较多的情况下可能会出现您所说的这个问题。不过解决方案我眼下想到一个,就是当前进程中获取读写权限时,生成一个LOCK文件,进行标识,等到读写结束时,删除这个文件。这样做的好处就是当另一个进程过来访问该缓存文件时,可以先看一下是否存在LOCK文件,如存在的话,就暂时先不访问,直到LOCK文件被删除之后再来访问缓存文件。
不知道这样解释您是否清楚:)

  回复  引用    
2008-09-03 15:07 | jsst[未注册用户]
看了代码,DiscuzNT的缓存最终还是通过
webCache.Insert(objId, o, null, DateTime.MaxValue, TimeSpan.Zero, System.Web.Caching.CacheItemPriority.High, callBack);存到了.NET自带的cache中,不明白为什么要绕个圈,开这么多个定时器去检查文件日期,为何不用自带的文件依赖。
“而.NET本身所提供的缓存机制又显得过于“单薄”,比如说订
制不太灵活方便, 缓存对象之间层次感不强, 使用时缺乏统一的管理等等。”这里的“订制不太灵活方便”怎么说?

  回复  引用  查看    
2008-12-10 14:36 | sagacite      
看了源码里 private static System.Timers.Timer cacheConfigTimer = new System.Timers.Timer(15000);
这只是每隔15秒检查一下缓存是否要清理,在即时性要求很高的情况下,还是会出现脏读写。。。为什么不多加一个缓存策略,使用velocity或者memcached之类的,问题就解决了。

  回复  引用  查看    
2008-12-10 17:40 | 代震军      
@sagacite
我们的产品是不建议使用第三方的组件产品的。另外velocity是在.net4中被支持,目前很少有人用。并且其在大规模布署上的可靠性和效率还没有一些成功用例。所以大家还是观望为上策。

  回复  引用  查看    
2008-12-18 14:01 | John Zhuang      
挺不错的
支持

  回复  引用  查看    
2009-01-06 07:18 | 小洋      
非常好,您可以给我相关的开发文档吗?我向学习学习,谢谢啊
  回复  引用  查看    
2009-01-06 09:27 | 代震军      
@小洋
在nt.discuz.net的官方有相关的开发文档。呵呵

  回复  引用    
2009-02-03 16:19 | elevenbus[未注册用户]
一个系统中如果缓存键特别多,例如上万个,这样是否在查找缓存时,相对会花掉较多时间?
  回复  引用  查看    
2009-02-03 16:27 | 代震军      
@elevenbus
如是缓存的数据越来越多(而一台机器的内存有限),那.net本身提供的缓存机制就可能越来越不堪重负了,这时就该是memcached,velocity这类分布式缓存的用武之地了。

  回复  引用    
2009-02-04 14:49 | elevenbus[未注册用户]
如果采用集群,那么相同的缓存都会存在于集群内的每台服务器?
如果都存在的话,某一个缓存失效后,如何通知其他服务器的缓存也失效呢?
谢谢

  回复  引用  查看    
2009-02-04 14:55 | 代震军      
@elevenbus
memcache的分布缓存只把数据记录在其中的一台服务器上,而是不是每台服务器都存一份。而velocity还不太清楚是怎么个存储方法

  回复  引用    
2009-02-06 15:03 | elevenbus[未注册用户]
假如一个程序中会不断的生成xml文件,而这些文件又会经常使用
如果集群中的每个服务器都只在自己的硬盘上生成,不在磁盘阵列上存储,就会有问题,即其中的一个服务器生成了xml文件,而其他的服务器上没有
如何处理这种情况呢?
程序需要做怎样的处理,可以把xml文件生成成共享存储阵列上?
谢谢

  回复  引用  查看    
2009-02-06 16:13 | 代震军      
@elevenbus
那么那个程序就要将生成的XML文件对象缓存一份到memcached上,这样下次访问时就该memcached中的缓存对象就行了。
各自的xml文件不必共享,因为memcached可以看成是唯一的(共享)的数据源了。

  回复  引用    
2009-03-06 16:53 | huyh[未注册用户]
是否有考虑用轻量级的数据库(比如db4o,sqlite之类)来进行缓存设计呢
  回复  引用    
2009-03-17 10:40 | koenemy[未注册用户]
我要把自己开发的老论坛,数据导到discuz里,,导uchome_feed时,不知道这两个字段数据是怎么组合的。。请告诉我一下。

title_data 这个串是怎么组合出来的
a:2:{s:7:"subject";s:73:"<a href="http://www1.uan.com/bbs/viewthread.php?tid=3989560">fuca</a>";s:7:"message";s:15:"sdfasdfasdfasdf";}

这个hash_data的数据是怎么来的
df73c450c08f423b921d59157d9fdc55

  回复  引用  查看    
2009-03-17 10:46 | 代震军      
@koenemy
您所说的这个问题应该是php版的吧,这块我不是很清楚,你可以去官方发帖询问一下,www.discuz.net

  回复  引用    
2009-03-17 19:43 | like discuz[未注册用户]
我第一次来贵地
楼主好细心呀
几乎每个都有回复。

继续关注楼主。 目前正在 研究 discuz!nt!
很多地方不懂 还请楼主多多指教!

  回复  引用  查看    
2009-03-18 13:27 | 代震军      
@like discuz
:)

  回复  引用  查看    
2009-04-11 12:11 | 黑白之间      
最近用到discuz缓存出现cup频率过高的问题,不知老代能否指点一二
  回复  引用  查看    
2009-05-24 13:11 | skyaspnet      
感谢楼主,您可以抽时间讲解一下TestCase开发的简单步骤吗?估计不少人都不太懂测试方面的知识,谢谢!
  回复  引用  查看    
2009-05-25 08:57 | 代震军      
@skyaspnet
呵呵,如果大家感兴趣,可以抽时间交流一下

  回复  引用  查看    
2009-05-25 10:31 | skyaspnet      
您现在还在这个项目组吗?
请问像这种项目得多少牛人才能开发呢?
谢谢!

  回复  引用  查看    
2009-05-25 10:48 | 代震军      
@skyaspnet
目前还在,呵呵。

  回复  引用  查看    
2009-05-25 11:28 | skyaspnet      
请问像这种项目得多少牛人才能开发呢?
谢谢!

  回复  引用  查看    
2009-05-25 11:58 | 代震军      
@skyaspnet
目前是三个开发者

  回复  引用  查看    
2009-05-25 17:08 | skyaspnet      
@代震军
非常佩服,看着源码的工程不小,只有三个人开发,向您多学习,一定要好好学学DISCUZ!NT的源码,希望您能多指点,谢谢!

  回复  引用  查看    
2009-05-25 17:11 | 代震军      
@skyaspnet
过奖了

  回复  引用    
2009-06-27 11:04 | bigfanofcpp[未注册用户]
我们单位现在也在做缓存,准备使用memcached

请问 代震军 一个设计上的问题:

我希望所有数据全部缓存起来,这样就算数据库服务器down了也没有关系;但是同事认为不需要,应该是实时性要求高的数据不能缓存,我却认为,实时性要求高的数据,可以通过缓存与数据库定义高优先级(同步时间短,比如5秒)同步方案来解决,请问你觉得呢??

评论共2页: 上一页 1 2 



发表评论

昵称: [登录] [注册]

主页:

邮箱:(仅博主可见)

评论内容:

  登录  注册

[使用Ctrl+Enter键快速提交评论]

0 855163




相关文章:

相关链接: