ASP.NET 中 Session 实现原理浅析 [2] 状态管理器
状态管理本来是一件很美好的事情,嘿嘿,只可惜总是有些厂商在实现的时候考虑得不那么周全。例如 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 属性选择的四种不同的状态管理器实现。
Off 模式禁止会话管理,同时 ASP.NET 还允许通过在页面中以 EnableSessionState 属性细粒度管理页面的会话支持状态
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》一书的测试,相对值如下:
可以看到,无论是从 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 接口是状态管理器的统一管理接口,主要提供以下功能:
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 实例实现的。
而此 SessionDictionary 实例与 HttpSessionState 实例的构造,都是在前面提到的完成会话构造的 SessionStateModule.CompleteAcquireState 方法中完成的:
这儿涉及到的几个字段,基本上都能跟 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 的参考实现,不过还是任重而道远啊,呵呵
btw: 想回头有空写篇文章介绍一下 Java 下面几款缓存实现,如 EHCache, OSCache, JCS 等等,不知道有没有人感兴趣,呵呵
状态管理本来是一件很美好的事情,嘿嘿,只可惜总是有些厂商在实现的时候考虑得不那么周全。例如 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 属性选择的四种不同的状态管理器实现。
|
Off 模式禁止会话管理,同时 ASP.NET 还允许通过在页面中以 EnableSessionState 属性细粒度管理页面的会话支持状态
|
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 接口是状态管理器的统一管理接口,主要提供以下功能:
|
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 实例实现的。
|
而此 SessionDictionary 实例与 HttpSessionState 实例的构造,都是在前面提到的完成会话构造的 SessionStateModule.CompleteAcquireState 方法中完成的:
|
这儿涉及到的几个字段,基本上都能跟 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 的参考实现,不过还是任重而道远啊,呵呵
btw: 想回头有空写篇文章介绍一下 Java 下面几款缓存实现,如 EHCache, OSCache, JCS 等等,不知道有没有人感兴趣,呵呵