首页 > 编程 > .NET > 正文

ASP.NET 中 Session 实现原理浅析 [2] 状态管理器

2024-07-10 12:56:57
字体:
来源:转载
供稿:网友
状态管理本来是一件很美好的事情,嘿嘿,只可惜总是有些厂商在实现的时候考虑得不那么周全。例如 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 的参考实现,不过还是任重而道远啊,呵呵



发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表