Discuz!NT 缓存设计简析 [原创]

作为一个社区类型软件,大并发支持和高效稳定运行永远是“硬道理”,而有效安全的使用
缓存恰恰能起到事倍功半的效果。而.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架构的文章要等到正式版发布之后了。到时大家下
载代码之后再对照新代码给大家聊聊这个产品的其它设计思路(按我的理解)。

转载于:https://www.cnblogs.com/aaa6818162/archive/2009/11/19/1605933.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/264137.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

VSCode从下载到配置Ubuntu系统

Visual Studio Code从下载到配置Ubuntu系统 一、下载和安装Visual Studio Code 1、进入Visual Studio Code官网&#xff08;Visual Studio Code - Code Editing. Redefined&#xff09;&#xff0c;点击箭头所指地方下载红框内的安装包&#xff1b; 2、因为是国外网址下载&am…

python 写入excel 日期_Python实例:excel文档写入操作

来自PythonABC.org老师的课程很好&#xff0c;但是每个视频都蛮长的&#xff0c;听着听着就有些晕乎&#xff0c;所以根据视频自己整理了一下&#xff0c;以便记录学习使用Python实现excel的文档写操作import openpyxl from openpyxl.utils import get_column_letterwb openpy…

eclipse集成maven插件

一、准备工作 1. 安装jdk并配置&#xff1a;https://www.cnblogs.com/diandiangui/p/10002100.html  2. 已安装好 maven并配置&#xff1a;https://www.cnblogs.com/diandiangui/p/10768339.html  3. 安装eclipse并配置&#xff1a;https://www.cnblogs.com/diandiangui/p/…

批量改名_手把手教你用Python批量给图片添加水印 | 知了干货分享

我们在网上浏览一些文章的时候&#xff0c;经常会发现文章中会有一些图片&#xff0c;上面会有一些标识&#xff0c;而这些标识就是我们经常说的水印了。很多时候&#xff0c;我们需要给图片加上一些修饰&#xff0c;好让别人能直观的认识到这个图片的出处以及来源&#xff0c;…

linux环境下安装nginx步骤(不错)

开始前&#xff0c;请确认gcc g开发类库是否装好&#xff0c;默认已经安装。 ububtu平台编译环境可以使用以下指令 apt-get install build-essential apt-get install libtool centos平台编译环境使用如下指令 安装make&#xff1a; yum -y install gcc automake autoconf libt…

OpenGL.Tutorial15_Lightmaps

ZC&#xff1a;撤销 & 重做 — Blender Manual.html&#xff08;https://docs.blender.org/manual/zh-hans/dev/interface/undo_redo.html&#xff09; ZC&#xff1a;Blender下载地址&#xff1a;Index of _release_Blender2.50alpha_.html&#xff08;https://download.b…

后处理没有pui文件怎么打开_UG NX10.0四轴后处理下载(带PUI文件)

&#xfeff; 提示&#xff1a;点击上方"NX网"↑ 免费订阅 关注老叶NC程序演示&#xff1a;%G40 G17 G94 G80 G90M05M09G91 G28 Z0.0M01T00 M06(Tool NaneD4R0.5 XY 0.10mm Z0.00mm)(D4.00 R0.50 FL50.00 L75.00)G00 G90 G55 X68.263 Y.969 A187.77 S4500 M03G43 Z79…

catia v5法矢数据软件_catia介绍

CATIA是英文 Computer Aided Tri-Dimensional Interface Application 的缩写。 是世界上一种主流的CAD/CAE/CAM 一体化软件。在70年代Dassault Aviation 成为了第一个用户&#xff0c;CATIA 也应运而生。从1982年到1988年&#xff0c;CATIA 相继发布了1版本、2版本、3版本&…

(十七)WebGIS中距离及面积测量的原理和实现以及坐标转换的简单介绍

文章版权由作者李晓晖和博客园共有&#xff0c;若转载请于明显处标明出处&#xff1a;http://www.cnblogs.com/naaoveGIS/。 1.背景 在这一章里我们将讨论基础工具栏中另外两个常用工具&#xff1a;距离测量工具盒面积测量工具。 距离测量工具要求实现如下功能&#xff1a; a.通…

access查询出生日期格式转换_从身份证中提取出生日期的3个方法和计算年龄和星座的方法...

