BeginProcessRequest()函数发生了什么?(涉及IT技术)

59

我们正在使用NewRelic提供服务端应用追踪。

我们发现一些应用程序在方法System.Web.Mvc.MvcHandler.BeginProcessRequest()中始终花费约100毫秒。

这发生在调用任何自定义控制器代码之前(该代码已分开记录,并非累计时间) - 不清楚为什么会在此方法中耗费如此多的时间。

MVC在这个方法中会做哪些事情?这可能只是请求排队吗?

[编辑:] 正如所猜测的那样 - Scalayer下面的答案很准确。我们去除了所有会话依赖项并进行了优化,看到了应用程序可扩展性和稳定性的大幅提高。


http://blog.stevensanderson.com/blogfiles/2007/ASPNET-MVC-Pipeline/ASP.NET%20MVC%20Pipeline.pdf - Ravi Gadag
@Ravi BeginRequest() 不在那里!:( - Dave Bish
你是否正在使用任何异步处理程序(IHttpAsyncHandler)? - Paul Zahra
1
我的猜测是你正在使用异步控制器。ASP.NET MVC在幕后做了很多事情。 - CoffeeCode
@CoffeeCode 我们在一些地方使用它们,但不是在相关的页面/跟踪上... - Dave Bish
4个回答

87
你可能看到的是.NET中常被称为线程敏捷性的问题。在主题标签下方的结果(即 System.Web.HttpApplication.BeginRequest()),你看到的大多数时间并不一定是正在执行的代码,而是Web上下文等待线程从读写锁中释放回来的时间。 Application_BeginRequest()的“暂停”在ASP.NET Web堆栈中非常普遍。通常情况下,在BeginRequest中看到长时间加载时,你正在处理ASP.NET线程敏捷性和/或线程锁 - 尤其是在处理IO和基于会话的操作时。这并不是什么坏事,这只是.NET确保你的线程保持并发的方式。
时间差通常发生在BeginRequest和PreRequestHandlerExecute之间。如果应用程序将多个内容写入会话,则ASP.NET将对HttpContext.Current.Session发出读写锁。
一个好的方法是检查线程ID以确定是否面临线程敏捷性的问题 - 对于给定的请求,ID将是不同的。
例如,在调试时,你可以添加以下内容到你的Global.asax.cs:
protected void Application_BeginRequest(Object sender, EventArgs e) { 
      Debug.WriteLine("BeginRequest_" + Thread.CurrentThread.ManagedThreadId.ToString()); 
   }

打开调试输出窗口(从Visual Studio: View >> Output,然后从“show output from”下拉菜单中选择“Debug”)。

在调试时,进入你看到长时间等待的页面。然后查看输出日志--如果你看到多个id,则可能会遇到这个问题。

这就是为什么有时候你会看到延迟,而有时候你不会看到延迟,应用程序代码可能会以略微不同的方式使用session,或者从页面到页面的session或IO操作可能会更高或更低。

如果是这种情况,根据站点或每个给定页面上如何使用session,可以采取一些措施来帮助加快速度。

对于不修改session的页面:

   <% @Page EnableSessionState="ReadOnly" %>

对于不使用会话状态的页面:

<% @Page EnableSessionState="False" %>

如果应用程序不使用session(web.config):

<configuration>
    <system.web>
      <sessionState mode="Off" />
    </system.web>
</configuration>

让我们以以下示例为例:

用户加载一个页面,然后在第一个请求完成加载之前决定进入另一个页面,ASP.NET将强制进行会话锁定,导致新页面请求的加载等待直到第一个页面请求完成。使用ASP.NET MVC时,每个操作都会锁定用户会话以进行同步,从而导致相同的问题。

所有花费在解锁的时间都将通过New Relic报告,更不用说那些用户放弃会话并且线程回来寻找不再存在的用户的情况了。

顺便提一下,UpdatePanel控件会导致相同的行为 - http://msdn.microsoft.com/en-us/magazine/cc163413.aspx

可以采取的措施:

这个锁定问题是微软拥有SessionStateUtility类的原因之一 - http://msdn.microsoft.com/en-us/library/system.web.sessionstate.sessionstateutility.aspx

因此,如果您遇到此问题,可以覆盖默认行为,就像在此Redis实现中所看到的那样:https://github.com/angieslist/AL-Redis

.net基础网站使用的默认状态提供程序有许多选项。但是通常知道,这个事务时间表示线程被锁定并等待请求完成。


2
感谢您对这个问题提供如此出色的答案 - 我有强烈的感觉您是正确的,在相关的页面上 - 我们很可能执行大量笨重的会话操作。 - Dave Bish
1
一个好的答案,其中许多内容(以及更多)可以在这个相关问题中找到。 - Co7e
8
我们不使用Session,已将其禁用,但仍然出现此问题。 - Doug
4
我知道这是一个老问题,但我和Doug一样,遇到了同样的问题,无法使用会话! - Mark Milford
很多人都在谈论不使用会话的问题,我也是其中之一。我不确定我的等待时间是否“长”,但它们占整个交易的85%,这对我来说似乎很长。我想知道,对于我们这些不使用会话的人来说,ActionFilters、ViewBag、ViewData或TempData是否与此有关? - Brad
显示剩余4条评论

6

如果您想让某个控制器能够处理单个用户的并行请求,您可以使用一个名为SessionState的属性。此属性自MVC 3版本以来已被引入。要实现并行处理,不必使用无会话控制器,只需使用SessionStateBehavior的Ready-only选项即可通过基于Session数据实现的安全检查。

[SessionState(System.Web.SessionState.SessionStateBehavior.ReadOnly)]
[OutputCache(NoStore = true, Duration = 0, VaryByParam = "*")]
public class ParallelController : Controller
{
    ...
}

在尝试执行一些长时间运行的操作时,我也在NewRelic中看到了System.Web.Mvc.MvcHandler.BeginProcessRequest()存在延迟。这些属性解决了问题,并使该控制器能够并行处理操作。


2

我在一个几乎没有使用session的应用程序中遇到了同样的问题。尽管考虑了已接受的答案,甚至为了诊断目的暂时完全删除了SessionState,但我在这个“方法”中仍然有显着的性能问题。

基于todd_saylor在此线程中的评论,我在自己的网站上进行了大量研究,并发现BeginProcessRequest可以作为New Relic的杂项性能损失的总括,该损失出现在不同的代码区域中。通过使用Red Gate的ANTs Performance Profiler在我的本地机器上对代码进行分析,我能够显著减少这个区域所需的时间。

我的本地分析揭示了一些事情:

  1. 我使用Ninject作为DI容器,这导致对象解析的性能损失。我将其替换为SimpleInjector并将性能提高了一个数量级。
  2. 我发现,在AutoMapper的IQueryable扩展Project().To<>()和Linq的Sql Server提供程序之间,投影的动态编译和JITing占据了50%的请求时间。将其替换为CompiledQuerys又产生了显著的效果。

希望这能帮助其他人!


0

如果你正在遇到IIS高CPU使用率的问题,而且无法解释,请看一下我在NewRelic支持论坛上开启的这个帖子。

我已经处理了这个问题超过一个星期,直到我意识到他们的代理是问题的根本原因。

.NET代理导致IIS高CPU使用率

所以我建议你首先尝试删除.NET代理,并查看是否有任何变化,然后再深入进行更复杂的实验。

我在这个特定的问题上发布这个答案,因为我在处理这个问题时第一个到达这里,而线程敏捷性让我走了一条不正确的长路...(虽然本身非常有趣)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接