ASP.NET Session会导致的性能问题

而且还是阵发性的,有某个用户投诉慢的时候,你访问却很快,本地又复现不出来?今天你访问了博客园真是太幸运了,也许这里就有你想要的一个答案——假如你的站点动态处理了图片或者其他原本不会动态处理的非网页资源的话。没想到吧,这是Session惹的祸!
我们公司某个站点访问量一直很大,几年前就已经发现客户经常投诉说很慢,而这个问题我们一直都在排查和解决。从线路问题,到数据库连接泄露,到数据库死锁、索引,以及程序的优化问题,我们都查找过,并且解决过。然而,这个问题总还是存在。比如说,在IIS日志中,就会经常看到time-taken大于10秒钟的访问。而最近我们的KPI要求解决这个问题,不得已,再次踏上这趟艰难的排查之旅。

之所以说艰难,是因为这个现象在本地几乎无法复现,一个是这个现象是阵发性的,第二个是本地访问量或者数据量不够大。我们也尝试过在本地加载线上服务器的数据库,然后拿服务器的IIS的Log文件重播,结果确实是偶尔发现IIS日志中的time-taken也会比较长,但是本地服务器性能不如线上服务器的,比如说IO就差很远,于是总说不清楚到底是什么问题。而线上服务器也不方便用做Dump的形式查找:第一是因为线上一做Dump,站点就会在几分钟内失去响应,这样我们客服的电话就会被打爆;另一个原因还是阵发性的问题,我们不可能一直盯着性能看,然后在发生问题的瞬间做Dump。再说了,看性能也看不出来,因为那些响应时间很长的访问,其前后附近的响应都特别的快。不可避免的,我们只能够在代码里面塞入一些调试和日志的代码,以期望能够得到一些有用的信息。

最开始的时候,我们认为最可能的原因是数据库阻塞,比如正好某一个访问锁住某个数据库对象(比如一张表或者某几行),如果这个时候访问特定的页面,可能就会比较慢。于是在数据库层塞入了一堆代码,这些代码会在数据库连接数量多,或者某个数据库连接时间特别长的时候,将所有数据库连接的当前访问语句、连接持续总时间,以及这些连接当前的堆栈情况都输出到文件中。与此同时,我们还打开Sql profiler对这个数据库进行监视,将持续时间超过1秒钟的都记录下来。然而跑了一天之后,却发现什么都没有被记录下来,但IIS日志中仍然在这段时间内出现许多10秒以上的访问。这个时候,我们就意识到,可能方向错了。

接着,我们就开始塞另外一段代码:一个IHttpModule。这个模块就只是截获每一个页面访问的完整生命周期中的所有事件,比如BeginRequest、PreAuthenticateRequest等。同时在BeginRequest开始的时候设置一个定时器,每隔一秒钟就会出发一个事件,来记录本次访问过程中,本模块收集到的所有线程中当前的堆栈情况。最后,在EndRequest的时候,如果访问时间超过5秒,就将之前截获的所有信息输出到文件中。这一下子问题就变得非常的清晰了,比如说,下面这种输出(片断)就是非常典型的一个情况:

#Steps:
#Fields: date time threadId stepName
#---------------------------------------------------
2009-07-09 16:48:01.752 0024 BeginRequest
2009-07-09 16:48:01.752 0024 AuthenticateRequest
2009-07-09 16:48:01.752 0024 PostAuthenticateRequest
2009-07-09 16:48:01.752 0024 AuthorizeRequest
2009-07-09 16:48:01.752 0024 PostAuthorizeRequest
2009-07-09 16:48:01.752 0024 ResolveRequestCache
2009-07-09 16:48:01.752 0024 PostResolveRequestCache
2009-07-09 16:48:01.752 0024 PostMapRequestHandler
2009-07-09 16:48:06.284 0007 AcquireRequestState
2009-07-09 16:48:06.284 0007 PostAcquireRequestState
2009-07-09 16:48:06.284 0007 PreRequestHandlerExecute
2009-07-09 16:48:06.284 0007 EndRequest
#---------------------------------------------------
#End of steps.

       也就是说,在PostMapRequestHandler之后,AcquireRequestState之前,有一大段的等待时间。而这个报告后面的堆栈情况,则显得很奇怪:线程7从第一次截快照的时候开始,一直到结束都是空白的!甚至在其他的一些报告中甚至会发现这个线程已经被分派处理其他页面的工作了(堆栈中的调用很明显指出是另一个aspx页面的代码)。结果一Google“PostMapRequestHandler”和“AcquireRequestState”这两个关键字,就找到了国外另一个有类似遭遇的受害者:
