ASP.NET應用下基於SessionState的“狀態編程框架”解決方案
在一個基於ASP.NET的Web應用程序中,我們通常使用SessionState保存基於某個客戶端的狀態信息。但是這種單純使用SessionState的編程方式具有很多局限,比如Session Item的,比如沒有一個有效的。為了實現對客戶端狀態的有效管理,並提高應用開發效率,在很多年前我們的開發框架體係中就具有相應的一個叫做State的編程框架。最近我開始對其進行升級和重新設計,將實現原理和概要設計方麵的東西寫出來與大家共享,希望對各位有些啟發。同時希望借此得到你們一些好的建議和意見,以便能夠充實我們的框架。於此同時,我寫了一個簡單的模擬程序實現了該設計思想,有興趣的話可以通過這裏下載該模擬程序。
目錄
一、單純基於SessionState編程的局限性
二、通過狀態後備存儲機製解決Web Server內存的壓力
三、後備存儲狀態項的“複蘇”
四、狀態項後備策略的定義
五、通過代碼生成機製幫助你以強類型的方式操作狀態
SessionState對於ASP.NET的開發者在熟悉不過了,我們可以通過它來存儲一些基於客戶端的狀態信息。從編程角度來說,,我們通過的方式進行Session Item的設置和獲取。但是這種單純地基於字典索引的編程方式,具有諸多局限:
- 。放入SessionState的值是一個System.Object類型的對象,在獲取的使用我們需要進行;而Session Item的Key是手工指定的字符串,如果沒有對Key值進行有效的分配,在進行設置的時候很容易造成一個,從而導致整個狀態的混亂;在獲取某個Session Item的時候,你指定的Key值可能和預先指定的不符。
- 。在默認的情況下(采用InProc會話模式),SessionState存儲於服務端內存,如果過多、過大的Session Item常駐內存,勢必會為服務端帶來內存壓力。實際上,基於客戶端的所有的Session Item並不是在整個Session存續期間都是必須的,很多Session Item僅僅是在某幾個少數的Web頁麵中使用。但是我們不能通過程序手工地將其從SessionState中刪除,因為我們不能確定該Session Item在那一刻不再需要,因為這往往取決於UI交互的行為。如果太多的低頻率使用的Session Item存在,並且它們還不小,服務端內存過多地被占用必要導致性能的下降。
- 。這樣的性能損失包括:Session Item的序列化和反序列化、序列化後的Session Item在Web Server和State Server或者SQL Server的網絡傳輸、針對State Server或者SQL Server的數據存取(保存和提取)等。
實際上,我們的State框架還是建立在SessionState基礎之上,但是它能夠很好的解決上述的三大難題:
- 通過配置為所有使用到的狀態項(狀態屬性名稱、數據類型等)提供結構化的定義,並通過基於該結構化配置提供的成為可能。這比較類似於ASP.NET中的配置和強類型編程的方式;
- 提供狀態的(Backing Storing)機製將低頻率使用的大對象從SessionState中移到相應的後備存儲(比如文件、數據庫)中,從而緩解服務端內存壓力;
- 提供方式以實現基於具體運行環境的最優配置。後備策略主要包括兩方麵的內容,其一是怎樣的狀態項需要被後備存儲,其二采用怎樣的方式進行後備存儲。確定後備存儲狀態項的因素包括:自最近一次被訪問以來的超時時限(通過使用頻率判斷狀態項再次被使用的可能性);需要被後備存儲對象必須具有的最小字節數(後備存儲小對象毫無意義) ;以及狀態項的作用域(很多狀態項的作用範圍僅僅限於某一個相關的Web頁麵,或者基於某個基地址)等。而具體采用的後備存儲方式決定於配置的“後備存儲器”,比如在我提供的例子中采用的是基於文件的存儲方式,你可以編寫基於數據庫的後備存儲器。
狀態的後備機製是整個狀態編程框架的核心。通過對所有狀態項的掃描,。然後將它們進行,並借助於指定的將它們存儲到相應的物理存儲介質。最後,相應的狀態會,從而緩解了Web Server的內存壓力。除了將序列化的狀態對象進行後備存儲之前,後備存儲器還負責從相應的存儲介質中提取狀態數據。
簡單起見,我們並沒有在後台運行一個實施後備檢測操作的引擎,而是直接通過的方式讓每一個請求自動去觸發基於本會話的後備存儲,我們注冊的事件是HttpApplication的。出於性能的考慮,當事件PostRequestHandlerExecute被觸發的時候,並不是總是立即執行後備狀態項的檢查。而是設置一個,隻有超出這個間隔的情況下,才會進行真正地區檢查那些狀態向需要進行後備存儲了。狀態項的後備存儲緊接著在後備對象的檢查之後進行。
我們通過一個具體的例子來進一步說明後備存儲的過程。如左圖(點擊看大圖)所示,在Web Server的IIS進程中的SessionState中維持著三個狀態項:Foo、Bar、Baz。當Web Server接收並執行來自瀏覽器的HTTP請求後,,它發現狀態項在這種情況下,狀態項Baz的值,同它的Key一並進行序列化並進行後備存儲。最後將該Baz從SessionState中移除。
如果該Web應用使用Web Farm部署方式,並采用了或者的會話模式,在同步到Sate Server或者SQL Server的時候,由於SessionState中缺少了Baz這個大對象,也會因為少了對它序列化、網絡傳輸和數據存取使性能得到相應的提升。
被後備存儲的狀態項已經不再存儲於SessionState中,但是,它們依然。在這種情況下,我們會通過我們指定的後備存儲器將相應的狀態值以字節數組的形式從存儲介質中提取出來,進行反序列化後再次放到SessionState中,我個人將這種機製成為“”。
在對機製進行進一步講解之前,我們需要了解一個前提:框架始終維護著,這些信息包括:狀態項、、(SessionState或者BackingStore)、以及相關的等。這個列表放在SessionState中。
右麵所示的序列圖(點擊看大圖)反映了當我們的程序獲取某個狀態項時,狀態後備機製采用的處理流程:當接收到一個來自對某個狀態項的請求時,根據Key值獲取該狀態項當前的運行時信息。如果運行時信息反映它還存在於SessionState中(Location=Session),則直接從SessionState中返回,並更新它的運行時信息(最後一次被訪問時間)。
如果該狀態項已經進行了背後存儲(Location=BackingStore),則借助相應的後備存儲器從存儲介質中對應的值以字節數組的形式提取出來。在完成反係列化後再次保存到SessionState中,並更新相應運行時信息(最後一次訪問時間和當前位置:BackingStore-〉Session)。最後返回反序列化後的具體狀態對象。
判斷一個存在於SessionState中的狀態項是否應該被後備存儲取決於以下三個方麵,當同時滿足條件1和2,或者2和3的狀態項會被後備存儲。
- 針對該狀態項的最近一次訪問的事件到當前時間的間隔超過了設定的超時時限;
- 狀態項的總的字節數超過了設定的需要進行後備存儲的下限;
- 當前的請求的URL是否超出了設定的狀態作用的範圍。
但是我們的狀態後備策略並,而是應用於一個較大的粒度:。狀態組的結構和應用在它上麵的後備策略通過配置進行定義,下麵的XML體現的配置大體上的結構。
1: <?xml version="1.0" encoding="utf-8" ?>
2: <states>
3: <properties>
4: <property name="UserName" type="System.String"/>
5: <property name="Position" type="System.String"/>
6: </properties>
7: <group name="Profile" inactiveTimeout="00:10:00" minimunTotalBytes="1024" >
8: <property name="Age" type="System.Int32"/>
9: <property name="Address" type="System.String"/>
10: </group>
11: <group name="Product" inactiveTimeout="00:10:00" minimunTotalBytes="1024" scope="Page1, Page2,Page3" >
12: <property name="ProductId" type="System.String"/>
13: <property name="UnitPrice" type="System.Decimal"/>
14: </group>
15: </states>
在上麵的XML片段中,我們定義兩個(UserName和Position)和兩個(Profile和Product)。兩個狀態組中又包含各自的狀態項,以及對應的後備策略。、和分別表示超時時限、序列化後的最下值和使用的範圍。
既然所有的狀態和數據類型(即可以是係統預定義類型,也可以是自定義類型)都能通過XML的形式表示出來,那麼我們就能通過代碼生成機製將它們通過代碼的形式反映出來。你可以采用CodeDOM+Cutom Tool的方式[可以參考我的文章《從數據到代碼》(上篇、下篇)],或者是直接使用T4模板[可以參考我的文章《創建代碼生成器可以很簡單:如何通過T4模板生成代碼?》(上篇、下篇)]。比如說,你可以生成一個繼承自Page的類型,比如PageBase,添加如下一個State的屬性。(下麵的代碼僅僅代碼大體的結構,並省略的具體的實現)
1: public class PageBase : Page
2: {
3: public ExtendedRootStateNode State { get; }
4: }
5: public class ExtendedRootStateNode : RootStateNode
6: {
7: public string UserName { get; set; }
8: public string Position { get; set; }
9: public ProfileGroupStateNode Profile { get; private set; }
10: public ProductGroupStateNode Product { get; private set; }
11: }
12: public class ProfileGroupStateNode : GroupStateNode
13: {
14: public int Age { get; set; }
15: public Gender Gender { get; set; }
16: public string Address { get; set; }
17: }
18: public class ProductGroupStateNode : GroupStateNode
19: {
20: public string ProductId { get; set; }
21: public string ProductName { get; set; }
22: }
如果讓你的所有Web頁麵都繼承自這個PageBase,你可以通過的方式獲取或者設置每個狀態項了。
微信公眾賬號:大內老A
微博:www.weibo.com/artech
如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識別二維碼)關注個人公眾號(原來公眾帳號蔣金楠的自媒體將會停用)。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁麵明顯位置給出原文連接,否則保留追究法律責任的權利。
最後更新:2017-10-27 14:04:22