ASP.NET应用下基于SessionState的“状态编程框架”解决方案

本文涉及的产品
云数据库 RDS SQL Server,基础系列 2核4GB
RDS SQL Server Serverless,2-4RCU 50GB 3个月
推荐场景:
简介:

在一个基于ASP.NET的Web应用程序中,我们通常使用SessionState保存基于某个客户端的状态信息。但是这种单纯使用SessionState的编程方式具有很多局限,比如Session Item的Key值冲突,比如没有一个有效的SessionState清除机制会为Web Server带来内存压力。为了实现对客户端状态的有效管理,并提高应用开发效率,在很多年前我们的开发框架体系中就具有相应的一个叫做State的编程框架。最近我开始对其进行升级和重新设计,将实现原理和概要设计方面的东西写出来与大家共享,希望对各位有些启发。同时希望借此得到你们一些好的建议和意见,以便能够充实我们的框架。于此同时,我写了一个简单的模拟程序实现了该设计思想,有兴趣的话可以通过这里下载该模拟程序。

目录
一、单纯基于SessionState编程的局限性
二、通过状态后备存储机制解决Web Server内存的压力
三、后备存储状态项的“复苏”
四、状态项后备策略的定义
五、通过代码生成机制帮助你以强类型的方式操作状态

一、单纯基于SessionState编程的局限性

SessionState对于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的内存压力。除了将序列化的状态对象进行后备存储之前,后备存储器还负责从相应的存储介质中提取状态数据。

image简单起见,我们并没有在后台运行一个实施后备检测操作的引擎,而是直接通过事件注册的方式让每一个请求自动去触发基于本会话的后备存储,我们注册的事件是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这个大对象,也会因为少了对它序列化、网络传输和数据存取使性能得到相应的提升。

三、后备存储状态项的“复苏”

Drawing1被后备存储的状态项已经不再存储于SessionState中,但是并不意味着它已经是所谓的垃圾对象,它们依然可以被再次访问。在这种情况下,我们会通过我们指定的后备存储器将相应的状态值以字节数组的形式从存储介质中提取出来,进行反序列化后再次放到SessionState中,我个人将这种机制成为“后备对象的复”。

在对后备对象的复苏机制进行进一步讲解之前,我们需要了解一个前提:框架始终维护着每一个状态项运行时信息,这些信息包括:状态项最后一次被访问的时间状态项的使用范围状态项当前的存储位置(SessionState或者BackingStore)、以及相关的后备策略信息等。这个列表放在SessionState中。

右面所示的序列图(点击看大图)反映了当我们的程序获取某个状态项时,状态后备机制采用的处理流程:当接收到一个来自对某个状态项的请求时,根据Key值获取该状态项当前的运行时信息。如果运行时信息反映它还存在于SessionState中(Location=Session),则直接从SessionState中返回,并更新它的运行时信息(最后一次被访问时间)。

如果该状态项已经进行了背后存储(Location=BackingStore),则借助相应的后备存储器从存储介质中对应的值以字节数组的形式提取出来。在完成反系列化后再次保存到SessionState中,并更新相应运行时信息(最后一次访问时间和当前位置:BackingStore-〉Session)。最后返回反序列化后的具体状态对象。

四、状态项后备策略的定义

判断一个存在于SessionState中的状态项是否应该被后备存储取决于以下三个方面,当同时满足条件1和2,或者2和3的状态项会被后备存储。

  • 针对该状态项的最近一次访问的事件到当前时间的间隔超过了设定的超时时限;
  • 状态项的总的字节数超过了设定的需要进行后备存储的下限;
  • 当前的请求的URL是否超出了设定的状态作用的范围。

但是我们的状态后备策略并没有直接应用于单个的状态项,而是应用于一个较大的粒度:状态组——若干相关状态项的组合。状态组的结构和应用在它上面的后备策略通过配置进行定义,下面的XML体现的配置大体上的结构。

   1: <?xml version="1.0" encoding="utf-8" ?>
   2: <states>
   3:   <properties>
   4:     <property name="UserName" type="System.String"/>
   5:     <property name="Position" type="System.String"/>
   6:   </properties>
   7:   <group name="Profile" inactiveTimeout="00:10:00" minimunTotalBytes="1024" >
   8:     <property name="Age" type="System.Int32"/>
   9:     <property name="Address" type="System.String"/>
  10:   </group>
  11:   <group name="Product" inactiveTimeout="00:10:00" minimunTotalBytes="1024" scope="Page1, Page2,Page3" >
  12:     <property name="ProductId" type="System.String"/>
  13:     <property name="UnitPrice" type="System.Decimal"/>
  14:   </group>
  15: </states>