http://forums.iis.net/t/1147300.aspx

这个楼主是一个aspx页面里面有好几个iframe,并且每一个iframe里面都是访问同一个Web应用下的Aspx页面。现象就是iframe内的页面会一个个蹦出来,尤其是这些页面都比较慢的时候。和我这里截获到的情况一样,也是PostMapRequestHandler事件到AcquireRequestState之间有很长的空白时间。顺藤摸瓜,又找到了这么一个帖子:
http://www.microsoft.com/communities/newsgroups/en-us/default.aspx?dg=microsoft.public.dotnet.framework.aspnet&mid=7f56033f-caac-47c2-bd9c-95512aa14b47

原来,在AcquireRequestState之前,需要等待上一个同SessionId的页面处理完毕之后,才会继续处理当前页面。其根本原因是:同一个SessionId下面的Session对象不应该被同时写入,否则就会全乱套了,原理和多线程竞争是一样的。因此在设计上,同一个SessionId的页面(或者IHttpHandler)就会顺序执行。

好了,到这里真相又一次大白了!(指不定还有别的问题,还会有下一次大白的时候)在我们的系统中,由于特殊的原因,对图片的输出我们会通过Asp.Net来处理,比如说加上一些合适的缓存标记,甚至会动态生成图片输出(令人惊讶的是,这个动态输出的总时间消耗甚至不到100毫秒,所以不要说动态生成是一种不好的实施方式)。但是,由于没有经过特殊处理,Asp.Net底层认为这些访问时需要对Session进行写操作的,于是就在AcquireRequestState之前挂起,等待上一次同SessionId访问结束。可想而知,图片多了就会导致排队,甚至排很长的队。如果在排队结束之前访问下一个页面,正好系统又没有丢掉之前的队列,则这次访问就会变得比较漫长了。那么怎么做呢? 在一次顺藤摸瓜,找到了确切的说法:
http://msdn.microsoft.com/en-us/library/ms178581.aspx  

注意下面的这段话:

Concurrent Requests and Session State
Access to ASP.NET session state is exclusive per session, which means that if two different users make concurrent requests, access to each separate session is granted concurrently. However, if two concurrent requests are made for the same session (by using the same SessionID value), the first request gets exclusive access to the session information. The second request executes only after the first request is finished. (The second session can also get access if the exclusive lock on the information is freed because the first request exceeds the lock time-out.) If theEnableSessionState value in the @ Page directive is set to ReadOnly, a request for the read-only session information does not result in an exclusive lock on the session data. However, read-only requests for session data might still have to wait for a lock set by a read-write request for session data to clear.

       也就是说,对于页面,我们可以简单的在Aspx上的<% @Page %>内设置EnableSessionState=”false”,或者EnableSessionState=”ReadOnly”,就可以减轻这种问题的症状。前一种设置将会禁止对Session的访问,而后一种设置则只能允许只读访问(你不能够对Session进行写操作)。正好,我们刚才提到的IHttpHandler中,对图片进行处理的部分,是不需要对Session进行写操作的,但是却需要读取Session(根据状态不同,而需要获取一些特殊信息),因此可以采取上述的措施来解决问题。

上述参考文献中,并没有说明如何对不是Page的IHttpHandler如何设置,这里我特别说明一下。对于自定义的一个实现了IHttpHandler接口的类,只要同时实现IReadOnlySessionState,即可达到EnableSessionState="ReadOnly"的效果。同时,如果没有实现IRequiresSessionState,则等价于EnableSessionState=”false”的效果。

注意:本文所说的,不是说你在代码里面有没有用到Session,Asp.Net不会扫描你的代码看看有没有访问Session,或者在你第一次访问Session的时候才会加锁。Asp.Net是在AcquireRequestState事件之前就首先申请这个锁——只要你的页面没有设置EnableSessionState=”false”或EnableSessionState=”ReadOnly”,或者你的IHttpHandler实现了IRequiresSessionState却没有实现IReadOnlySessionState接口就会这样。回复中有很多同学说“只要我不用Session不就行了吗?”或者“我从来不用Session”,这么想完全是错误的,只能说你没有理解这背后的机制,甚至可能连Asp.net生命周期都不太清楚。Asp.Net之所以在你的页面代码开始执行之前就锁定,是要保证整个环境的完整性,避免部分执行的情况。当然,也正如上面的一些引用中所提到的,你可以自己写一个SessionProvider而不做任何的锁工作,但这样做肯定有不确定性的风险,到时候只能你自己承担,并且更难复现和调试。

