posts - 283,  comments - 6275,  trackbacks - 107

        作为一个社区类型软件,大并发支持和高效稳定运行永远是“硬道理”,而有效安全的使用
缓存恰恰能起到事倍功半的效果。而.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 代震军 阅读(19683) 评论(159) 编辑 收藏

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

小弟水平有限,见谅!

 回复 引用   
#62楼[楼主]
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[未注册用户]
感谢代震军,没想到你这么快回了,
我看了看《应用框架的设计与实现》
基本明白了,还是慢慢体会吧,

 回复 引用   
#65楼[楼主]
2008-04-14 13:15 | 代震军      
@小张2
欢迎交流

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

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

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

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



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

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





急需确切答案...

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

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


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


望代兄继续解答





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

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

还有个问题想请教,

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

是不是非磁盘文件缓存

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

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

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

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


请指点,谢谢

 回复 引用   
#77楼[楼主]
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;
}
}

 回复 引用 查看   
#78楼[楼主]
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)
{
。。。
}

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

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

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

基本了解了。

谢谢

 回复 引用   
#82楼[楼主]
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/ 下所有子节点,




 回复 引用   
#84楼[楼主]
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呢?

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

 回复 引用   
#86楼[楼主]
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      
谢谢博主,我刚好也想设计一个缓存,可以借鉴一下。
另外,“我当时正好有一个比较好的“原型”(从一本书上看到的源码)“能否告诉是那本书?

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

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

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

 回复 引用 查看   
2008-07-21 23:56 | 傲然林      
Discuz!NT 还有好多地方看不太懂。
 回复 引用 查看   
#96楼[楼主]
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();

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

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

 回复 引用 查看   
2008-08-27 16:07 | voidarea      
拆箱装箱是不是太频繁?是否可以考虑泛型?
 回复 引用 查看   
#103楼[楼主]
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只在当前进程有效。请问你们设计的时候有考虑这个问题吗?

 回复 引用   
#105楼[楼主]
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之类的,问题就解决了。

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

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

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

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

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

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

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

 回复 引用   
#117楼[楼主]
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

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

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

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

 回复 引用   
#122楼[楼主]
2009-03-18 13:27 | 代震军      
@like discuz
:)

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

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

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

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

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

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

 回复 引用 查看   
#131楼[楼主]
2009-05-25 17:11 | 代震军      
@skyaspnet
过奖了

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

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

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

 回复 引用   
2009-08-14 13:22 | liyutao      
volatile 修饰应该可以跨WEB园吧
 回复 引用 查看   
2009-08-14 16:25 | liyutao      
DISCUZ!NT的多数据库访问层也应该是策略模式吧
DatabaseProvider是应用场景吧

 回复 引用 查看   
#135楼[楼主]
2009-08-14 17:47 | 代震军      
@liyutao
多数据库访问用的是抽象工厂模式,我在这个系列中做过介绍:)

 回复 引用 查看   
2009-08-15 10:00 | liyutao      
刚看了下您的那篇文章,抽象工厂模式怎么和策略模式很像呢,都分不清楚,原来还有DBHELPER1.0,我只看到DBHELPER2.0,所以就当成了策略模式。谢谢代老师。
volatile 修饰应该可以跨WEB园吧?这个还没回答我呢。您的IT餐馆能不能每天写一集,很喜欢您的文章。

 回复 引用 查看   
#137楼[楼主]
2009-08-17 09:16 | 代震军      
@liyutao
可能你还没有搞清web园本身就是进程,而进程与线程是完全不同的概念。“声明为 volatile 的字段不受编译器优化(假定由单个线程访问)的限制。这样可以确保该字段在任何时间呈现的都是最新的值。 其通常用于由多个线程访问但不使用 lock 语句对访问进行序列化的字段。”这主要是说volatile 是用于线程数据更新,而不是进程间数据同步。

 回复 引用 查看   
2009-11-06 10:23 | 明轩      

引用需要说明的是在4月份时缓存机制出现了一些问题,比如缓存数据丢失以及在.net2下的死循环的问题



能否告诉我缓存数据丢在什么一个场景下,而.net2下的死循环又是在什么一个情况下。我是在是想不出来。

 回复 引用 查看   
