在ASP.NET会话中存储任何内容会导致500毫秒延迟

ben*_*ree 34 asp.net-mvc session webforms httpsession

问题:

在ASP.NET站点中使用会话时,几乎在同一时间加载多个请求时会导致严重的延迟(500毫秒的倍数).

更具体地说,我的问题

我们的网站专门为其SessionId使用会话.我们使用此密钥来查找具有用户信息等的db表.这似乎是一个很好的设计,因为它存储了最少的会话数据.不幸的是,如果你在会话中没有任何东西,SessionId会改变,所以我们存储Session["KeepId"] = 1;.这足以说服SessionId不要改变.

在一个看似无关的说明中,该网站通过控制器操作提供定制的产品图像.如果需要,此操作会生成图像,然后重定向到缓存的图像.这意味着一个网页可能包含图像或其他资源,通过ASP.NET管道发送数十个请求.

无论出于何种原因,当您(a)同时发送多个请求并且(b)以任何方式涉及会话时,您将最终获得大约1/2时间的随机500ms延迟.在某些情况下,这些延迟将是1000毫秒或更长,总是以~500毫秒的间隔增加.更多请求意味着更长的等待时间 对于某些图像,我们包含数十张图像的页面可能需要等待10秒以上.

如何重现问题:

  1. 创建一个空的ASP.NET MVC Web应用程序
  2. 创建一个空的控制器动作:

    public class HomeController : Controller
    {
      public ActionResult Test()
      {
        return new EmptyResult();
      }
    }
    
    Run Code Online (Sandbox Code Playgroud)
  3. 创建一个test.html页面,其中包含一些符合该操作的img标记:

    <img src="Home/Test?1" />
    <img src="Home/Test?2" />
    <img src="Home/Test?3" />
    <img src="Home/Test?4" />
    
    Run Code Online (Sandbox Code Playgroud)
  4. 运行页面并观察firebug中的快速加载时间:

    每张图片加载速度都相当快

  5. 执行以下操作之一(两者都具有相同的结果):

  6. 再次运行该页面并注意响应中的偶然/随机延迟.

    有些请求会遇到长时间延迟

我所知道的

我的问题

我如何使用会话但避免这些延迟?有谁知道修复?

如果没有修复,我想我们将不得不绕过会话并直接使用cookie(会话使用cookie).

ben*_*ree 21

正如Gats所提到的,问题是ASP.NET会锁定会话,因此同一会话的每个请求必须以串行方式运行.

令人讨厌的部分是如果我连续运行所有5个请求(从示例中),它需要大约40ms.使用ASP.NET锁定它超过1000毫秒.似乎ASP.NET说,"如果会话正在使用,那么睡眠500毫秒再试一次."

如果您使用StateServer或SqlServer而不是InProc,它将无济于事 - ASP.NET仍然会锁定会话.

有几个不同的修复程序.我们最终使用了第一个.

请改用Cookie

Cookie会在每个请求的标头中发送,因此您应该保持清晰并避免敏感信息.话虽这么说,会话默认使用cookie来记住谁是谁通过存储ASPNET_SessionId字符串.我所需要的就是id,所以没有理由忍受ASP.NET的会话锁定,因为它只是一个包含在cookie中的id的包装器.

所以我们完全避免会话并在cookie中存储guid.Cookie不会锁定,因此延迟是固定的.

使用MVC属性

您可以使用会话,但通过将会话设置为只读来避免对某些请求的锁定.

对于MVC 3应用程序,在控制器上使用此属性可将会话设置为只读(它不适用于特定操作):

[SessionState(SessionStateBehavior.ReadOnly)]
Run Code Online (Sandbox Code Playgroud)

禁用某些路由的会话

您也可以通过MVC路由禁用会话,但它有点复杂.

禁用特定页面上的会话

对于WebForms,您可以禁用某些aspx页面的会话.


tig*_*rou 11

我看了一下ASP.NET框架.管理会话状态的类在此处定义:http://referencesource.microsoft.com/#System.Web/State/SessionStateModule.cs,114

以下是它的工作原理(简化代码):

LOCKED_ITEM_POLLING_INTERVAL = 500ms;
LOCKED_ITEM_POLLING_DELTA = 250ms;

bool GetSessionStateItem() {
    item = sessionStore.GetItem(out locked);

    if (item == null && locked) {
        PollLockedSession();
        return false;
    }
    return true;
}

void PollLockedSession() {
    if (timer == null) {
        timer = CreateTimer(PollLockedSessionCallback, LOCKED_ITEM_POLLING_INTERVAL);
    }
}

void PollLockedSessionCallback() {
    if (DateTime.UtcNow - lastPollCompleted >= LOCKED_ITEM_POLLING_DELTA) {             
          isCompleted = GetSessionStateItem();
          lastPollCompleted = DateTime.UtcNow;

          if(isCompleted) {
              ResetPollTimer();
          }
    } 
}
Run Code Online (Sandbox Code Playgroud)

摘要:如果由于另一个线程锁定了会话项而无法检索该会话项,则会创建一个计时器.它将定期池化会话以尝试再次获取项目(默认情况下每500毫秒).成功检索项目后,将清除计时器.此外,还要检查以确保GetSessionStateItem()调用之间存在给定的延迟(LOCKED_ITEM_POLLING_DELTA默认情况下= 250 ms).


可以LOCKED_ITEM_POLLING_INTERVAL通过在注册表中创建以下密钥来更改默认值(这将影响在计算机上运行的所有网站):

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\ASP.NET
SessionStateLockedItemPollInterval (this is a DWORD)
Run Code Online (Sandbox Code Playgroud)

另一种方式(这是一种黑客攻击)是通过反射来改变值:

Type type = typeof(SessionStateModule);
FieldInfo fieldInfo = type.GetField("LOCKED_ITEM_POLLING_INTERVAL",
   BindingFlags.NonPublic | BindingFlags.Static);
fieldInfo.SetValue(null, 100); //100ms
Run Code Online (Sandbox Code Playgroud)

免责声明:降低此值的确切后果尚不清楚.它可能会增加服务器上的线程争用,创建潜在的死锁等...更好的解决方案是避免使用会话状态或用SessionStateBehavior.ReadOnly其他用户建议的属性装饰您的控制器.


归档时间:

查看次数:

7597 次

最近记录:

9 年,11 月 前