后记: 
       可能有不少人都知道Session是什么,也有很多人知道Asp.net的生命周期都包含哪些,比如一搜索"PostMapRequestHandler AcquireRequestState",就会出来很多中文的页面介绍HttpApplication类都有哪些事件,以及生命周期等等。而说到Session会造成特殊情况下站点性能问题的,似乎中文界我这还真是第一篇。当然了,这个问题可能比较偏,因为一般大家都只会处理Aspx页面而不会处理图片,甚至用Aspx页面来动态输出图片的机会都比较少,英文的似乎也只有文中提到的唯一一个同志提出来了。

不过,有一个很常见的场景会受这个问题的影响,那就是“验证码”。如果验证码生成速度很慢,同时客户端浏览器选择长连接而服务器也接受长连接,则可能会影响在输出验证码完毕之前访问下一个页面的速度。反过来,我们也经常可以体验到,验证码出来的速度总是特别慢,总是“蹦”出来的,尤其是当前页面特别复杂的时候,似乎页面没有加载完就总不会出现验证码。估计我说的问题就是原因之一。

不过验证码的问题还不是特别好解决,因为为了避免信息泄露的问题,验证码的答案通常是存在在Session里面的,而通常的设计也是一访问验证码图片,就会将验证码答案写到Session里面。所以,很不幸,文中提到的那个解决办法是行不通的,至少不是能够直接行得通的。这个问题有没有救药?有,当然有了。怎么救?哈,那就有劳你自己开动脑筋了,我这里算是点到即止了。

(0)

