轉(zhuǎn)帖|其它|編輯:郝浩|2010-11-18 14:38:46.000|閱讀 519 次
概述:為了實(shí)現(xiàn)對客戶端狀態(tài)的有效管理,并提高應(yīng)用開發(fā)效率,在很多年前我們的開發(fā)框架體系中就具有相應(yīng)的一個(gè)叫做State的編程框架。最近我開始對其進(jìn)行升級和重新設(shè)計(jì),將實(shí)現(xiàn)原理和概要設(shè)計(jì)方面的東西寫出來與大家共享,希望對各位有些啟發(fā)。同時(shí)希望借此得到你們一些好的建議和意見,以便能夠充實(shí)我們的框架。
# 界面/圖表報(bào)表/文檔/IDE等千款熱門軟控件火熱銷售中 >>
在一個(gè)基于ASP.NET的Web應(yīng)用程序中,我們通常使用SessionState保存基于某個(gè)客戶端的狀態(tài)信息。但是這種單純使用SessionState的編程方式具有很多局限,比如Session Item的Key值沖突,比如沒有一個(gè)有效的SessionState清除機(jī)制會為Web Server帶來內(nèi)存壓力。為了實(shí)現(xiàn)對客戶端狀態(tài)的有效管理,并提高應(yīng)用開發(fā)效率,在很多年前我們的開發(fā)框架體系中就具有相應(yīng)的一個(gè)叫做State的編程框架。最近我開始對其進(jìn)行升級和重新設(shè)計(jì),將實(shí)現(xiàn)原理和概要設(shè)計(jì)方面的東西寫出來與大家共享,希望對各位有些啟發(fā)。同時(shí)希望借此得到你們一些好的建議和意見,以便能夠充實(shí)我們的框架。于此同時(shí),我寫了一個(gè)簡單的模擬程序?qū)崿F(xiàn)了該設(shè)計(jì)思想,有興趣的話可以通過這里下載該模擬程序。
一、單純基于SessionState編程的局限性
SessionState對于ASP.NET的開發(fā)者在熟悉不過了,我們可以通過它來存儲一些基于客戶端的狀態(tài)信息。從編程角度來說,SesssionState是依附和當(dāng)前HttpContext的一個(gè)用于類似于字典的數(shù)據(jù)容器,我們通過鍵值對的方式進(jìn)行Session Item的設(shè)置和獲取。但是這種單純地基于字典索引的編程方式,具有諸多局限:
首先,這種弱類型的編程方式不便于快速開發(fā)需求。放入SessionState的值是一個(gè)System.Object類型的對象,在獲取的使用我們需要進(jìn)行手工轉(zhuǎn)型;而Session Item的Key是手工指定的字符串,如果沒有對Key值進(jìn)行有效的分配,在進(jìn)行設(shè)置的時(shí)候很容易造成一個(gè)Key值得沖突,從而導(dǎo)致整個(gè)狀態(tài)的混亂;在獲取某個(gè)Session Item的時(shí)候,你指定的Key值可能和預(yù)先指定的不符。
其次,統(tǒng)一的SessionState的清除機(jī)制的缺乏導(dǎo)致服務(wù)端內(nèi)存壓力。在默認(rèn)的情況下(采用InProc會話模式),SessionState存儲于服務(wù)端內(nèi)存,如果過多、過大的Session Item常駐內(nèi)存,勢必會為服務(wù)端帶來內(nèi)存壓力。實(shí)際上,基于客戶端的所有的Session Item并不是在整個(gè)Session存續(xù)期間都是必須的,很多Session Item僅僅是在某幾個(gè)少數(shù)的Web頁面中使用。但是我們不能通過程序手工地將其從SessionState中刪除,因?yàn)槲覀儾荒艽_定該Session Item在那一刻不再需要,因?yàn)檫@往往取決于UI交互的行為。如果太多的低頻率使用的Session Item存在,并且它們還不小,服務(wù)端內(nèi)存過多地被占用必要導(dǎo)致性能的下降。
最后,如果你采用State Server或者SQL Server會話管理模式,還會造成更多的性能問題。這樣的性能損失包括:Session Item的序列化和反序列化、序列化后的Session Item在Web Server和State Server或者SQL Server的網(wǎng)絡(luò)傳輸、針對State Server或者SQL Server的數(shù)據(jù)存取(保存和提取)等。
實(shí)際上,我們的State框架還是建立在SessionState基礎(chǔ)之上,但是它能夠很好的解決上述的三大難題:
二、通過狀態(tài)后備存儲機(jī)制解決Web Server內(nèi)存的壓力
狀態(tài)的后備機(jī)制是整個(gè)狀態(tài)編程框架的核心。通過對所有狀態(tài)項(xiàng)的掃描,標(biāo)記出所有需要進(jìn)行后備存儲的狀態(tài)項(xiàng)。然后將它們進(jìn)行序列化,并借助于指定的后備存儲器將它們存儲到相應(yīng)的物理存儲介質(zhì)。最后,相應(yīng)的狀態(tài)會從SessionState中刪除,從而緩解了Web Server的內(nèi)存壓力。除了將序列化的狀態(tài)對象進(jìn)行后備存儲之前,后備存儲器還負(fù)責(zé)從相應(yīng)的存儲介質(zhì)中提取狀態(tài)數(shù)據(jù)。
簡單起見,我們并沒有在后臺運(yùn)行一個(gè)實(shí)施后備檢測操作的引擎,而是直接通過事件注冊的方式讓每一個(gè)請求自動去觸發(fā)基于本會話的后備存儲,我們注冊的事件是HttpApplication的PostRequestHandlerExecute。出于性能的考慮,當(dāng)事件PostRequestHandlerExecute被觸發(fā)的時(shí)候,并不是總是立即執(zhí)行后備狀態(tài)項(xiàng)的檢查。而是設(shè)置一個(gè)相鄰兩次后備檢查的間隔,只有超出這個(gè)間隔的情況下,才會進(jìn)行真正地區(qū)檢查那些狀態(tài)向需要進(jìn)行后備存儲了。狀態(tài)項(xiàng)的后備存儲緊接著在后備對象的檢查之后進(jìn)行。
我們通過一個(gè)具體的例子來進(jìn)一步說明后備存儲的過程。如左圖(點(diǎn)擊看大圖)所示,在Web Server的IIS進(jìn)程中的SessionState中維持著三個(gè)狀態(tài)項(xiàng):Foo、Bar、Baz。當(dāng)Web Server接收并執(zhí)行來自瀏覽器的HTTP請求后,PostRequestHandlerExecute事件的處罰激活了我們的后備檢查管理器,它發(fā)現(xiàn)狀態(tài)項(xiàng)Baz最近一次被訪問的時(shí)間到當(dāng)前時(shí)間的間隔已經(jīng)超出了設(shè)置的超時(shí)時(shí)限,并且計(jì)算出該對象的總字節(jié)數(shù)超過了設(shè)定的下限,就會將該對象標(biāo)記為后備存儲對象。在這種情況下,狀態(tài)項(xiàng)Baz的值,同它的Key一并進(jìn)行序列化并進(jìn)行后備存儲。最后將該Baz從SessionState中移除。
如果該Web應(yīng)用使用Web Farm部署方式,并采用了Sate Server或者SQL Server的會話模式,在同步到Sate Server或者SQL Server的時(shí)候,由于SessionState中缺少了Baz這個(gè)大對象,也會因?yàn)樯倭藢λ蛄谢⒕W(wǎng)絡(luò)傳輸和數(shù)據(jù)存取使性能得到相應(yīng)的提升。
三、后備存儲狀態(tài)項(xiàng)的"復(fù)蘇"
被后備存儲的狀態(tài)項(xiàng)已經(jīng)不再存儲于SessionState中,但是并不意味著它已經(jīng)是所謂的垃圾對象,它們依然可以被再次訪問。在這種情況下,我們會通過我們指定的后備存儲器將相應(yīng)的狀態(tài)值以字節(jié)數(shù)組的形式從存儲介質(zhì)中提取出來,進(jìn)行反序列化后再次放到SessionState中,我個(gè)人將這種機(jī)制成為"后備對象的復(fù)蘇"。
在對后備對象的復(fù)蘇機(jī)制進(jìn)行進(jìn)一步講解之前,我們需要了解一個(gè)前提:框架始終維護(hù)著每一個(gè)狀態(tài)項(xiàng)運(yùn)行時(shí)信息,這些信息包括:狀態(tài)項(xiàng)最后一次被訪問的時(shí)間、狀態(tài)項(xiàng)的使用范圍、狀態(tài)項(xiàng)當(dāng)前的存儲位置(SessionState或者BackingStore)、以及相關(guān)的后備策略信息等。這個(gè)列表放在SessionState中。
右面所示的序列圖(點(diǎn)擊看大圖)反映了當(dāng)我們的程序獲取某個(gè)狀態(tài)項(xiàng)時(shí),狀態(tài)后備機(jī)制采用的處理流程:當(dāng)接收到一個(gè)來自對某個(gè)狀態(tài)項(xiàng)的請求時(shí),根據(jù)Key值獲取該狀態(tài)項(xiàng)當(dāng)前的運(yùn)行時(shí)信息。如果運(yùn)行時(shí)信息反映它還存在于SessionState中(Location=Session),則直接從SessionState中返回,并更新它的運(yùn)行時(shí)信息(最后一次被訪問時(shí)間)。
如果該狀態(tài)項(xiàng)已經(jīng)進(jìn)行了背后存儲(Location=BackingStore),則借助相應(yīng)的后備存儲器從存儲介質(zhì)中對應(yīng)的值以字節(jié)數(shù)組的形式提取出來。在完成反系列化后再次保存到SessionState中,并更新相應(yīng)運(yùn)行時(shí)信息(最后一次訪問時(shí)間和當(dāng)前位置:BackingStore-〉Session)。最后返回反序列化后的具體狀態(tài)對象。
四、狀態(tài)項(xiàng)后備策略的定義
判斷一個(gè)存在于SessionState中的狀態(tài)項(xiàng)是否應(yīng)該被后備存儲取決于以下三個(gè)方面,當(dāng)同時(shí)滿足條件1和2,或者2和3的狀態(tài)項(xiàng)會被后備存儲。
但是我們的狀態(tài)后備策略并沒有直接應(yīng)用于單個(gè)的狀態(tài)項(xiàng),而是應(yīng)用于一個(gè)較大的粒度:狀態(tài)組——若干相關(guān)狀態(tài)項(xiàng)的組合。狀態(tài)組的結(jié)構(gòu)和應(yīng)用在它上面的后備策略通過配置進(jìn)行定義,下面的XML體現(xiàn)的配置大體上的結(jié)構(gòu)。
<?xml version="1.0" encoding="utf-8" ?>
<states>
<properties>
<property name="UserName" type="System.String"/>
<property name="Position" type="System.String"/>
</properties>
<group name="Profile" inactiveTimeout=
"00:10:00" minimunTotalBytes="1024" >
<property name="Age" type="System.Int32"/>
<property name="Address" type="System.String"/>
</group>
<group name="Product" inactiveTimeout="00:10:00"
minimunTotalBytes="1024" scope="Page1, Page2,Page3" >
<property name="ProductId" type="System.String"/>
<property name="UnitPrice" type="System.Decimal"/>
</group>
</states>
在上面的XML片段中,我們定義兩個(gè)全局的狀態(tài)項(xiàng)(UserName和Position)和兩個(gè)狀態(tài)組(Profile和Product)。兩個(gè)狀態(tài)組中又包含各自的狀態(tài)項(xiàng),以及對應(yīng)的后備策略。inactiveTimeout、minimumTotlaBytes和scope分別表示超時(shí)時(shí)限、序列化后的最下值和使用的范圍。
五、通過代碼生成機(jī)制幫助你以強(qiáng)類型的方式操作狀態(tài)
既然所有的狀態(tài)和數(shù)據(jù)類型(即可以是系統(tǒng)預(yù)定義類型,也可以是自定義類型)都能通過XML的形式表示出來,那么我們就能通過代碼生成機(jī)制將它們通過代碼的形式反映出來。你可以采用CodeDOM+Cutom Tool的方式[可以參考我的文章《從數(shù)據(jù)到代碼》(上篇、下篇)],或者是直接使用T4模板[可以參考我的文章《創(chuàng)建代碼生成器可以很簡單:如何通過T4模板生成代碼?》(上篇、下篇)]。比如說,你可以生成一個(gè)繼承自Page的類型,比如PageBase,添加如下一個(gè)State的屬性。(下面的代碼僅僅代碼大體的結(jié)構(gòu),并省略的具體的實(shí)現(xiàn))
public class PageBase : Page
{
public ExtendedRootStateNode State { get; }
}
public class ExtendedRootStateNode : RootStateNode
{
public string UserName { get; set; }
public string Position { get; set; }
public ProfileGroupStateNode Profile { get; private set; }
public ProductGroupStateNode Product { get; private set; }
}
public class ProfileGroupStateNode : GroupStateNode
{
public int Age { get; set; }
public Gender Gender { get; set; }
public string Address { get; set; }
}
public class ProductGroupStateNode : GroupStateNode
{
public string ProductId { get; set; }
public string ProductName { get; set; }
}
如果讓你的所有Web頁面都繼承自這個(gè)PageBase,你可以通過強(qiáng)類型的方式獲取或者設(shè)置每個(gè)狀態(tài)項(xiàng)了。
本站文章除注明轉(zhuǎn)載外,均為本站原創(chuàng)或翻譯。歡迎任何形式的轉(zhuǎn)載,但請務(wù)必注明出處、不得修改原文相關(guān)鏈接,如果存在內(nèi)容上的異議請郵件反饋至chenjj@fc6vip.cn
文章轉(zhuǎn)載自:博客轉(zhuǎn)載