`
csstome
  • 浏览: 1467991 次
  • 性别: Icon_minigender_1
  • 来自: 北京
文章分类
社区版块
存档分类
最新评论

Session 有没有必要使用它

 
阅读更多

今天来说说 Session 。这个东西嘛,我想每个Asp.net开发人员都知道它,尤其是初学Asp.net时,肯定也用过它,因为用它保存会话数据确实非常简单。 与前二篇博客不同,这次我不打算细说它的使用,而是打算说说它的缺点,同时我还会举个实际的例子,来看看它到底有什么不好的影响。 当然了,光批评是没有意义,事情也得解决,没有会话也不行,所以,本文将也给出一个自认为能替代Session的解决方案。

Session的来龙去脉

当我们新建一个网站时,VS20XX 生成的网站模板代码中,Session就是打开。是的,如果你没有关闭它,Session其实是一直在工作着。您只需要在Page中用一行代码就能判断您的网站是否在使用Session,

很简单,就是写一下Session,如果代码能运行,不出现异常,就表示您的网站是支持Session的。我们可以去web.config从全局关闭它,

再运行上面的代码,就能看到黄页了。换句话说:当您访问Session时发生以下异常即表示您的网站(或者当前页面)是不支持Session的。

这里要说明一下:如果您在某个页面中访问Session时,出现以上黄页,也有可能是页面级别关闭了Session 。在每个aspx页的Page指令行, 只要我们设置一下EnableSessionState即可,这个属性有3个可选项。我创建了三个页面,分别接受IDE给的默认名称。

对于Default.aspx来说,EnableSessionState这个设置可以不用显式指定,因为它就是默认值。页面的这个参数的默认值也可以在web.config中设置,如:<pages enableSessionState="ReadOnly">
以上三个设置就分别设置了三个不同的Session使用方法。下面我们再来看一下,这个设置对于Session来说,是如何起作用的。

如果您的web.config中有如下设置:

那么,可以在x:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Temporary ASP.NET Files\websiteName\xxxxxx\xxxxxxxx中找到这么三个aspx页面的【编译前版本】:
说明:Asp.net的编译临时目录也可以在web.config中指定,如:<compilation debug="true" tempDirectory="D:\Temp">

或者您也可以编译整个网站,从生成的程序集去看这些类的定义,也能看到以上结果。

也就是说:Page指令中的设置被编译器转成一些接口【标记】,那么,您或许有点好奇,为什么搞这么几个接口,它们在哪里被使用? 下面我们来看看这个问题,当然了,也只能反编译.net framework的代码找线索了。最终发现在Application的PostMapRequestHandler事件中

接着找HttpContext的Handler属性

至此,应该大致搞清楚了,原来这二个接口也只是一个标记。我们可以看一下它们的定义:

完全就是个空接口,仅仅只是为了区分使用Session的方式而已。 可能您会想HttpContext的这二个属性RequiresSessionState, ReadOnlySessionState又是在哪里被使用的。答案就是在SessionStateModule中。SessionStateModule就是实现Session的HttpModule ,它会检查了所有请求,根据HttpContext的这二个属性分别采用不同的处理方式。 大致是如下方法:

这块的代码比较散,为了对这二个参数有个权威的说明,我将直接引用MSDN中的原文。

会话状态由 SessionStateModule 类进行管理,在请求过程中的不同时间,该类调用会话状态存储提供程序在数据存储区中读写会话数据。 请求开始时,SessionStateModule 实例通过调用GetItemExclusive 方法或 GetItem 方法(如果 EnableSessionState 页属性已设置为 ReadOnly) 从数据源检索数据。请求结束时,如果修改了会话状态值,则 SessionStateModule 实例调用 SessionStateStoreProviderBase.SetAndReleaseItemExclusive 方法将更新的值写入会话状态存储区。

上面的说法提到了锁定,既然有锁定,就会影响并发。我们再看看MSDN中关于并发的解释。

对 ASP.NET 会话状态的访问专属于每个会话,这意味着如果两个不同的用户同时发送请求,则会同时授予对每个单独会话的访问。 但是,如果这两个并发请求是针对同一会话的(通过使用相同的 SessionID 值),则第一个请求将获得对会话信息的独占访问权。 第二个请求将只在第一个请求完成之后执行。(如果由于第一个请求超过了锁定超时时间而导致对会话信息的独占锁定被释放, 则第二个会话也可获得访问权。)如果将 @ Page 指令中的 EnableSessionState 值设置为 ReadOnly, 则对只读会话信息的请求不会导致对会话数据的独占锁定。但是,对会话数据的只读请求可能仍需等到解除由会话数据的读写请求设置的锁定。

ASP.NET 应用程序是多线程的,因此可支持对多个并发请求的响应。多个并发请求可能会试图访问同一会话信息。 假设有这样一种情况,框架集中的多个框架全部引用同一应用程序中的 ASP.NET 网页。 框架集中每个框架的独立请求可以在 Web 服务器的不同线程上并发执行。如果每个框架的 ASP.NET 页都访问会话状态变量, 则可能会有多个线程并发访问会话存储区。为避免会话存储区中的数据冲突和意外的会话状态行为,SessionStateModule 和 SessionStateStoreProviderBase 类提供了一种功能,能在执行 ASP.NET 页期间以独占方式锁定特定会话的会话存储项。请注意,如果 EnableSessionState 属性标记为 ReadOnly,则不会对会话存储项设置锁定。 但是,同一应用程序中的其他 ASP.NET 页也许可以写入会话存储区,因此对存储区中只读会话数据的请求可能仍然必须等待锁定数据被释放。

在对 GetItemExclusive 方法的调用中,请求开始时即对会话存储数据设置锁定。请求完成后,在调用 SetAndReleaseItemExclusive 方法期间释放锁定。

如果 SessionStateModule 实例在调用 GetItemExclusive 或 GetItem 方法过程中遇到锁定的会话数据, 则该实例每隔半秒重新请求一次该会话数据,直到锁定被释放或 ExecutionTimeout 属性中指定的时间已经过去。 如果请求超时,SessionStateModule 将调用 ReleaseItemExclusive 方法来释放会话存储数据,然后立即请求该会话存储数据。

为当前响应调用 SetAndReleaseItemExclusive 方法之前,锁定的会话存储数据可能已经在单独的线程上由对 ReleaseItemExclusive 方法的调用释放。 这可能导致 SessionStateModule 实例设置和释放已经由其他会话释放和修改的会话状态存储数据。 为避免这种情况,SessionStateModule 为每个请求都提供一个锁定标识符,以便修改锁定的会话存储数据。 仅当数据存储区中的锁定标识符与 SessionStateModule 提供的锁定标识符匹配时,会话存储数据才能修改。

在权威文字面前,我再解释就显得是多余的。不过,通过我上面的代码分析及MSDN解释,我们可以明白三点:

1. 它说明了,为什么在Application的一系列事件中,PostMapRequestHandler事件要早于AcquireRequestState事件的原因。 因为SessionStateModule要访问HttpContext.RequiresSessionState,但是这个属性又要等到给HttpContext.Handler赋值后才能获取到, 而HttpContext.Handler的赋值操作是在PostMapRequestHandler事件中完成的,有意思吧。

2. 如果你没有关闭Session,SessionStateModule就一直在工作中,尤其是全采用默认设置时,会对每个请求执行一系列的调用。

3. 使用Session时,尤其是采用默认设置时,会影响并发访问。

Session对并发访问的影响

如果您觉得前面的文字可能不是太好理解,没关系,我特意做了几个实验页面,请继续往下看。

第一个页面,主要HTML部分:

第一个页面,后台代码部分:

第二个页面,主要HTML部分(无后台代码):

第三个页面,主要HTML部分(无后台代码):

现在轮到主框架页面上场了,主要HTML部分

主框架页面,后台代码部分:

以上代码实在太简单,我也不多说了。现在来看一下页面显示效果吧。首先看到的是这个样子:

5秒后,所有子框架的页面才会全部加载完成。

上面的示例代码写得很清楚,只有default1.aspx才会执行5秒,后面2个页面没有任何延迟,应该会直接显示的。 但从结果可以看出:第一个页面请求阻塞了后面的所有页面请求!!

其实同样的场景还会发生在Ajax比较密集的网站中,这类网站中,一个页面也有可能发出多个请求,而且是在【上一个请求还没完成前】 就发出了下一个请求,此时的请求过程其实与上面的子框架是一样的。有人可能想问:我的网站就没关Session,Ajax的使用也很多,为什么就没有这种感觉呢? 其实,前面也说了:这里的并发影响只限于同一个用户的多次请求,而且如果服务器响应比较快时, 我们通常也是不能察觉的,但它却实也是会阻塞后面的请求。

我们感觉不到Session的阻塞,是因为阻塞的时间不够长,而我的测试用例故意则让这种现象更明显了。不管你们信不信,反正我是信了。

对于并发问题,我想谈谈我的想法:微软在Session中,使用了锁定的设计,虽然会影响并发,但是,设计本身是安全的、周密的。 因为确实有可能存在一个用户的多个请求中会有修改与读取的冲突操作。微软是做平台的,他们不得不考虑这个问题。 但现实中,这种冲突的可能性应该是很小的,或者是我们能控制的,在此情况下,会显得这个问题是不可接受的。

Session的缺点总结

任何事情都有二面性,优缺点都是兼有的。在评价一个事物时,我们应该要全面地分析它的优缺点,否则评价也就失去了意义。 今天我们还是在批评Session的缺点前,先看看它的优点:只需要一行代码就可以方便的维持用户的会话数据。这其实是个伟大的实现!

但是,现在为什么还是有人会不使用它呢?比如我就不用它,除非做点小演示,否则我肯定不会使用它。为什么?

我个人认为这个伟大的实现,还是有些局限制性,或者说是一些缺点吧。现在我们再来看看Session的缺点:
1. 当mode="InProc"时,也就是默认设置时,容易丢失数据,为什么?因为网站会因为各种原因重启。
2. 当mode="InProc"时,Session保存的东西越多,就越占用服务器内存,对于用户在线人数较多的网站,服务器的内存压力会比较大。
3. 当mode="InProc"时,程序的扩展性会受到影响,原因很简单:服务器的内存不能在多台服务器间共享。
4. 虽然Session可以支持扩展性,也就是设置mode="SQLServer"或者mode="StateServer",但这种方式下,还是有缺点: 在每次请求时,也不管你用不用会话数据,都为你准备好,这其实是浪费资源的。
5. 如果你没有关闭Session,SessionStateModule就一直在工作中,尤其是全采用默认设置时,会对每个请求执行一系列的调用。浪费资源。
6. 并发问题,前面有解释,也有示例。
7. 当你使用无 Cookie 会话时,为了安全,Session默认会使用 重新生成已过期的会话标识符 的策略, 此时,如果通过使用 HTTP POST 方法发起已使用已过期会话 ID 发起的请求, 将丢失发送的所有数据。这是因为 ASP.NET 会执行重定向,以确保浏览器在 URL 中具有新的会话标识符。

不可否认的是,或许有些人认为这些缺点是可以接受的,他们更看中Session的简单、易使用的优点,那么,Session仍然是完美的。

不使用Session的替代方法

对于前面我列出的Session的一些缺点,如果您认为你有些是不能接受的,那么,可以参考一下我提出的替代解决方法。

1. 如果需要在一个页面的前后调用过程中维持一些简单的数据,可以使用<input type="hidden" />元素来保存这些数据。

2. 您希望在整个网站都能共享一些会话数据,就像mode="InProc"那样。此时,我们可以使用Cookie与Cache相结合做法, 自行控制会话数据的保存与加载。具体做法也简单:为请求分配置一个Key(有就忽略),然后用这个Key去访问Cache, 以完成保存与加载的逻辑。如果要使用的会话数据数量不止一个,可以自定义一个类型或者使用一个诸如Dictionary, HashTable这样的集合来保存它们。很简单吧,基本上这种方式就是与mode="InProc"差不多了。只是没有锁定问题,因此也就没有并发问题。

3. 如果您想实现mode="StateServer"类似的效果,那么可以考虑使用memcached这类技术,或者自己写个简单的服务, 在内部使用一个或者多个Dictionary, HashTable来保存数据即可。这样我们可以更精确的控制读写时机。 这种方法也需要使用Cookie保存会话ID。

4. 如果您想实现mode="SQLServer"类似的效果,那么可以考虑使用mongodb这类技术,同样我们可以更精确的控制读写时机。 这种方法也需要使用Cookie保存会话ID。 如果您没用使用过mongodb,可以参考博文:MongoDB实战开发 【零基础学习,附完整Asp.net示例】

从前面三种替代方法来看,如果不使用Session,那么Cookie就是必需的。其实Cookie本身就是设计用来维持会话状态的。 只是它不适合保存过大的数据而已,因此,用它保存会话ID这样的数据,可以说是很恰当的。事实上,Session就是这样做的。

推荐方法:为了保持网站程序有较好的扩展性,且不需要保存过大的会话数据,那么,直接使用Cookie将是最好的选择。
由于Cookie保存在浏览器,且不安全,所以建议只保存诸如:id, key 之类的简单数据,需要其它的会话数据时再根据这些id, Key去获取。

到这里,我想我可以回答标题中的问题了:Session,其实是没有必要使用的,不用它,也能容易地实现会话数据的保存。

Asp.net MVC 中的Session

我们再来看一下Asp.net MVC中是如何使用Session的。Asp.net平台作为底层的框架,它提供了HttpContext.Session这个成员属性 让我们可以方便地使用Session,但是在MVC中,Controller抽象类为也提供了这样一个属性,我们只要访问它就可以了(支持更好的测试性)。

回想一下,前面我们看到SessionStateModule是根据当前HttpHandler来决定是不是启用Session。但是现在Controller和Page是分开的,Controller又是如何使用Session的呢?要回答这个问题就要扯到路由了,简单地说:现在在MVC处理请求的时候,当前HttpHandler是MvcHandler类的实例,它有如下定义:

public class MvcHandler : IHttpAsyncHandler, IHttpHandler, IRequiresSessionState {

因此,在Controller.Session中,它是访问的HttpContext.Session,而MvcHandler实现了IRequiresSessionState接口,所以, 访问HttpContext.Session就可以获取到Session 。 注意哦,我上面的代码取自MVC 2.0,从类型实现的接口可以看出,Session将一直有效,不能关闭,而且属于影响并发的那种模式。 所以,此时你只能从web.config中全局关闭它。
说明,在MVC 3.0 和Asp.net 4.0中,才可以支持Controller订制Session的访问性。

在这种使用方式下,如果您不想继续使用Session,可以使用上面我列出的替代方法。

在MVC中,还有一个地方也在使用Session,那就是Controller.TempData这个成员属性。通常我们可能会这样使用它:

在这种地方,这些保存到TempData的数据其实也是存放在Session中的。你可以从web.config中关闭Session,你就能看到异常了。 对于这种使用方法,你仍然可以前面的替代方法,但是,还有另一种方法也能做为替代Session的方法。 我们看一下Controller的一段代码:

TempData就是通过这种Provider的方式来支持其它的保存途径。而且在MvcFutures中,还有一个CookieTempDataProvider类可供使用。 使用也很简单,获取MVC源码,编译项目MvcFutures,然后引用它,重写以上虚方法就可以了:

注意哦,这里有2个陷阱:MVC 2的MvcFutures的CookieTempDataProvider并不能正常工作。至于我在尝试时,发现它是这样写的(注释部分是我加的):

就算能运行,这样做会导致生成的Cookie的长度较大,因此容易导致浏览器不支持。最终我重写了以上代码(以及另一个序列化的代码):

上面的方法虽然解决了序列化结果过长的问题,但它也引入了新的问题:由于使用IDictionary<string, object>类型,造成复杂类型在序列化时就丢失了它们的类型信息, 因此,在反序列化时,就不能还原正原的类型。也正是因为此原因,这种方法将只适合保存简单基元类型数据。

现有的代码怎么办?

本来,这篇博客到这里就没有了。是啊,批也批过了,解决办法也给了,还有什么好说的,不过,突然想到一个很现实的问题, 要是有人问我:Fish,我的代码很多的地方在使用Session,如果按你前面的方法,虽可行,但是要改动的代码比较多,而且需要测试, 还要重新部署,这个工作量太大了,有没有更好的办法?

是啊,这个还真是个现实的问题。怎么办呢?

针对这个问题,我也认真的思考过,也回忆过曾经如何使用Session,以及用Session都做过些什么。 一般说来,用Session基本上也就是保存一些与用户相关的临时信息,而且不同的页面使用的Session冲突的可能性也是极小的, 使用方式以 mode="InProc" 为主。其实也就是Cache,只是方便了与“当前用户”的关联而已。

于是针对这个前提,继续想:现在要克服的最大障碍是并发的锁定问题。至于这个问题嘛,我们可以参考一下前面MSND中的说明, 就是因为GetItemExclusive这些方法搞出来的嘛。想到这里,似乎办法也就有了:我也来实现一个使用Cache的Provider, 并且在具体实现时,故意不搞锁定,不就行了嘛。

最终,我提供二个Provider,它们都是去掉了锁定相关的操作, 试了一下,并发问题不存了。但有个问题需要说明一下,ProcCacheSessionStateStore采用Cache保存Session的内容,与 mode="InProc" 类似,CookieSessionStateStore则采用Cookie保存Session对象,但它有个限制,只适合保存简单基元类型数据(且不包含敏感信息),原因与CookieTempDataProvider一样。 所以,请根据您的使用场景来选择合适的Provider

以下是使用方法:很简单,只要在web.config中加一段以下配置就好了:

好了,这次不用改代码了,在部署环境中,也只需要修改了一下配置就完事了。

警告:我提供的这二个Provider只是做了简单的测试,并没经过实际的项目检验,如果您需要使用,请自行测试它的可用性。

点击此处下载本文的全部示例代码

分享到:
评论

相关推荐

    php-session类.zip

    一个完整的 Session 类,整合了 Session 最基本的属性值,其中,打开,关闭与清理是符合php编程规范的,这也是一个很好的...小小的说明一下,如果网站不是大量使用 Session 类,基本上就没必要使用 SESSION 类了。

    PHP session有效期session.gc_maxlifetime

    一个已知管用的方法是,使用session_set_save_handler,接管所有的session管理工作,一般是把session信息存储到数据库,这样可以通过SQL语句来删除所有过期的session,精确地控制session的有效期。这也是基于PHP的...

    CodeIgniter配置之SESSION用法实例分析

    刚使用Codeigniter时也被其中的SESSION迷惑过,后来就再也没用过CI自带的SESSION,想必还是有必要整理一下SESSION。为弄清CI中的SESSION,先来说一下PHP中SESSION是如何工作的。由于HTTP协议本身是无状态的,所以当...

    Weblogic 10.3集群配置指南与Session持久化

    2.3. 配置集群应用的必要条件 6 3. Weblogic集群的安装与配置 6 3.1. Weblogic软件安装 6 3.2. Weblogic集群配置规划 6 3.3. Weblogic集群配置 7 3.3.1. 创建新的Domain 7 3.3.2. 选择安装域源 8 3.3.3. 指定域名和...

    PHP session有效期问题

    一个已知管用的方法是,使用session_set_save_handler,接管所有的session管理工作,一般是把session信息存储到数据库,这样可以通过SQL语句来删除所有过期的session,精确地控制session的有效期。这也是基于PHP的...

    .net core 3.1 WepApi 前后分离身份验证及webapi调试demo ,jwt+swagger

    如果把session中的认证信息都保存在JWT中,在服务端就没有session存在的必要了。当服务端水平扩展的时候,就不用处理session复制(session replication)/ session黏连(sticky session)或是引入外部session存储了...

    VNC Session Manager-开源

    VNC会话管理器在VNC的两种执行模式之间添加了必要的粘合代码,从而使基于xdm的登录能够连接到断开的会话。 即,它为基于* nix的vnc服务器提供“终端服务”(如登录名)。

    Apache-Shiro-中文参考文档.docx

    安全有时候是很复杂的,甚至是痛苦的,但它没有必要这样。框架应该尽可能掩盖复杂的地方,露出一个干净而直观的 API,来简化开发人员在使他们的应用程序安全上的努力。 以下是你可以用 Apache Shiro 所做的事情: ...

    Apache_Shiro

    安全有时候是很复杂的,甚至是痛苦的,但它没有必要这样。框架应 该尽可能掩盖复杂的地方,露出一个干净而直观的API,来简化开发人员在使他们的应用程序安全上的努力。 以下是你可以用Apache Shiro 所做的事情: . ...

    aws-session:具有MFA支持的AWS CLI的交互式Shell会话

    在受支持的shell( bash , zsh )中,它还增强了shell提示以显示临时证书的剩余寿命,并使用shell函数重载aws命令,该shell函数将在必要时首先刷新证书,然后再委派给实际的AWS CLI。 还可以通过外壳函数aws-...

    PHP中使用Session配合Javascript实现文件上传进度条功能

    当需要上传的文件比较大的时候,提供一个显示上传进度的进度条就很有必要了。 在PHP 5.4以前,实现这样的进度条并不容易,主要有三种方法: 1.使用Flash, Java, ActiveX 2.使用PHP的APC扩展 3.使用HTML5的File API 第...

    jwt简单的介绍和了解

    2、JWT可以在自身存储一些其他业务逻辑所必要的非敏感信息。 3、便于传输,jwt的构成非常简单,字节占用很小,所以它是非常便于传输的。它不需要在服务端保存会话信息, 所以它易于应用的扩展 JWT(json web ...

    Apache_Shiro参考手册中文版.zip

    安全有时候是很复杂的,甚至是痛苦的,但它没有必要这样。框架应 该尽可能掩盖复杂的地方,露出一个干净而直观的 API,来简化开发人员在使他们的应用程序安全上的努力。 以下是你可以用 Apache Shiro 所做的事情: ...

    mean-session3

    #MEAN第三场##Assignment 将必要的 Angular 代码添加到 Pirate Portfolio(ng-app、ng-init、ng-model),以使现有输入字段的行为与我们在课堂上处理的示例 angular js 文件中一样。 请务必下载 version3 - 最新的 ...

    Apache Shiro中文开发文档.pdf

    安全有时候是很复杂的,甚至是痛苦的,但它没有必要这样。框架应 该尽可能掩盖复杂的地方,露出一个干净而直观的 API,来简化开发人员在使他们的应用程序安全上的努力。 以下是你可以用 Apache Shiro 所做的事情...

    毕业设计:医院在线预约挂号管理系统

    该系统使用JavaEE景点三层架构来...权限模块需要使用RBAC权限模式设计数据表,必要时要用SpringMVC框架的Filter过滤器来过滤权限,为了保证菜单的加载速度,将菜单写入Session. 内含项目完整代码,数据库及配套报告。

    毕业设计:ASP网上购书管理系统(源代码)

    变量被声明后不是在任何地方都可以被使用,每个变量都有它的作用域,作用域是指程序中那些代码能引用变量。过程内部声明的变量称为过程过程级变量或局部变量,这样的变量只有在声明它们的过程中才能使用,即无法在...

    Web开发+java+ssh框架

    二、Hibernate是一个和JDBC密切关联的框架,所以Hibernate的兼容性和JDBC驱动,和数据库都有一定的关系,但是和使用它的Java程序,和App Server没有任何关系,也不存在 兼容性问题。 三、Hibernate不能用来直接和...

    聊天室实现私聊第1/4页

    那么聊天室支持悄悄话功能将是什分有必要的了,实现悄悄话的方法很多,现在小虎介绍一下利用session对象来实现它,用session对象来实现悄悄话,是有优缺点的,因为session对象的应用,其实就是客户端cookies的使用。...

    nginx-encrypted-session-auth:使用HTTP POST形式的仅用于Nginx身份验证的示例配置

    它涵盖了登录/注销/失败的登录以及每个用户的目录。先决条件: Nginx的nginx模块: 当前不包含)这个怎么运作: auth_request用于对另一台服务器进行身份验证(该服务器本身将auth_basic用于演示) 为auth_request...

Global site tag (gtag.js) - Google Analytics