在上面的XML片段中,我们定义两个全局的状态项(UserName和Position)和两个状态组(Profile和Product)。两个状态组中又包含各自的状态项,以及对应的后备策略。inactiveTimeoutminimumTotlaBytesscope分别表示超时时限、序列化后的最下值和使用的范围。

五、通过代码生成机制帮助你以强类型的方式操作状态

既然所有的状态和数据类型(即可以是系统预定义类型,也可以是自定义类型)都能通过XML的形式表示出来,那么我们就能通过代码生成机制将它们通过代码的形式反映出来。你可以采用CodeDOM+Cutom Tool的方式[可以参考我的文章《从数据到代码》(上篇下篇)],或者是直接使用T4模板[可以参考我的文章《创建代码生成器可以很简单:如何通过T4模板生成代码?》(上篇下篇)]。比如说,你可以生成一个继承自Page的类型,比如PageBase,添加如下一个State的属性。(下面的代码仅仅代码大体的结构,并省略的具体的实现)

   1: public class PageBase : Page
   2: {
   3:     public ExtendedRootStateNode State { get; }
   4: }
   5: public class ExtendedRootStateNode : RootStateNode
   6: {
   7:     public string UserName { get; set; }
   8:     public string Position { get; set; }
   9:     public ProfileGroupStateNode Profile { get; private set; }
  10:     public ProductGroupStateNode Product { get; private set; }       
  11: }
  12: public class ProfileGroupStateNode : GroupStateNode
  13: {
  14:     public int Age { get; set; }
  15:     public Gender Gender { get; set; }
  16:     public string Address { get; set; }
  17: }
  18: public class ProductGroupStateNode : GroupStateNode
  19: {
  20:     public string ProductId { get; set; }
  21:     public string ProductName { get; set; }
  22: }

如果让你的所有Web页面都继承自这个PageBase,你可以通过强类型的方式获取或者设置每个状态项了。

image


