最近拜讀了代振軍同學寫的關於.Net開發下的Discuz!NT的緩存設計的一篇文章Discuz!NT 緩存設計簡析 [原創],頗有些想法,姑且寫在這裡讓大家拍磚吧。;)
緩存真是個好東西,在大型的系統中可以有效地提升系統的速度,此乃廢話就不多說了,在.Net 平台下面我把緩存從功用大致分為兩類,數據對象緩存和頁面輸出緩存. 對於數據緩存來講是由System.Web.Caching.Cache這個類來實現,可以從上下文對象Context.Cache 來獲取這個對象的引用.而頁面/控件輸出緩存則是由.Net環境在運行時依據頭部的緩存申明來控制緩存策略. 本文主要論證與數據緩存相關的一些應用與問題.
文中代同學提到了"無法跨Web園共享數據的問題",雖然提到解決方案就是使用XML文件來存放緩存的鍵值,這裡有一個疑惑,就是.Net的Web園既然是進程獨立的又何來共享之說呢,真要是這樣的話即便是通過XML文檔寫入緩存鍵值緩存的對象也不能同時在兩個進程中共享,而這裡獲得的好處僅是避免了在其它的進程中讀到了已在當前進程中失效的“髒”緩存數據,這樣的話開幾個Web園就會產生幾個緩存的對象對系統資源的利用系就比較低了. 如果是用Web場布署的話浪費就更多了,也許是還少有論壇達到這樣的規模故不在設計能力的范圍中吧.CommunityServer也是使用了這個系統對象,並對它作了一些包裝形成了CommunityServer.Components.CSCache這個類,還是不錯的,可以在項目中選擇使用.
基於這個類的應注實現還有EnterpriseLibrary的CacheBlock裡面的NullBackingStore方式,但是為了滿足多進程/服務器共程緩存數據的需求EntLib還提供了將SQL SERVER作為後端存儲設備的方案,這樣在性能要求不是太嚴,客戶端連接不是太多的情況下也可以使用這種方式.只需要將EntLib 配置為共享數據庫分區的工作方式即可,所有的CacheManager實例都有對緩存塊的讀寫權,當然你也可配置為只允許一個實例寫,其它的來讀.
那麼還有沒有更好的辦法呢,其實是有的. 不過我很奇怪在.Net平台下居然沒有“原生態”的分布式緩存解決方案,也許是俺孤陋寡聞吧,有哪位達人知道的請分享。還好我們有Memcached這東西,它在PHP平台上已經取得了巨大的成功,是優秀的分布式緩存解決方案,可以參看這篇文章 , 大型的站點上應該必不可少吧.有舉的同學可以去看看, 另外還想好一個思路, 就是在EntLib的基礎上作擴展實現IBackingStore 接口從BaseBackingStore派生一個實現出來,再經由Remoting或者ICE這樣的分布式中間件技術應該也可以實現的類似的功能吧.
用XML作為緩存鍵的存儲方式倒是一個不錯的想法,這樣在批量移除緩存項的時候就不需要作掃描而直接得到相應的緩存鍵值,跟分布式緩存作一個整合應當是一個不錯的方案。
好了,讓我們再回頭看看Discuz!NT在頁面緩存上有些什麼高招.
總的說來我是不怎麼喜歡.Net2.0提供的頁面輸出緩存功能,主要是不能手動地控制頁面緩存的過期,而使有緩存依賴項似乎也有點不爽。事實上使用數據綁定控件相對來說是比較消耗資源的,同樣的數據我用StringBuilder直接拼出來輸出速度要快不少,測試代碼比較簡單我這裡就不給了,大家可以自己去測去,Discuz!NT在設計中也大量地采用了這樣的方法(怪不得速度這麼快呢;))。一般來講模版被保存後後台會在aspx目錄生成對應的頁面文件, 比如你有一個頁面,上面需要顯示一個來訪者的姓名,它的偽代碼看起來可能是這個樣子
模版文件內容show.html:
以下為引用的內容:
<html>
<body>
Hello, Your name is <% yourname %>
</body>
</html>
生成的文件 show.aspx
templateBuilder.AppendLine("<html>");
templateBuilder.AppendLine("<body>");
templateBuilder.AppendLine("Hello, Your name is " + this.yourname);
templateBuilder.AppendLine("</body>");
templateBuilder.AppendLine("</html>");
生成的文件 show.aspx
以下為引用的內容:
templateBuilder.AppendLine("");
templateBuilder.AppendLine("");
templateBuilder.AppendLine("Hello, Your name is " + this.yourname);
templateBuilder.AppendLine("");
templateBuilder.AppendLine("");
這裡的this.yourname對應著相應頁面後台類裡面的一個屬性,由程序在運行時進行初始化賦值,這樣最後得到的頁面執行結果就可以從這個templateBuilder對象的ToString()方法得到, templateBuilder也就是一個頁面後台類裡面的StringBuilder類的實例,最後在頁面執行完畢後的OnLoad事件中根據不同的頁面類型,如首頁,頻道首頁,內容頁等, 使用不同的緩存策略將頁面執行結果的HTML代碼插入到緩存中,下一個請求進來的時候在進入頁面生命周期之前的HttpModule(這裡面還包含地址重寫功能代碼)中判斷這個緩存是否有效,直接從內存讀取緩存發回客戶端.這樣速度當然就快了, 頁面上看到的執行時間自然是0ms. 不過對於登陸用戶來說由於要顯示不同的登陸信息所以不能使用匿名的緩存文件版本,所以說一旦你登陸頁面才會真正執行一次,但是上面要顯示的數據都有獨立的緩存項,所以僅僅是重新組裝一次頁面代碼罷了,速度還是比較快的,在官方論壇上看到首頁加載時間是15ms, 夠快的了. 愚以為連這個時間其實也是可以再節省節省的. 比如用戶登陸信息這部分東西可以生成一段JS, 在向浏覽器發出了匿名用戶的緩存版本時再判斷如果用戶登陸了就追加這樣一段JS代碼,在裡面去把相應的HTML替換掉就可以了,也可以使用AJAX技術在客戶端去取,這樣就解決了已登陸用戶和未登陸用戶在共享緩存版本上的問題,至少在首頁這一級是可以的吧,其它的主要頁面不好說應該也差不多,俺對論壇程序的流程不是很了解.
從另一個角度講已登陸用戶不應該速度比匿名用戶還慢吧~~~;)
HOHO,終於完成了,先寫這些吧。聊點別的`~~總體上看俺覺得實用的就是最好的,不要說DNT的設計有多爛多爛,層次結構多不合理,代碼多不OO,但是在這樣一個特定的應用環境下它提供了一種相對靈活和高速的解決的方案,我覺得就是一個正確的選擇。量體裁衣做出來的東西雖然可維護可重用性較差還是有它的好處的,俺自己也比較傾向於這種針對性很強的解決方案思路,希望DNT能做得更好~~~正如BlogEngine這樣的程序,它幾乎將很多的數據都Load到內存裡面,數據存儲默認的也是XML的存儲方式,你很難去講它是一個糟糕的程序,因為它有特定的應用場景,合適的才是最好的~~~~
本文基於DNT 2.0 RC1, 不當之處請大家斧正, 謝謝.