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樹來管理已加入到系統的緩存對象了。
???
??????? ?使用"單件模式"模式生成全局唯一的“應用場景”,因為緩存這種東西通常在存儲共享
數據時它的效果最好,編碼也最容易實現和管理,同時項目本身基本上就是對經常訪問但不
經常改變的數據庫數據(可看成是共享數據)進行緩存,所以使用單件模式就順理成章了。
????? ? 請看如下代碼:
{
???
?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下
的死循環的問題,后來在雪人的建議下采用每個緩存都有緩存標志來解決數據丟失的問題。也就
是如下的代碼段:
?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
來存取一組對象了。
??????? 它的實現代碼也相對簡單,這里就不多說了,只把代碼貼在此處。
{???
?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)
?????????它的實現代碼也相對簡單,這里就不多說了, 只把代碼貼在此處。
?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
總結
以上是生活随笔為你收集整理的Discuz!NT 缓存设计简析 [原创]的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 发财机会到!二手特斯拉Model 3卖出
- 下一篇: Windows软件调试学习笔记(1)