ASP.NET 中 Session 實現原理淺析 [2] 狀態管理器
最後更新:2017-02-28
來源:互聯網
上載者:User
asp.net|session 狀態管理本來是一件很美好的事情,嘿嘿,只可惜總是有些廠商在實現的時候考慮得不那麼周全。例如 MS 在 ASP 中的狀態管理實現就比較爛,因為只實現了一個進程內的基於記憶體的狀態管理,故而存在很多問題:
1.所有的 Session 資料都儲存在 Web 服務的進程中,會造成伺服器支援會話數量受到伺服器記憶體資源的限制問題,同時也因為大量非活動會話導致記憶體被無效佔用。
2.伺服器處理序崩潰會導致所有的會話資料丟失。
3.會話無法跨進程或在負載平衡情況下使用,除非負載平衡技術保證同一使用者每次都能被路由到同一機器上。就算這樣也無法保障伺服器崩潰造成的會話資料丟失。
4.需要 Cookie 的支援,而現在因為安全性問題,很多人在瀏覽器中關閉了 Cookie 和 js 的支援。
為此 ASP 的使用者不得不自己手工將會話資訊以會話 ID 為主鍵同步到外部資料庫中,以緩解類似問題。
而在 ASP.NET 中,因為設計時就考慮了這些問題,能夠避免這些限制:
1.支援進程外的狀態管理,通過獨立狀態管理服務或 SQL Server 狀態伺服器管理工作階段狀態
2.支援不使用 Cookie 的狀態維護,通過在 URL 中自動增加會話 ID 來避免使用 Cookie
3.通過獨立的狀態管理服務或SQL Server 狀態伺服器支援負載平衡時同步使用會話資訊
實現這些特性的正是上節提到的 SessionStateModule.InitModuleFromConfig 函數中,根據 sessionState 標記的 mode 屬性選擇的四種不同的狀態管理器實現。
以下內容為程式碼:
<system.web>
<sessionState mode="InProc"
stateConnectionString="tcpip=127.0.0.1:42424"
stateNetworkTimeout="10"
sqlConnectionString="data source=127.0.0.1;Integrated Security=SSPI"
cookieless="false"
timeout="20" />
</system.web>
Off 模式禁止會話管理,同時 ASP.NET 還允許通過在頁面中以 EnableSessionState 屬性細粒度管理頁面的會話支援狀態
以下內容為程式碼:
<%@ Page EnableSessionState=" True|False|ReadOnly" %>
InProc 模式相容以前 ASP 的策略,在 ASP.NET 同一進程空間內實現基於記憶體的工作階段狀態管理,速度最快但受到與 ASP 相同的限制;
StateServer 模式通過 ASP.NET 獨立安裝的 ASP.NET State Service 服務(aspnet_state.exe),以 stateConnectionString 指定的IP和連接埠響應工作階段狀態服務;
SQLServer 模式則通過 sqlConnectionString 指定的 SQL Server 服務器,以記憶體暫存資料表(以 InstallSqlState.sql建庫,使用 tempdb 記憶體資料庫)或獨立表(以InstallPersistSqlState.sql 監控,使用獨立的 ASPState 庫)維護工作階段狀態。
這四種不同的狀態管理器,在效能上據《Performance Tuning and Optimizing ASP.NET Appliation》一書的測試,相對值如下:
以下為引用:
Table 4-1: Normalized TTLB(Time to Last Byte) by Session State Mode (in Milliseconds per 100 Requests)
CONCURRENT BROWSERS MODE = OFF MODE = INPROC MODE = STATESERVER MODE = SQLSERVER
1 7.81 4.54 8.27 8.47
5 28.28 20.25 27.25 29.29
10 89.38 46.08 77.29 85.11
Table 4-2: Average Requests per Second by Session State Mode
CONCURRENT BROWSERS MODE = OFF MODE = INPROC MODE = STATESERVER MODE = SQLSERVER
1 18.86 24.17 18.31 18.11
5 21.66 25.74 21.54 21.34
10 17.23 23.8 18.11 17.6
可以看到,無論是從 TTLB 還是每秒平均請求數來說,進程外狀態管理器的效能都是可以令人接受的,當然還需要針對狀態管理情況在編寫代碼時做相關最佳化。不過要使用進程外狀態管理器,則儲存在會話中的對象受到必須提高二進位序列化支援的限制。
從使用角度來看,狀態管理器實際上都是由上節提到的 HttpSessionModule 建立管理,並通過 HttpSessionState 介面提供訪問的,結構如下圖:
MSDN 上的 Underpinnings of the Session State Implementation in ASP.NET 一文非常詳細的解釋了幾種不同狀態管理器的原理和使用,這兒就不羅嗦了。
從實現角度來看,上節中提到的 SessionStateModule.InitModuleFromConfig 函數,根據設定檔中狀態管理器的模式,分別建立 System.Web.SessionState.InProcStateClientManager, System.Web.SessionState.OutOfProcStateClientManager 和 System.Web.SessionState.SqlStateClientManager 三類狀態管理器的執行個體。他們都繼承自 System.Web.SessionState.StateClientManager 抽象基類,並通過 System.Web.SessionState.IStateClientManager 介面向 HttpApplication 提高狀態管理服務。
IStateClientManager 介面是狀態管理器的統一管理介面,主要提供以下功能:
以下內容為程式碼:
internal interface System.Web.SessionState.IStateClientManager.IStateClientManager
{
// 組態管理狀態管理器
void ConfigInit(SessionStateSectionHandler.Config config, SessionOnEndTarget onEndTarget);
// 儲存 SessionStateModule 執行個體供後面使用
void SetStateModule(SessionStateModule module);
void ResetTimeout(string id);
void Dispose();
void Set(string id, SessionStateItem item, bool inStorage);
// 維護狀態管理器內容
IAsyncResult BeginGet(string id, AsyncCallback cb, object state);
SessionStateItem EndGet(IAsyncResult ar);
IAsyncResult BeginGetExclusive(string id, AsyncCallback cb, object state);
SessionStateItem EndGetExclusive(IAsyncResult ar);
void ReleaseExclusive(string id, int lockCookie);
}
ConfigInit 方法主要在初始化狀態管理器時通知其根據配置進行初始化工作,並將負責工作階段狀態清除的 SessionOnEndTarget 對象執行個體綁定到會話管理器(我們後面討論工作階段狀態管理實現時詳細討論)。對 OutOfProcStateClientManager 和 SqlStateClientManager 來說,在此階段還會初始化與外部伺服器的串連,並通過一個 System.Web.Util.ResourcePool 執行個體,提供基於時間策略的資源集區來維護串連;
ResetTimeout 方法重設指定 Session 的逾時時間;對 InProcStateClientManager 來說,這個逾時時間是通過 System.Web.Caching.CacheInternal 類型實現的緩衝對象來使用的; OutOfProcStateClientManager 直接通過 MakeRequest 函數構造請求發給外部獨立的狀態管理器執行; SqlStateClientManager 則調用預存程序 TempResetTimeout 更新 ASPStateTempSessions 表的到期時間 Expires 欄位;
Dispose 方法是否狀態管理器的資源,落實到代碼就是對 OutOfProcStateClientManager 和 SqlStateClientManager 中資源集區的釋放;
Set 方法則將指定的 SessionStateItem 儲存到 id 相關的會話資料中,並根據 inStorage 指定的對象狀態,決定在發生異常的情況下是否釋放對此會話的鎖。與 ResetTimeout 的實作類別似,OutOfProcStateClientManager 發送請求給外部獨立的狀態管理器;SqlStateClientManager 調用預存程序 TempUpdateStateItemXXX 更新工作階段狀態表 ASPStateTempSessions 中的到期時間 Expires 欄位、鎖定狀態 Lock 欄位、以及狀態資訊 SessionItemShort/SessionItemLong (分別儲存7000位元組以下或之上的資料)。如發生異常並設定 inStorage 標記,則先調用 TempReleaseStateItemExclusive 釋放會話鎖。
對狀態管理器中資料的擷取較為複雜,IStateClientManager 介面使用的是非同步呼叫的模式,並為提高效率將獨佔的擷取資料單獨拿出來。狀態管理器實作類別通過通用基類 System.Web.SessionState.StateClientManager 實現的幾個工具方法,將資料擷取操作非同步化。再最終由實作類別通過 Get 和 GetExclusive 方法完成操作。擷取資料的方法 InProcStateClientManager 通過緩衝;OutOfProcStateClientManager 通過請求;SqlStateClientManager 通過 TempGetStateItemXXX 預存程序完成。
在瞭解了 SessionStateModule 控制的狀態伺服器的實現和使用方法後,我們來看看上層的 HttpSessionState 是如何使用的。
Mandeep S Bhatia 的 ASP.NET Session Management Internals 介紹了 HttpSessionState 內部完成狀態資訊管理的原理。HttpSessionState 的 Item 屬性實際上是通過 SessionDictionary 執行個體實現的。
以下內容為程式碼:
public sealed class HttpSessionState : ...
{
private SessionDictionary _dict;
public object this[string name]
{
get
{
return _dict[name];
}
set
{
_dict[name] = value;
}
}
}
而此 SessionDictionary 執行個體與 HttpSessionState 執行個體的構造,都是在前面提到的完成會話構造的 SessionStateModule.CompleteAcquireState 方法中完成的:
以下內容為程式碼:
public sealed class SessionStateModule : IHttpModule
{
private string _rqId;
private SessionDictionary _rqDict;
private HttpStaticObjectsCollection _rqStaticObjects; // 靜態對象,通過頁面中 <object Runat="Server" Scope="Session"/> 標記設定
private int _rqTimeout;
private bool _rqIsNewSession;
private bool _rqReadonly;
private HttpContext _rqContext;
private SessionStateItem _rqItem;
private void CompleteAcquireState()
{
if (_rqItem != null)
{
if (_rqItem.dict != null)
{
_rqDict = _rqItem.dict;
}
else
{
_rqDict = new SessionDictionary();
}
_rqStaticObjects = ((_rqItem.staticObjects != null) ? _rqItem.staticObjects :
_rqContext.Application.SessionStaticObjects.Clone());
_rqTimeout = _rqItem.timeout;
_rqIsNewSession = false;
_rqInStorage = true;
_rqStreamLength = _rqItem.streamLength;
}
else
{
_rqDict = new SessionDictionary();
_rqStaticObjects = _rqContext.Application.SessionStaticObjects.Clone();
_rqTimeout = SessionStateModule.s_config._timeout;
_rqIsNewSession = true;
_rqInStorage = false;
}
_rqDict.Dirty = false;
_rqSessionState = new HttpSessionState(_rqId, _rqDict, _rqStaticObjects, _rqTimeout, _rqIsNewSession,
SessionStateModule.s_config._isCookieless, SessionStateModule.s_config._mode, _rqReadonly);
_rqContext.Items.Add("AspSession", _rqSessionState);
}
}
這兒涉及到的幾個欄位,基本上都能跟 HttpSessionState 提供的公用屬性對應起來。需要注意的是 HttpSessionState.StaticObjects 是通過 ASP.NET 頁面上的 <object Runat="Server" Scope="Session"/> 類似標記靜態定義的;_rqReadonly 則是前面提到的 <%@ Page EnableSessionState=" ReadOnly" %> 標記設定的。
至此,狀態管理器的使用與實現方法基本上分析完成,下面整理一下其使用流程:
1.構造:HttpApplication 在初始化過程中調用 InitModules 初始化設定檔 Machine.config 中註冊的實現了 IHttpModule 介面的 HTTP 模組;其中 SessionStateModule 作為模組之一被構造並初始化;其 InitModuleFromConfig 方法根據設定檔中狀態管理器的相關配置,構造並初始化相應的狀態管理器;並根據各種條件調用 CompleteAcquireState 方法完成 HttpSessionState 的構造工作。
2.使用:HttpSessionState 通過 SessionDictionary 實現其 Item 屬性的狀態資料管理;SessionDictionary 本身由 SessionStateModule.OnReleaseState 在適當的時候寫回狀態管理器;其他維護操作也是通過 SessionStateModule 調用狀態管理器的 IStateClientManager 介面完成的。
3.實現:狀態管理器從抽象基類 StateClientManager 獲得非同步呼叫的封裝;通過 IStateClientManager 介面提供給 SessionStateModule 管理其初始化、釋放和管理的介面。
雖然 ASP.NET 做了很多工作,但個人感覺還遠遠不夠。例如 InProc/OutOfProc 實際上都是在記憶體中,只是解決了一個可靠性和資料集中同步的問題;SQL Server 雖然能夠解決容量、可靠性和資料集中同步的問題,但效率又受到影響。這方面 .NET 應該向 Java 好好學習一下,例如 Java 下 EHCache 和 OSCache 都提供了平滑的可配置二級(記憶體/硬碟)緩衝介質切換,並且後者還提供了對負載平衡的簡單支援,此外還有 JBoss 等實現的基於 IP 多播等實現技術的負載平衡緩衝實現等等,都遠遠超出了 ASP.NET 提供的緩衝機制所考慮到的範圍。雖然 ASP.NET 也有獨立的緩衝機制,MS 也提出了 Cache Application Block 的參考實現,不過還是任重而道遠啊,呵呵