Flier's Sky

天空,蓝色的天空,眼睛看不到的东西,眼睛看得到的东西
posts - 115, comments - 305, trackbacks - 42, articles - 0
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 属性选择的四种不同的状态管理器实现。
<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 的参考实现,不过还是任重而道远啊,呵呵

btw: 想回头有空写篇文章介绍一下 Java 下面几款缓存实现,如 EHCache, OSCache, JCS 等等,不知道有没有人感兴趣,呵呵

Feedback

#1楼   回复  引用    

2004-08-07 02:37 by 温少
Filer Lu,你竟然研究起ASP .NET来了,嘻嘻 :)

#2楼   回复  引用    

2004-08-07 09:40 by jinuo
是否有使用SQL Server存储会话状态的实例?贴出来?

thanks 

#3楼   回复  引用    

2004-08-07 10:32 by Fantasysoft
老大,我有兴趣听您介绍Java下面缓存实现哦,期待中~~~

#4楼   回复  引用    

2004-08-07 16:19 by Flier Lu
to 温少:

我还是两三年前折腾过一段时间ASP.NET,现在基本上已经生疏了。分析这个是因为前段有朋友问起ASP.NET中Session管理的成本和效率问题,加上手头项目原因正在看几个Java的Cache实现,故而有了这个分析。跟你们这些专业搞这个的还是存在很大差距的,补充一下吧,呵呵

to jinuo:

使用 SQL Server 存储会话状态其实很容易,改一下文中所说的 Machine.config 的 sessionState 标记,再使用自带sql脚本建库就行,MSDN和我文中引用的文章里有很详细的说明。

to Fantasysoft:

呵呵,等我整理一下思路先,那块也是一个比较大的话题了,只能找几个细节点大概说说我的理解。:D

#5楼   回复  引用    

2004-08-07 16:27 by 温少
@Flier Lu
我也两年多没写Web应用程序了,很多东西也记不清楚了,想写一些文章,但动手时就觉得懒懒的。
你本来技术功底就很好,文章写得很不错的,我认真去写,也未必写得比你好呢。

#6楼   回复  引用    

2004-08-13 00:21 by wayfarer
本文已加入精华区!

#7楼   回复  引用    

2004-08-19 22:25 by
各位大虾,请问在.NET下怎样实现System.String 类
或System.INT类

#8楼   回复  引用    

2004-08-20 01:05 by Flier Lu
to 笨:

可以用 Refelctor.NET 直接查看 BCL 实现,或者下载 Rotor 源码阅览。

#9楼   回复  引用    

2005-05-19 13:07 by Tinker
你好!看了你的文章后觉得挺有收获,谢了!
问个问题,最近碰到一个问题Application变量不能保存,也就是在每一次访问后,Application都会重新Start一次,请问有可能是什么原因?

#10楼   回复  引用  查看    

2005-06-05 00:49 by 双鱼座      
时间过去久了,可能作者已经不再维护这个文档了。Application会重新Start,这样的事我还没有遇到过。根据ms的文档,Application重启会因为Web.Config和Global.asax或者其它任何配置文档的改动而重启。是否在每一次访问的时候修改了这些文件?

我不怎么关心Session的建立,建立过程不重要,重要的是释放。我觉得会话建立时可能会为会话锁定一些资源,在会话释放后必须为这些资源的释放抛出一个通知。本来在Global的Session_End中是可以做到的,但是这种方式不具备扩展性和伸缩性。需要部署的时候重新编译而不能直接在某个IHttpModule中截获会话释放通知。

作者的文章的确写得很好,如果这方面作者能够给人一些提示就更好了。

#11楼   回复  引用  查看    

2005-12-03 17:33 by 晓风残月      
to thinker:
Application 重启我碰到过一次,刚开始也是莫名其妙的,后来发现,是因为我将 access数据库文件放在了了bin 目录, 如果对数据库进行了,写操作,应用程序就会重启了。

From 双鱼座:
“根据ms的文档,Application重启会因为Web.Config和Global.asax或者其它任何配置文档的改动而重启”

因此,可以补充一点的是,改写了bin目录的任意文件都会导致应用程序重启,但是IIS并不会重启。

#12楼   回复  引用  查看    

2006-03-07 21:43 by Laser.NET      
请问加入要自己重写Session机制,想把session持久化到外部的一个文件中,是否可能啊?该如何做呢?
以前好像看到过类似的文章,当时么有仔细看。想这样做主要是为了多个站点之间共享session。不知大家有没有好的建议,谢谢了!

#13楼   回复  引用    

2009-04-02 20:21 by lovegril
@Laser.NET
ding



发表评论

昵称: [登录] [注册]

主页:

邮箱:(仅博主可见)

评论内容:

  登录  注册

[使用Ctrl+Enter键快速提交评论]

0 30902




相关文章:

相关链接: