一起谈.NET技术,ASP.NET应用下基于SessionState的“状态编程框架”解决方案

本文涉及的产品
RDS SQL Server Serverless,2-4RCU 50GB 3个月
推荐场景:
云数据库 RDS SQL Server,基础系列 2核4GB
简介:   在一个基于ASP.NET的Web应用程序中,我们通常使用SessionState保存基于某个客户端的状态信息。但是这种单纯使用SessionState的编程方式具有很多局限,比如Session Item的Key值冲突,比如没有一个有效的SessionState清除机制会为Web Server带来内存压力。

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

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

  简单起见,我们并没有在后台运行一个实施后备检测操作的引擎,而是直接通过事件注册的方式让每一个请求自动去触发基于本会话的后备存储,我们注册的事件是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中(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)。两个状态组中又包含各自的状态项,以及对应的后备策略。inactiveTimeout、minimumTotlaBytes和scope分别表示超时时限、序列化后的最下值和使用的范围。

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

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

 
 
public class PageBase : Page
{
public ExtendedRootStateNode State { get ; }
}
public class ExtendedRootStateNode : RootStateNode
{
public string UserName { get ; set ; }
public string Position { get ; set ; }
public ProfileGroupStateNode Profile { get ; private set ; }
public ProductGroupStateNode Product { get ; private set ; }
}
public class ProfileGroupStateNode : GroupStateNode
{
public int Age { get ; set ; }
public Gender Gender { get ; set ; }
public string Address { get ; set ; }
}
public class ProductGroupStateNode : GroupStateNode
{
public string ProductId { get ; set ; }
public string ProductName { get ; set ; }
}

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

相关实践学习
使用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
目录
相关文章
|
29天前
|
存储 Shell Linux
快速上手基于 BaGet 的脚本自动化构建 .net 应用打包
本文介绍了如何使用脚本自动化构建 `.net` 应用的 `nuget` 包并推送到指定服务仓库。首先概述了 `BaGet`——一个开源、轻量级且高性能的 `NuGet` 服务器,支持多种存储后端及配置选项。接着详细描述了 `BaGet` 的安装、配置及使用方法,并提供了 `PowerShell` 和 `Bash` 脚本实例,用于自动化推送 `.nupkg` 文件。最后总结了 `BaGet` 的优势及其在实际部署中的便捷性。
60 10
|
18天前
|
数据采集 JSON API
.NET 3.5 中 HttpWebRequest 的核心用法及应用
【9月更文挑战第7天】在.NET 3.5环境下,HttpWebRequest 类是处理HTTP请求的一个核心组件,它封装了HTTP协议的细节,使得开发者可以方便地发送HTTP请求并接收响应。本文将详细介绍HttpWebRequest的核心用法及其实战应用。
53 6
|
2月前
|
Linux iOS开发 开发者
跨平台开发不再难:.NET Core如何让你的应用在Windows、Linux、macOS上自如游走?
【8月更文挑战第28天】本文提供了一份详尽的.NET跨平台开发指南,涵盖.NET Core简介、环境配置、项目结构、代码编写、依赖管理、构建与测试、部署及容器化等多个方面,帮助开发者掌握关键技术与最佳实践,充分利用.NET Core实现高效、便捷的跨平台应用开发与部署。
67 3
|
2月前
|
缓存 Java API
【揭秘】.NET高手不愿透露的秘密:如何让应用瞬间提速?
【8月更文挑战第28天】本文通过对比的方式,介绍了针对 .NET 应用性能瓶颈的优化方法。以一个存在响应延迟和并发处理不足的 Web API 项目为例,从性能分析入手,探讨了使用结构体减少内存分配、异步编程提高吞吐量、EF Core 惰性加载减少数据库访问以及垃圾回收机制优化等多个方面,帮助开发者全面提升 .NET 应用的性能和稳定性。通过具体示例,展示了如何在不同场景下选择最佳实践,以实现更高效的应用体验。
32 3
|
2月前
|
前端开发 JavaScript 开发工具
跨域联姻:React.NET——.NET应用与React的完美融合,解锁前后端高效协作新姿势。
【8月更文挑战第28天】探索React.NET,这是将热门前端框架React与强大的.NET后端无缝集成的创新方案。React以其组件化和虚拟DOM技术著称,能构建高性能、可维护的用户界面;.NET则擅长企业级应用开发。React.NET作为桥梁,使.NET应用轻松采用React构建前端,并优化开发流程与性能。通过直接托管React组件,.NET应用简化了部署流程,同时支持服务器端渲染(SSR),提升首屏加载速度与SEO优化。
28 1
|
2月前
|
存储 缓存 安全
.NET 在金融行业的应用:高并发交易系统的构建与优化之路
【8月更文挑战第28天】在金融行业,交易系统需具备高并发处理、低延迟及高稳定性和安全性。利用.NET构建此类系统时,可采用异步编程提升并发能力,优化数据库访问以降低延迟,使用缓存减少数据库访问频率,借助分布式事务确保数据一致性,并加强安全性措施。通过综合优化,满足金融行业的严苛要求。
34 1
|
2月前
|
数据库 C# 开发者
WPF开发者必读:揭秘ADO.NET与Entity Framework数据库交互秘籍,轻松实现企业级应用!
【8月更文挑战第31天】在现代软件开发中,WPF 与数据库的交互对于构建企业级应用至关重要。本文介绍了如何利用 ADO.NET 和 Entity Framework 在 WPF 应用中访问和操作数据库。ADO.NET 是 .NET Framework 中用于访问各类数据库(如 SQL Server、MySQL 等)的类库;Entity Framework 则是一种 ORM 框架,支持面向对象的数据操作。文章通过示例展示了如何在 WPF 应用中集成这两种技术,提高开发效率。
41 0
|
2月前
|
开发者 API Windows
从怀旧到革新:看WinForms如何在保持向后兼容性的前提下,借助.NET新平台的力量实现自我进化与应用现代化,让经典桌面应用焕发第二春——我们的WinForms应用转型之路深度剖析
【8月更文挑战第31天】在Windows桌面应用开发中,Windows Forms(WinForms)依然是许多开发者的首选。尽管.NET Framework已演进至.NET 5 及更高版本,WinForms 仍作为核心组件保留,支持现有代码库的同时引入新特性。开发者可将项目迁移至.NET Core,享受性能提升和跨平台能力。迁移时需注意API变更,确保应用平稳过渡。通过自定义样式或第三方控件库,还可增强视觉效果。结合.NET新功能,WinForms 应用不仅能延续既有投资,还能焕发新生。 示例代码展示了如何在.NET Core中创建包含按钮和标签的基本窗口,实现简单的用户交互。
53 0
|
2月前
|
Java Spring 自然语言处理
Spring 框架里竟藏着神秘魔法?国际化与本地化的奇妙之旅等你来揭开谜底!
【8月更文挑战第31天】在软件开发中,国际化(I18N)与本地化(L10N)对于满足不同地区用户需求至关重要。Spring框架提供了强大支持,利用资源文件和`MessageSource`实现多语言文本管理。通过配置日期格式和货币符号,进一步完善本地化功能。合理应用这些特性,可显著提升应用的多地区适应性和用户体验。
32 0
|
2月前
|
开发者 C# Android开发
Xamarin 与 .NET:解锁现代化移动应用开发的超级武器——深入探讨C#与.NET框架如何赋能跨平台应用,实现高效编码与卓越性能
【8月更文挑战第31天】Xamarin 与 .NET 的结合为开发者提供了强大的平台,用于构建现代化移动应用。通过 C# 和 .NET 框架,Xamarin 可以实现一次编写、多平台运行,覆盖 iOS、Android 和 Windows。这种方式不仅节省了开发时间和成本,还保证了应用的一致性和高质量。Xamarin 是一个开源框架,专为跨平台移动应用开发设计,允许使用 C# 语言和 .NET 核心库构建原生应用,并访问各平台特定功能。微软维护的 Xamarin 是 Visual Studio 生态系统的一部分,极大地提高了开发效率。
61 0