2009-11-17 09:46 | xiaoxiaofish[未注册用户]
想在数据表(用户注册表)中新添几个字段,却一直提示为空.数据写不进去.请问怎么处理.谢谢!
异常详细信息: System.NullReferenceException: 未将对象引用设置到对象的实例。

 回复 引用   
2009-12-02 13:18 | Darak      
感谢LZ,最近一直在研究
 回复 引用 查看   
2009-12-09 10:23 | funny zak      
研究了2天,终于把discuz的缓存研究通了,现在看起来觉的它的设计真的很棒!谢谢你们!
 回复 引用 查看   
2009-12-09 10:45 | funny zak      
2.另外需要说明的是在4月份时缓存机制出现了一些问题,比如缓存数据丢失以及在.net2下
的死循环的问题,后来在雪人的建议下采用每个缓存都有缓存标志来解决数据丢失的问题。也就
是如下的代码段:

-----------------
最近发布的版本,看到把这个功能全去了,难道不怕出现类似问题吗?

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

-----------------
最近发布的版本,看到把这个功能全去了,难道不怕出现类似问题吗?

我也想问一下,现在最新的版本里把这些都注释了,怎么回事?/

 回复 引用 查看   
#144楼[楼主]
2010-03-16 12:01 | 代震军      
@Astar
目前已不存在死循环问题了,所以这个代码被注释了,注释代码会在下个开源版本中去掉。

 回复 引用 查看   
2010-04-09 11:43 | 叶子绿了      
AggregationFacade.ForumAggregation.GetForumTopicList()


这个方法,找不到在哪里设置了缓存.代老师能否讲下.

 回复 引用 查看   
2010-04-09 11:45 | 叶子绿了      
public DataTable GetForumTopicList()
{
if (__topicList != null)
{
return __topicList;
}

//返回的记录数
int topnumber = 10;
XmlNode xmlnode = __xmlDoc.DocumentElement.SelectNodes("/Aggregationinfo/Aggregationpage/Website/Forum/Bbs").Item(0);

if (__xmlDoc.GetSingleNodeValue(xmlnode, "Topnumber") == null)
{
topnumber = 10;
}
else
{
try
{
topnumber = Convert.ToInt32(__xmlDoc.GetSingleNodeValue(xmlnode, "Topnumber").ToLower());
}
catch
{
topnumber = 10;
}
}

if (__xmlDoc.GetSingleNodeValue(xmlnode, "Showtype") != null)
{
__topicList = DatabaseProvider.GetInstance().GetWebSiteAggForumTopicList(__xmlDoc.GetSingleNodeValue(xmlnode, "Showtype").ToLower(), topnumber);
}
else
{
__topicList = DatabaseProvider.GetInstance().GetWebSiteAggForumTopicList("3", topnumber);
}
return __topicList;
}
这里面好像看不出有设置缓存,那是在哪设的呢

 回复 引用 查看   
2010-06-22 23:13 | sps.shareach.com      
觉得lock(lockHelper)在进程内有效,多个web园情况下其实不起作用的。
一个w3cp 没法lock住另一个进程内的资源。
不知道是否我理解错误

 回复 引用 查看   
2010-08-10 21:52 | mrxliu      
好文章,留名!
 回复 引用 查看   
2010-11-02 15:41 | LLorJJ999      
0:058> !clrstack
OS Thread Id: 0x102c (58)
ESP EIP
1f49effc 7c9585ec [GCFrame: 1f49effc]
1f49f134 7c9585ec [HelperMethodFrame: 1f49f134] System.Threading.Monitor.Enter(System.Object)
1f49f188 1d23cf11 Discuz.Cache.DNTCache.RemoveObject(System.String, Boolean)
1f49f1bc 1baaa6f1 Discuz.Cache.CachesFileMonitor.CheckAndRemoveCache(Discuz.Cache.DNTCache)
1f49f208 1d95924c Discuz.Cache.DNTCache.Timer_Elapsed(System.Object, System.Timers.ElapsedEventArgs)
1f49f20c 7a64df0d System.Timers.Timer.MyTimerCallback(System.Object)
1f49f244 793d9c1a System.Threading._TimerCallback.TimerCallback_Context(System.Object)
1f49f248 793685af System.Threading.ExecutionContext.runTryCode(System.Object)
1f49f670 79e88f63 [HelperMethodFrame_PROTECTOBJ: 1f49f670] System.Runtime.CompilerServices.RuntimeHelpers.ExecuteCodeWithGuaranteedCleanup(TryCode, CleanupCode, System.Object)
1f49f6d8 793684fb System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object)
1f49f6f0 793683ee System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object)
1f49f708 793d9c7f System.Threading._TimerCallback.PerformTimerCallback(System.Object)

