明輝手游網(wǎng)中心:是一個免費(fèi)提供流行視頻軟件教程、在線學(xué)習(xí)分享的學(xué)習(xí)平臺!

ASP.NET 中 Session 完成原理淺析 [2] 狀態(tài)管理器

[摘要]狀態(tài)管理本來是一件很美好的事情,嘿嘿,只可惜總是有些廠商在實現(xiàn)的時候考慮得不那么周全。例如 MS 在 ASP 中的狀態(tài)管理實現(xiàn)就比較爛,因為只實現(xiàn)了一個進(jìn)程內(nèi)的基于內(nèi)存的狀態(tài)管理,故而存在很多問題:...
狀態(tài)管理本來是一件很美好的事情,嘿嘿,只可惜總是有些廠商在實現(xiàn)的時候考慮得不那么周全。例如 MS 在 ASP 中的狀態(tài)管理實現(xiàn)就比較爛,因為只實現(xiàn)了一個進(jìn)程內(nèi)的基于內(nèi)存的狀態(tài)管理,故而存在很多問題:

1.所有的 Session 數(shù)據(jù)都保存在 Web 服務(wù)的進(jìn)程中,會造成服務(wù)器支持會話數(shù)量受到服務(wù)器內(nèi)存資源的限制問題,同時也因為大量非活動會話導(dǎo)致內(nèi)存被無效占用。
2.服務(wù)器進(jìn)程崩潰會導(dǎo)致所有的會話數(shù)據(jù)丟失。
3.會話無法跨進(jìn)程或在負(fù)載均衡情況下使用,除非負(fù)載均衡技術(shù)保障同一用戶每次都能被路由到同一機(jī)器上。就算這樣也無法保障服務(wù)器崩潰造成的會話數(shù)據(jù)丟失。
4.需要 Cookie 的支持,而現(xiàn)在因為安全性問題,很多人在瀏覽器中關(guān)閉了 Cookie 和 js 的支持。

為此 ASP 的使用者不得不自己手工將會話信息以會話 ID 為主鍵同步到外部數(shù)據(jù)庫中,以緩解類似問題。

而在 ASP.NET 中,因為設(shè)計時就考慮了這些問題,能夠避免這些限制:

1.支持進(jìn)程外的狀態(tài)管理,通過獨(dú)立狀態(tài)管理服務(wù)或 SQL Server 狀態(tài)服務(wù)器管理會話狀態(tài)
2.支持不使用 Cookie 的狀態(tài)維護(hù),通過在 URL 中自動增加會話 ID 來避免使用 Cookie
3.通過獨(dú)立的狀態(tài)管理服務(wù)或SQL Server 狀態(tài)服務(wù)器支持負(fù)載均衡時同步使用會話信息

實現(xiàn)這些特性的正是上節(jié)提到的 SessionStateModule.InitModuleFromConfig 函數(shù)中,根據(jù) sessionState 標(biāo)記的 mode 屬性選擇的四種不同的狀態(tài)管理器實現(xiàn)。


以下內(nèi)容為程序代碼:

<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 屬性細(xì)粒度管理頁面的會話支持狀態(tài)
以下內(nèi)容為程序代碼:

<%@ Page EnableSessionState=" True False ReadOnly" %>



InProc 模式兼容以前 ASP 的策略,在 ASP.NET 同一進(jìn)程空間內(nèi)實現(xiàn)基于內(nèi)存的會話狀態(tài)管理,速度最快但受到與 ASP 相同的限制;
StateServer 模式通過 ASP.NET 獨(dú)立安裝的 ASP.NET State Service 服務(wù)(aspnet_state.exe),以 stateConnectionString 指定的IP和端口響應(yīng)會話狀態(tài)服務(wù);
SQLServer 模式則通過 sqlConnectionString 指定的 SQL Server 服務(wù)器,以內(nèi)存臨時表(以 InstallSqlState.sql建庫,使用 tempdb 內(nèi)存數(shù)據(jù)庫)或獨(dú)立表(以InstallPersistSqlState.sql 監(jiān)控,使用獨(dú)立的 ASPState 庫)維護(hù)會話狀態(tài)。

這四種不同的狀態(tài)管理器,在性能上據(jù)《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 還是每秒平均請求數(shù)來說,進(jìn)程外狀態(tài)管理器的性能都是可以令人接受的,當(dāng)然還需要針對狀態(tài)管理情況在編寫代碼時做相關(guān)優(yōu)化。不過要使用進(jìn)程外狀態(tài)管理器,則保存在會話中的對象受到必須提高二進(jìn)制序列化支持的限制。

