单纯基于SessionState编程的局限性
essionState对于ASP.NET的开发者在熟悉不过了我们可以通过它来存储一些基于客户端的状态信息。从编程角度来说SesssionState是依附和当前HttpContext的一个用于类似于字典的数据容器我们通过键值对的方式进行Session Item的设置和获取。但是这种单纯地基于字典索引的编程方式具有诸多局限首先这种弱类型的编程方式不便于快速开发需求。放入SessionState的值是一个System.Object类型的对象在获取的使用我们需要进行手工转型而Session Item的Key是手工指定的字符串如果没有对Key值进行有效的分配在进行设置的时候很容易造成一个Key值得冲突从而导致整个状态的混乱在获取某个Session Item的时候你指定的Key值可能和预先指定的不符。其次统一的SessionState的清除机制的缺乏导致服务端内存压力。在默认的情况下采用InProc会话模式SessionState存储于服务端内存如果过多、过大的Session Item常驻内存势必会为服务端带来内存压力。实际上基于客户端的所有的Session Item并不是在整个Session存续期间都是必须的很多Session Item仅仅是在某几个少数的Web页面中使用。但是我们不能通过程序手工地将其从SessionState中删除因为我们不能确定该Session Item在那一刻不再需要因为这往往取决于UI交互的行为。如果太多的低频率使用的Session Item存在并且它们还不小服务端内存过多地被占用必要导致性能的下降。最后如果你采用State Server或者SQL Server会话管理模式还会造成更多的性能问题。这样的性能损失包括Session Item的序列化和反序列化、序列化后的Session Item在Web Server和State Server或者SQL Server的网络传输、针对State Server或者SQL Server的数据存取保存和提取等。实际上我们的State框架还是建立在SessionState基础之上但是它能够很好的解决上述的三大难题通过配置为所有使用到的状态项状态属性名称、数据类型等提供结构化的定义并通过基于该结构化配置提供的代码生成使强类型编程成为可能。这比较类似于ASP.NET中Profile的配置和强类型编程的方式提供状态的后备存储Backing Storing机制将低频率使用的大对象从SessionState中移到相应的后备存储比如文件、数据库中从而缓解服务端内存压力提供灵活的后备策略定义方式以实现基于具体运行环境的最优配置。后备策略主要包括两方面的内容其一是怎样的状态项需要被后备存储其二采用怎样的方式进行后备存储。确定后备存储状态项的因素包括自最近一次被访问以来的超时时限通过使用频率判断状态项再次被使用的可能性需要被后备存储对象必须具有的最小字节数后备存储小对象毫无意义 以及状态项的作用域很多状态项的作用范围仅仅限于某一个相关的Web页面或者基于某个基地址等。而具体采用的后备存储方式决定于配置的“后备存储器”比如在我提供的例子中采用的是基于文件的存储方式你可以编写基于数据库的后备存储器。二、通过状态后备存储机制解决Web Server内存的压力状态的后备机制是整个状态编程框架的核心。通过对所有状态项的扫描标记出所有需要进行后备存储的状态项。然后将它们进行序列化并借助于指定的后备存储器将它们存储到相应的物理存储介质。最后相应的状态会从SessionState中删除从而缓解了Web Server的内存压力。除了将序列化的状态对象进行后备存储之前后备存储器还负责从相应的存储介质中提取状态数据。简单起见我们并没有在后台运行一个实施后备检测操作的引擎而是直接通过事件注册的方式让每一个请求自动去触发基于本会话的后备存储我们注册的事件是HttpApplication的PostRequestHandlerExecute。出于性能的考虑当事件PostRequestHandlerExecute被触发的时候并不是总是立即执行后备状态项的检查。而是设置一个相邻两次后备检查的间隔只有超出这个间隔的情况下才会进行真正地区检查那些状态向需要进行后备存储了。状态项的后备存储紧接着在后备对象的检查之后进行。我们通过一个具体的例子来进一步说明后备存储的过程。如左图点击看大图所示在Web Server的IIS进程中的SessionState中维持着三个状态项Foo、Bar、Baz。当Web Server接收并执行来自浏览器的HTTP请求后PostRequestHandlerExecute事件的处罚激活了我们的后备检查管理器它发现状态项Baz最近一次被访问的时间到当前时间的间隔已经超出了设置的超时时限并且计算出该对象的总字节数超过了设定的下限就会将该对象标记为后备存储对象。在这种情况下状态项Baz的值同它的Key一并进行序列化并进行后备存储。最后将该Baz从SessionState中移除。如果该Web应用使用Web Farm部署方式并采用了Sate Server或者SQL Server的会话模式在同步到Sate Server或者SQL Server的时候由于SessionState中缺少了Baz这个大对象也会因为少了对它序列化、网络传输和数据存取使性能得到相应的提升。三、后备存储状态项的“复苏”被后备存储的状态项已经不再存储于SessionState中但是并不意味着它已经是所谓的垃圾对象它们依然可以被再次访问。在这种情况下我们会通过我们指定的后备存储器将相应的状态值以字节数组的形式从存储介质中提取出来进行反序列化后再次放到SessionState中我个人将这种机制成为“后备对象的复苏”。在对后备对象的复苏机制进行进一步讲解之前我们需要了解一个前提框架始终维护着每一个状态项运行时信息这些信息包括状态项最后一次被访问的时间、状态项的使用范围、状态项当前的存储位置SessionState或者BackingStore、以及相关的后备策略信息等。这个列表放在SessionState中。右面所示的序列图点击看大图反映了当我们的程序获取某个状态项时状态后备机制采用的处理流程当接收到一个来自对某个状态项的请求时根据Key值获取该状态项当前的运行时信息。如果运行时信息反映它还存在于SessionState中LocationSession则直接从SessionState中返回并更新它的运行时信息最后一次被访问时间。如果该状态项已经进行了背后存储LocationBackingStore则借助相应的后备存储器从存储介质中对应的值以字节数组的形式提取出来。在完成反系列化后再次保存到SessionState中并更新相应运行时信息最后一次访问时间和当前位置BackingStore-〉Session。最后返回反序列化后的具体状态对象。四、状态项后备策略的定义判断一个存在于SessionState中的状态项是否应该被后备存储取决于以下三个方面当同时满足条件1和2或者2和3的状态项会被后备存储。针对该状态项的最近一次访问的事件到当前时间的间隔超过了设定的超时时限状态项的总的字节数超过了设定的需要进行后备存储的下限当前的请求的URL是否超出了设定的状态作用的范围。但是我们的状态后备策略并没有直接应用于单个的状态项而是应用于一个较大的粒度状态组——若干相关状态项的组合。状态组的结构和应用在它上面的后备策略通过配置进行定义下面的XML体现的配置大体上的结构。1:?xmlversion1.0encodingutf-8?2:states3:properties4:propertynameUserNametypeSystem.String/5:propertynamePositiontypeSystem.String/6:/properties7:groupnameProfileinactiveTimeout00:10:00minimunTotalBytes10248:propertynameAgetypeSystem.Int32/9:propertynameAddresstypeSystem.String/10:/group11:groupnameProductinactiveTimeout00:10:00minimunTotalBytes1024scopePage1, Page2,Page312:propertynameProductIdtypeSystem.String/13:propertynameUnitPricetypeSystem.Decimal/14:/group15:/states在上面的XML片段中我们定义两个全局的状态项UserName和Position和两个状态组Profile和Product。两个状态组中又包含各自的状态项以及对应的后备策略。inactiveTimeout、minimumTotlaBytes和scope分别表示超时时限、序列化后的最下值和使用的范围。五、通过代码生成机制帮助你以强类型的方式操作状态既然所有的状态和数据类型即可以是系统预定义类型也可以是自定义类型都能通过XML的形式表示出来那么我们就能通过代码生成机制将它们通过代码的形式反映出来。你可以采用CodeDOMCutom Tool的方式[可以参考我的文章《从数据到代码》上篇、下篇]或者是直接使用T4模板[可以参考我的文章《创建代码生成器可以很简单如何通过T4模板生成代码》上篇、下篇]。比如说你可以生成一个继承自Page的类型比如PageBase添加如下一个State的属性。下面的代码仅仅代码大体的结构并省略的具体的实现1:publicclassPageBase : Page2:{3:publicExtendedRootStateNode State { get; }4:}5:publicclassExtendedRootStateNode : RootStateNode6:{7:publicstringUserName { get; set; }8:publicstringPosition { get; set; }9:publicProfileGroupStateNode Profile { get;privateset; }10:publicProductGroupStateNode Product { get;privateset; }11:}12:publicclassProfileGroupStateNode : GroupStateNode13:{14:publicintAge { get; set; }15:publicGender Gender { get; set; }16:publicstringAddress { get; set; }17:}18:publicclassProductGroupStateNode : GroupStateNode19:{20:publicstringProductId { get; set; }21:publicstringProductName { get; set; }22:}如果让你的所有Web页面都继承自这个PageBase你可以通过强类型的方式获取或者设置每个状态项了。分类: [04] 架构思想