以上是我用windug抓取到的死锁的问题,没找到好的方法解决!

 回复 引用 查看   
2010-11-30 16:59 | 五味果      
缓存恰恰能起到“事倍功半”的效果。
 回复 引用 查看   
2011-05-26 13:29 | know@more      
@代震军
lz实现的缓存是存储在xml文件而不是cache在内存中?! 如果是,其主要好处是:访问文件比访问数据库查询速度要快? ——希望lz能给予解答,期待中

 回复 引用 查看   
#152楼[楼主]
2011-05-27 09:44 | 代震军      
@know@more
只是用XML进行缓存键的管理,不过这个方案目前已不在使用XML文件了,详见最新开源的代码

 回复 引用 查看   
2011-05-28 18:18 | know@more      
哦,想知道Dz.net目前理论上可支持同时在线的数量
 回复 引用 查看   
2011-06-12 22:30 | 野人花园      
楼主一般都是上班时间回贴啊!小心戴老板罚你薪水哦!
顺便问一下,你们团队用什么做源代码控制呢,VSS?SVN?
好奇ing

 回复 引用 查看   
2011-06-24 10:30 | HubertYe      
这篇文章几乎每一个评论都有回复,代老师的这种敬业和态度值得我们每个人学习!

在项目中刚好要使用缓存,也正在学习设计模式,看了代老师的缓存设计方案(单体模式+策略模式)很有感觉,谢谢代老师的分享!

代老师,我要超越你!哈哈(其它童鞋要喷我了...)
继续把DNT源码看完

 回复 引用 查看   
2011-06-24 13:42 | HubertYe      
DNTCache中移除缓存的方法

/// <summary>
/// 检查和移除缓存
/// </summary>
/// <param name="instance"></param>
/// <returns></returns>
public static DNTCache CheckAndRemoveCache(DNTCache instance)//
...
if (removedatetime > cachefilenewchange.AddSeconds(-2))
{
string xpath = dr["xpath"].ToString().Trim();
instance.RemoveObject(xpath, false);
}


请问判断依据-2是什么意思?

CheckAndRemoveCache这个函数的作用和意图是什么?

 回复 引用 查看   
#157楼[楼主]
2011-06-27 14:11 | 代震军      
@HubertYe
目前这个功能已在最近的开放出来的源码包中修改,请及时更新

 回复 引用 查看   
2011-11-15 23:45 | Jasen.xu      
您好,我有个问题想请教一下,如果我的应用层是在几台服务器,IIS也启用了多个园,采用cache + memcached(认为永恒)方式,但在更新缓存时有一个问题就是:比如一个DataTable数据,我本想每3秒才去数据库中取一次,虽然在cache的回调函数中来更新数据,但如果其中的几个web园同时并发,刚好在cache失效临界点,即同时去数据库里的读取,会出现同一时间读取数据库好几次,这样并不是我的初衷每3秒才读一次数据库.不知道这个问题有没有好的解决办法.谢谢
 回复 引用 查看   
2012-01-09 11:55 | 河蟹社会      
@Jasen.xu
double check 缓存内容

 回复 引用 查看   
评论共2页: 上一页 1 2 
昵称:代震军
园龄:5年11个月
荣誉:推荐博客
粉丝:492
关注:3

<2007年8月>
2930311234
567891011
12131415161718
19202122232425
2627282930311
2345678

搜索

 
 

常用链接

随笔分类(366)

随笔档案(283)

文章分类(8)

文章档案(31)

相册

JavaScript

LINQ

silverlight

UML,OO

WebBlogger

负载开源项目

  • Discuz!NT
  • LLServer
  • TokyoTyrantClient
  • WebCam

个人简历

漫画

其它

企业级架构

网站案例研究

积分与排名

  • 积分 - 1217876
  • 排名 - 26

最新评论

阅读排行榜

评论排行榜

推荐排行榜