在我们日常的工作当中&#xff0c;经常会遇到通过身份证来获取出生年月日的需求&#xff0c;今天就给大家介绍三种可以从身份证中提取出生年月日的方法。我们都知道身份证不同的区域是有不同的含义的&#xff0c;代表出生年月日的数字是第7位到第14位&#xff0c;也就说我们把这…

AutoRun Pro Enterprise II 教程--- 教你制作软件工具箱教程清晰版(教程画面清晰,教程语音可以调高)...

AutoRun Pro Enterprise II是一款功能极为强大的自动运行菜单制作工具。可在一个所见即所得的环境中创建并编辑一个专业的CD、DVD自动运行界面并生成自动运行文件。在自动运行界面中支持打开或执行文件&#xff0c;打印文档&#xff0c;发送邮件&#xff0c;访问站点&#xff0…

access如何设置定期报表汇总_报表工具选型对比系列 - 大报表

有些报表查询出的数据行数可达千万甚至上亿&#xff0c;这类报表通常被叫做大报表&#xff0c;大多数情况下都是些清单明细数据报表&#xff0c;也有少量分组报表。针对大报表&#xff0c;如果像常规报表一样&#xff0c;将数据一次性全取再交给前端呈现是不可行的。一是等待时…

PLSQL Developer远程连接oracle数据库

前提是本机已经安装oracle&#xff0c;利用 net configuration assistant 。若本机没有安装oracle&#xff0c;无需装oracle的方法请点我打开net configuration assistant&#xff0c;选择“本地网络服务名配置&#xff0c;如下图所示然后选择添加服务名选择远程数据库实例名主…

spad 探测器_大面阵SPAD阵列集成微透镜阵列,填充因子改善明显

基于CMOS制造工艺可实现大面阵、单片式单光子雪崩光电二极管(SPAD)阵列&#xff0c;并受到如3D成像、荧光寿命成像等各种应用的青睐&#xff0c;但常常受到低填充因子(fill factor&#xff0c;指感光区域面积与像元面积的比值)的困扰。SPAD填充因子的数值通常小于5%&#xff0c…

Azure Backup 简介

ViswanathTata云Enterprise 项目经理&#xfeff;&#xfeff;AzureBackup是 Azure恢复服务的一部分&#xff0c;在安全高效的环境中对上传到 Azure的数据提供简单可靠的管理和保护。Azure Backup以经济高效的方式提供无限的存储空间和长期的数据保留&#xff0c;这些功能使其成…

笔记:Java虚拟机运行时数据区

Java虚拟机在执行Java程序的过程中会把它管的内存划分为以下若干个不同的区域&#xff1a; 1、程序计数器 程序计数器是一块较小的内存空间&#xff0c;它可以看作是当前线程所执行的字节码的行号指示器&#xff1b;由于Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时…

高电压技术思维导图_钢铁技术:钢铁行业板坯连铸结晶器振动常见故障思维导图...

钢铁虾&#xff1a;(您距离钢铁虾只差关注)结晶器是连铸机的心脏&#xff0c;其振动偏差直接危及连铸过程的生产安全&#xff0c;其振动精度也往往与铸坯表面裂纹等缺陷息息相关。国内外常见板坯连铸结晶器振动形式有&#xff1a;①机械式振动&#xff1b;②双液压缸式振动&…

可以编辑vga格式文件的软件-PowerCreator Media Studio

vga格式的课件越来越多。不小心没有录制好的可见&#xff0c;想修改一下找了很久居然没有找到一个合适的软件。还有朋友给了一款&#xff1a;PowerCreator Media Studio用起来很方面&#xff0c;和premiere的操作很像。大赞。 安装后自带可以播放vga格式的播放器。 转载于:htt…

获取当前文件所在路径

代码 1 unitUnit1;2 interface3 uses4 Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,5 Dialogs, StdCtrls;6 type7 TForm1 class(TForm)8 Button1: TButton;9 Button2: TButton;10 procedureButton1Click(Sender: TObject);11 procedureButton2…

删除隐藏版本信息 版本回退_git之版本穿梭术

上一篇主要针对使用git add和git commit两个指令提交文件到本地版本库做了详细的介绍&#xff0c;其实提交文件到版本库无非就是两个步骤&#xff0c;先将文件添加到暂存区&#xff0c;所有要提交的文件全部添加完毕&#xff0c;统一提交到版本库。那我们每一次提交都会生成一个…