相关推荐

  • ASP.NET页面优化 性能提升8倍的方法

    为了让您对优化的效果有个直观的了解,我准备了下面的测试结果截图: 测试环境: 1. Windows Server 2003 SP2 2. Viaual Studio 2008,使用自带的WebDev.WebServer.EXE运行网站程序. 3. (ThinkPad SL510):Core2 T6670 2.2GHz, 4G内存 二个红框中的数字反映了优化前后的执行时间. 数字表明:优化前后,执行时间有了8倍多的差别. 测试背景 看过了优化结果,再来介绍一下:这个测试到底是在测试什么东西? 现在

  • 分享提高ASP.NET Web应用性能的技巧

    在这篇文章中,将介绍一些提高 ASP.NET Web 应用性能的方法和技巧.众所周知,解决性能问题是一项繁琐的工作,当出现性能问题,每个人都会归咎于编写代码的开发人员. 那性能问题到底该如何解决?以下是应用系统发布前,作为 .NET 开发人员需要检查的点. 1.debug=「false」 当创建 ASP.NET Web应用程序,默认设置为「true」.开发过程中,设置为「true」是非常有用多,但在应用程序发布部署时,需将其设置为「false」. <compilation defaultLang

  • 增加asp.net应用程序性能的20种方法(简单有效)

    1.禁用session假如您用不到session会话跟踪请务必禁用它.您可以在每个asp.net页面中设置如下:<%@ Page language="c#" Codebehind="WebForm1.aspx.cs" AutoEventWireup="false" Inherits="WebApplication1.WebForm1" EnableSessionState="false" %>

  • ASP.NET性能优化小结(ASP.NET&C#)

    ASP.NET: 一.返回多个数据集 检查你的访问数据库的代码,看是否存在着要返回多次的请求.每次往返降低了你的应用程序的每秒能够响应请求的次数.通过在单个数据库请求中返回多个结果集,可以减少与数据库通信的时间,使你的系统具有扩展性,也可以减少数据库服务器响应请求的工作量. 如果用动态的SQL语句来返回多个数据集,那用存储过程来替代动态的SQL语句会更好些.是否把业务逻辑写到存储过程中,这个有点争议.但是我认为,把业务逻辑写到存储过程里面可以限制返回结果集的大小,减小网络数据的流量,在逻辑层也不

  • ASP.NET 提高首页性能的十大做法

    前言 本文是我对ASP.NET页面载入速度提高的一些做法,这些做法分为以下部分: 1.采用 HTTP Module 控制页面的生命周期. 2.自定义Response.Filter得到输出流stream生成动态页面的静态内容(磁盘缓存). 3.页面GZIP压缩. 4.OutputCache 编程方式输出页面缓存. 5.删除页面空白字符串.(类似Google) 6.完全删除ViewState. 7.删除服务器控件生成的垃圾NamingContainer. 8.使用计划任务按时生成页面.(本文不包含该

  • asp.net 程序性能优化的七个方面 (c#(或vb.net)程序改进)

    1.使用值类型的ToString方法 在连接字符串时,经常使用"+"号直接将数字添加到字符串中.这种方法虽然简单,也可以得到正确结果,但是由于涉及到不同的数据类型,数字需要通过装箱操作转化为引用类型才可以添加到字符串中.但是装箱操作对性能影响较大,因为在进行这类处理时,将在托管堆中分配一个新的对象,原有的值复制到新创建的对象中. 使用值类型的ToString方法可以避免装箱操作,从而提高应用程序性能. int num=1; string str="go"+num.T

  • 打造基于jQuery的高性能TreeView(asp.net)

    根据我的项目实践情况,主要是几个关键点: 1:支持静态的树,即一次性将全部数据加载到客户端.2:异步树,即一次只加载一级或若干级节点,子节点可以异步加载数据.3:Checkbox树(可能是静态树也可能是异步树),用于选择(如选择组织机构,选择数据字典项)等,最好是能够支持节点级联(这个是难点)4:能够承载大数据量,并性能表现优异5:能够在主流浏览器中运行良好 那我要打造的TreeView就是为了实现这个5个主要指标的. 先来看下效果图 上图是中国行政区域的数据树,总共得节点是3500+. 那么我

  • ASP.NET 高性能分页代码

    最近给分页快搞死,记得之前曾经发过修改DW ASP分页的方法,后来又写过手工打造的ASP分页,现在进入.NET当然要配合存储过程打造纯手工高性能分页了. 为什么会叫做高性能,为什么要手工打造,不使用.NET现有的分页控件呢?这个还要追溯到我修改DW ASP分页的时候,那个我还不怎么懂程序这个东西,只会修修补补,就更不要去谈什么性能问题.当时改的很心烦,接着叫我的私人技术总监张总帮我看看,当时张总就以一种不屑一顾的眼神往着我,说了句话:值得吗? 接着到我手工打造ASP分页,又搞不下去了,张总丢给我

  • ASP.NET(C#) String, StringBuilder 与 StringWriter性能比较

    直观认识:正面交锋  性能测试1:StringBuilder  第 1 轮测试:用时 312.5 毫秒  第 2 轮测试:用时 421.875 毫秒  第 3 轮测试:用时 453.125 毫秒  第 4 轮测试:用时 421.875 毫秒  第 5 轮测试:用时 453.125 毫秒  性能测试2:StringWriter  第 1 轮测试:用时 406.25 毫秒  第 2 轮测试:用时 453.125 毫秒  第 3 轮测试:用时 421.875 毫秒  第 4 轮测试:用时 437.5 

  • ASP.NET Session会导致的性能问题

    而且还是阵发性的,有某个用户投诉慢的时候,你访问却很快,本地又复现不出来?今天你访问了博客园真是太幸运了,也许这里就有你想要的一个答案--假如你的站点动态处理了图片或者其他原本不会动态处理的非网页资源的话.没想到吧,这是Session惹的祸! 我们公司某个站点访问量一直很大,几年前就已经发现客户经常投诉说很慢,而这个问题我们一直都在排查和解决.从线路问题,到数据库连接泄露,到数据库死锁.索引,以及程序的优化问题,我们都查找过,并且解决过.然而,这个问题总还是存在.比如说,在IIS日志中,就会经常

  • ASP.NET Session使用详解

    Session模型简介 Session是什么呢?简单来说就是服务器给客户端的一个编号.当一台WWW服务器运行时,可能有若干个用户浏览正在运正在这台服务器上的网站.当每个用户首次与这台WWW服务器建立连接时,他就与这个服务器建立了一个Session,同时服务器会自动为其分配一个SessionID,用以标识这个用户的唯一身份.这个SessionID是由WWW服务器随机产生的一个由24个字符组成的字符串,我们会在下面的实验中见到它的实际样子. 这个唯一的SessionID是有很大的实际意义的.当一个用

  • ASP.NET中常用的优化性能的方法

    1. 数据库访问性能优化   数据库的连接和关闭  访问数据库资源需要创建连接.打开连接和关闭连接几个操作.这些过程需要多次与数据库交换信息以通过身份验证,比较耗费服务器资源.ASP.NET中提供了连接池(Connection Pool)改善打开和关闭数据库对性能的影响.系统将用户的数据库连接放在连接池中,需要时取出,关闭时收回连接,等待下一次的连接请求.  连接池的大小是有限的,如果在连接池达到最大限度后仍要求创建连接,必然大大影响性能.因此,在建立数据库连接后只有在真正需要操作时才打开连接,

  • asp.net session丢失的解决方法小结

    现在我就把原因和解决办法写出来. ASP.NET Session丢失原因: 由于Asp.net程序是默认配置,所以Web.Config文件中关于Session的设定如下: < sessionState mode='InProc' stateConnectionString='tcpip=127.0.0.1:42424' sqlConnectionString='data source=127.0.0.1;Trusted_Connection=yes' cookieless='true' time

  • ASP.NET session.timeout设置案例详解

    session.timeout 方法一: asp.net Session的默认时间设置是20分钟,即超过20分钟后,服务器会自动放弃Session信息. 当我们在asp.net程序中打开webconfig的时候,可以看到一段如下的代码:Asp.net程序代码: sessionState节点放在<system.web>节点下,形式如下: configuration 元素(常规设置架构) system.web 元素(ASP.NET 设置架构) sessionState 元素(ASP.NET 设置架

  • asp.net session的使用与过期实例代码

    1.Session是一种Web会话中的常用状态之一. 2.Session提供了一种把信息保存在服务器内存中的方式.他能储存任何数据类型,包含自定义对象. 3.每个客户端的Seesion是独立存储的. 4.在整个会话过程中,只要SessionID的cookie不丢失,都会保存Session信息的. 5.Session不能跨进程访问,只能由该会话的用户访问.应为提取Session数据的id标识是以Cookie的方式保存到访问者浏览器的缓存里的. 6.当会话终止,或过期时,服务器就清除Session对

  • ASP.NET Session的七点认识小结

    ASP.NET Session的七点认识之一 对于值类型的变量,Session中保存的是值类型的拷贝 Session["__test0"] = 1; int i = (int)Session["__test0"]+1; int j = (int)Session["__test0"]; 结果i=2,j=1 ASP.NET Session的七点认识之二 对于引用类新的变量,Session中保存的是引用 CDACommon cda = new CDAC

  • ASP中使用FileSystemObject时提高性能的方法

    按个测试了一下可能会是哪些属性引起的性能问题,在去掉Type也就是类型属性显示的时候,性能有了很大提高,之后的测试也证明了是Type属性的使用导致了性能问题. 仔细想了一下,Type引起性能低的原因应该是,引用Type属性所得到的是文件的具体类型信息,也就是我们在资源浏览器里看到的那样,比如TXT文件的类型就是显示为"文本文档",但是这个类型信息却是存储在系统中,通过文件扩展名进行关联的.在FSO中使用Type属性的时候,对于每个File对象,FSO都需要根据扩展名去系统中检索这个扩展

  • 关于扩展 Laravel 默认 Session 中间件导致的 Session 写入失效问题分析

    最近由于项目开发需要,手机客户端和网页端统一使用一套接口,为保证 会话(Session) 能够正常且在各类情况下兼容,我希望能够改变 SessionID 的获取方式.默认情况下,所有网站都是通过 HTTP 请求的 Header 头部中的 Cookie 实现的,通过 Cookie 中指定的 SessionID 来关联到服务端对应数据,从而实现会话功能. 但对于手机客户端,可能并不会支持原始的 Cookie,亦或者根据平台需要而屏蔽,因此开发中要求通过增加一个请求头 X-Session-Token

  • Java Code Cache满导致应用性能降低问题解决

    目录 0 问题描述 1 排查问题 2 解决问题 3 背景知识 3.1 JIT即时编译 3.2 Code Cache 3.3 分层编译 3.4 Code Cache 满了怎么办 3.5 Code Cache 调优 3.6 问题解决 3.7 运行时查看Code Cache 0 问题描述 一个应用在运行一段时间后,随着访问量不断增加,突然处理能力下降.但是从流量,jstack,gc上看基本正常.感觉好像突然从 "健康状态" 进入了 "虚弱状态". 1 排查问题 在JVM日

随机推荐