作者:蒋金楠
微信公众账号:大内老A
微博: www.weibo.com/artech
如果你想及时得到个人撰写文章以及著作的消息推送,或者想看看个人推荐的技术资料,可以扫描左边二维码(或者长按识别二维码)关注个人公众号(原来公众帐号 蒋金楠的自媒体将会停用)。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
相关实践学习
使用SQL语句管理索引
本次实验主要介绍如何在RDS-SQLServer数据库中,使用SQL语句管理索引。
SQL Server on Linux入门教程
SQL Server数据库一直只提供Windows下的版本。2016年微软宣布推出可运行在Linux系统下的SQL Server数据库,该版本目前还是早期预览版本。本课程主要介绍SQLServer On Linux的基本知识。 相关的阿里云产品:云数据库RDS&nbsp;SQL Server版 RDS SQL Server不仅拥有高可用架构和任意时间点的数据恢复功能,强力支撑各种企业应用,同时也包含了微软的License费用,减少额外支出。 了解产品详情:&nbsp;https://www.aliyun.com/product/rds/sqlserver
相关文章
|
10月前
|
存储 Shell Linux
快速上手基于 BaGet 的脚本自动化构建 .net 应用打包
本文介绍了如何使用脚本自动化构建 `.net` 应用的 `nuget` 包并推送到指定服务仓库。首先概述了 `BaGet`——一个开源、轻量级且高性能的 `NuGet` 服务器,支持多种存储后端及配置选项。接着详细描述了 `BaGet` 的安装、配置及使用方法,并提供了 `PowerShell` 和 `Bash` 脚本实例,用于自动化推送 `.nupkg` 文件。最后总结了 `BaGet` 的优势及其在实际部署中的便捷性。
410 10
|
9月前
|
XML JSON API
ServiceStack:不仅仅是一个高性能Web API和微服务框架,更是一站式解决方案——深入解析其多协议支持及简便开发流程,带您体验前所未有的.NET开发效率革命
【10月更文挑战第9天】ServiceStack 是一个高性能的 Web API 和微服务框架,支持 JSON、XML、CSV 等多种数据格式。它简化了 .NET 应用的开发流程,提供了直观的 RESTful 服务构建方式。ServiceStack 支持高并发请求和复杂业务逻辑,安装简单,通过 NuGet 包管理器即可快速集成。示例代码展示了如何创建一个返回当前日期的简单服务,包括定义请求和响应 DTO、实现服务逻辑、配置路由和宿主。ServiceStack 还支持 WebSocket、SignalR 等实时通信协议,具备自动验证、自动过滤器等丰富功能,适合快速搭建高性能、可扩展的服务端应用。
503 3
|
5月前
|
监控 Linux
yum install -y net-snmp-devel 安装不成功 zabbix项目安装,Errors during downloading metadata for repository ‘extras-common’:问题解决方案-优雅草卓伊凡
yum install -y net-snmp-devel 安装不成功 zabbix项目安装,Errors during downloading metadata for repository ‘extras-common’:问题解决方案-优雅草卓伊凡
149 13
yum install -y net-snmp-devel 安装不成功 zabbix项目安装,Errors during downloading metadata for repository ‘extras-common’:问题解决方案-优雅草卓伊凡
|
6月前
|
C# Android开发 iOS开发
2025年全面的.NET跨平台应用框架推荐
2025年全面的.NET跨平台应用框架推荐
223 23
|
8月前
|
开发框架 监控 .NET
【Azure App Service】部署在App Service上的.NET应用内存消耗不能超过2GB的情况分析
x64 dotnet runtime is not installed on the app service by default. Since we had the app service running in x64, it was proxying the request to a 32 bit dotnet process which was throwing an OutOfMemoryException with requests >100MB. It worked on the IaaS servers because we had the x64 runtime install
120 5
|
9月前
|
传感器 数据采集 物联网
探索.NET nanoFramework:为嵌入式设备编程的新途径
探索.NET nanoFramework:为嵌入式设备编程的新途
314 7
|
8月前
|
JSON 算法 安全
JWT Bearer 认证在 .NET Core 中的应用
【10月更文挑战第30天】JWT(JSON Web Token)是一种开放标准,用于在各方之间安全传输信息。它由头部、载荷和签名三部分组成,用于在用户和服务器之间传递声明。JWT Bearer 认证是一种基于令牌的认证方式,客户端在请求头中包含 JWT 令牌,服务器验证令牌的有效性后授权用户访问资源。在 .NET Core 中,通过安装 `Microsoft.AspNetCore.Authentication.JwtBearer` 包并配置认证服务,可以实现 JWT Bearer 认证。具体步骤包括安装 NuGet 包、配置认证服务、启用认证中间件、生成 JWT 令牌以及在控制器中使用认证信息
304 2
|
9月前
|
监控 网络安全 调度
Quartz.Net整合NetCore3.1,部署到IIS服务器上后台定时Job不被调度的解决方案
解决Quartz.NET在.NET Core 3.1应用中部署到IIS服务器上不被调度的问题,通常需要综合考虑应用配置、IIS设置、日志分析等多个方面。采用上述策略,结合细致的测试和监控,可以有效地提高定时任务的稳定性和可靠性。在实施任何更改后,务必进行充分的测试,以验证问题是否得到解决,并监控生产环境的表现,确保长期稳定性。
488 1
|
10月前
|
存储 安全 物联网
.NET 跨平台工业物联网网关解决方案
【9月更文挑战第28天】本文介绍了利用 .NET 构建跨平台工业物联网网关的解决方案。通过 .NET Core 和多种通信协议(如 MQTT 和 Modbus),实现工业设备的高效接入和数据采集。系统架构包括设备接入层、数据处理层、通信层、应用层和数据库层,确保数据的准确采集、实时处理和安全传输。此外,还详细阐述了设备身份认证、数据加密及安全审计等机制,确保系统的安全性。该方案适用于不同操作系统和工业环境,具备高度灵活性和扩展性。
207 1
|
10月前
|
数据采集 JSON API
.NET 3.5 中 HttpWebRequest 的核心用法及应用
【9月更文挑战第7天】在.NET 3.5环境下,HttpWebRequest 类是处理HTTP请求的一个核心组件,它封装了HTTP协议的细节,使得开发者可以方便地发送HTTP请求并接收响应。本文将详细介绍HttpWebRequest的核心用法及其实战应用。
397 6