從使用角度來看,狀態(tài)管理器實際上都是由上節(jié)提到的 HttpSessionModule 建立管理,并通過 HttpSessionState 接口提供訪問的,結(jié)構(gòu)如下圖:


MSDN 上的 Underpinnings of the Session State Implementation in ASP.NET 一文非常詳細(xì)的解釋了幾種不同狀態(tài)管理器的原理和使用,這兒就不羅嗦了。

從實現(xiàn)角度來看,上節(jié)中提到的 SessionStateModule.InitModuleFromConfig 函數(shù),根據(jù)配置文件中狀態(tài)管理器的模式,分別建立 System.Web.SessionState.InProcStateClientManager, System.Web.SessionState.OutOfProcStateClientManager 和 System.Web.SessionState.SqlStateClientManager 三類狀態(tài)管理器的實例。他們都繼承自 System.Web.SessionState.StateClientManager 抽象基類,并通過 System.Web.SessionState.IStateClientManager 接口向 HttpApplication 提高狀態(tài)管理服務(wù)。

IStateClientManager 接口是狀態(tài)管理器的統(tǒng)一管理接口,主要提供以下功能:
以下內(nèi)容為程序代碼:

internal interface System.Web.SessionState.IStateClientManager.IStateClientManager
{
// 配置管理狀態(tài)管理器
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);

// 維護(hù)狀態(tài)管理器內(nèi)容
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 方法主要在初始化狀態(tài)管理器時通知其根據(jù)配置進(jìn)行初始化工作,并將負(fù)責(zé)會話狀態(tài)清除的 SessionOnEndTarget 對象實例綁定到會話管理器(我們后面討論會話狀態(tài)管理實現(xiàn)時詳細(xì)討論)。對 OutOfProcStateClientManager 和 SqlStateClientManager 來說,在此階段還會初始化與外部服務(wù)器的連接,并通過一個 System.Web.Util.ResourcePool 實例,提供基于時間策略的資源池來維護(hù)連接;
ResetTimeout 方法重置指定 Session 的超時時間;對 InProcStateClientManager 來說,這個超時時間是通過 System.Web.Caching.CacheInternal 類型實現(xiàn)的緩存對象來使用的; OutOfProcStateClientManager 直接通過 MakeRequest 函數(shù)構(gòu)造請求發(fā)給外部獨(dú)立的狀態(tài)管理器執(zhí)行; SqlStateClientManager 則調(diào)用存儲過程 TempResetTimeout 更新 ASPStateTempSessions 表的過期時間 Expires 字段;
Dispose 方法是否狀態(tài)管理器的資源,落實到代碼就是對 OutOfProcStateClientManager 和 SqlStateClientManager 中資源池的釋放;

Set 方法則將指定的 SessionStateItem 存儲到 id 相關(guān)的會話數(shù)據(jù)中,并根據(jù) inStorage 指定的對象狀態(tài),決定在發(fā)生異常的情況下是否釋放對此會話的鎖。與 ResetTimeout 的實現(xiàn)類似,OutOfProcStateClientManager 發(fā)送請求給外部獨(dú)立的狀態(tài)管理器;SqlStateClientManager 調(diào)用存儲過程 TempUpdateStateItemXXX 更新會話狀態(tài)表 ASPStateTempSessions 中的過期時間 Expires 字段、鎖定狀態(tài) Lock 字段、以及狀態(tài)信息 SessionItemShort/SessionItemLong (分別保存7000字節(jié)以下或之上的數(shù)據(jù))。如發(fā)生異常并設(shè)置 inStorage 標(biāo)記,則先調(diào)用 TempReleaseStateItemExclusive 釋放會話鎖。

對狀態(tài)管理器中數(shù)據(jù)的獲取較為復(fù)雜,IStateClientManager 接口使用的是異步調(diào)用的模式,并為提高效率將獨(dú)占的獲取數(shù)據(jù)單獨(dú)拿出來。狀態(tài)管理器實現(xiàn)類通過通用基類 System.Web.SessionState.StateClientManager 實現(xiàn)的幾個工具方法,將數(shù)據(jù)獲取操作異步化。再最終由實現(xiàn)類通過 Get 和 GetExclusive 方法完成操作。獲取數(shù)據(jù)的方法 InProcStateClientManager 通過緩存;OutOfProcStateClientManager 通過請求;SqlStateClientManager 通過 TempGetStateItemXXX 存儲過程完成。

在了解了 SessionStateModule 控制的狀態(tài)服務(wù)器的實現(xiàn)和使用方法后,我們來看看上層的 HttpSessionState 是如何使用的。



Mandeep S Bhatia 的 ASP.NET Session Management Internals 介紹了 HttpSessionState 內(nèi)部完成狀態(tài)信息管理的原理。HttpSessionState 的 Item 屬性實際上是通過 SessionDictionary 實例實現(xiàn)的。
以下內(nèi)容為程序代碼:

public sealed class HttpSessionState : ...
{
private SessionDictionary _dict;

public object this[string name]
{
get
{
return _dict[name];
}
set
{
_dict[name] = value;
}
}
}



而此 SessionDictionary 實例與 HttpSessionState 實例的構(gòu)造,都是在前面提到的完成會話構(gòu)造的 SessionStateModule.CompleteAcquireState 方法中完成的:
以下內(nèi)容為程序代碼:

public sealed class SessionStateModule : IHttpModule
{
private string _rqId;
private SessionDictionary _rqDict;
private HttpStaticObjectsCollection _rqStaticObjects; // 靜態(tài)對象,通過頁面中 <object Runat="Server" Scope="Session"/> 標(biāo)記設(shè)置
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 提供的公共屬性對應(yīng)起來。需要注意的是 HttpSessionState.StaticObjects 是通過 ASP.NET 頁面上的 <object Runat="Server" Scope="Session"/> 類似標(biāo)記靜態(tài)定義的;_rqReadonly 則是前面提到的 <%@ Page EnableSessionState=" ReadOnly" %> 標(biāo)記設(shè)置的。

至此,狀態(tài)管理器的使用與實現(xiàn)方法基本上分析完成,下面整理一下其使用流程:

1.構(gòu)造:HttpApplication 在初始化過程中調(diào)用 InitModules 初始化配置文件 Machine.config 中注冊的實現(xiàn)了 IHttpModule 接口的 HTTP 模塊;其中 SessionStateModule 作為模塊之一被構(gòu)造并初始化;其 InitModuleFromConfig 方法根據(jù)配置文件中狀態(tài)管理器的相關(guān)配置,構(gòu)造并初始化相應(yīng)的狀態(tài)管理器;并根據(jù)各種條件調(diào)用 CompleteAcquireState 方法完成 HttpSessionState 的構(gòu)造工作。
2.使用:HttpSessionState 通過 SessionDictionary 實現(xiàn)其 Item 屬性的狀態(tài)數(shù)據(jù)管理;SessionDictionary 本身由 SessionStateModule.OnReleaseState 在適當(dāng)?shù)臅r候?qū)懟貭顟B(tài)管理器;其他維護(hù)操作也是通過 SessionStateModule 調(diào)用狀態(tài)管理器的 IStateClientManager 接口完成的。
3.實現(xiàn):狀態(tài)管理器從抽象基類 StateClientManager 獲得異步調(diào)用的封裝;通過 IStateClientManager 接口提供給 SessionStateModule 管理其初始化、釋放和管理的接口。

雖然 ASP.NET 做了很多工作,但個人感覺還遠(yuǎn)遠(yuǎn)不夠。例如 InProc/OutOfProc 實際上都是在內(nèi)存中,只是解決了一個可靠性和數(shù)據(jù)集中同步的問題;SQL Server 雖然能夠解決容量、可靠性和數(shù)據(jù)集中同步的問題,但效率又受到影響。這方面 .NET 應(yīng)該向 Java 好好學(xué)習(xí)一下,例如 Java 下 EHCache 和 OSCache 都提供了平滑的可配置二級(內(nèi)存/硬盤)緩存介質(zhì)切換,并且后者還提供了對負(fù)載均衡的簡單支持,此外還有 JBoss 等實現(xiàn)的基于 IP 多播等實現(xiàn)技術(shù)的負(fù)載均衡緩存實現(xiàn)等等,都遠(yuǎn)遠(yuǎn)超出了 ASP.NET 提供的緩存機(jī)制所考慮到的范圍。雖然 ASP.NET 也有獨(dú)立的緩存機(jī)制,MS 也提出了 Cache Application Block 的參考實現(xiàn),不過還是任重而道