在ASP.NET站点中使用会话时,几乎在同一时间加载多个请求时会导致严重延迟(500ms的倍数)。
我们的网站专门为其SessionId使用会话。我们使用此密钥来查找具有用户信息等的db表。这似乎是一个很好的设计,因为它存储了最少的会话数据。不幸的是,如果您在会话中不存储任何内容,SessionId将会更改,因此我们存储Session["KeepId"] = 1;
。这足以convince SessionId to not change。
在一个看似无关的说明中,该网站通过控制器操作提供定制的产品图像。如果需要,此操作会生成图像,然后重定向到缓存的图像。这意味着一个网页可能包含图像或其他资源,通过ASP.NET管道发送数十个请求。
无论出于何种原因,当您(a)同时发送多个请求并且(b)以任何方式涉及会话时,您将以大约1/2的时间结束随机500ms延迟。在某些情况下,这些延迟将是1000毫秒或更长,总是以~500毫秒的间隔增加。更多请求意味着更长的等待时间对于某些图像,我们的页面上有数十张图像可以等待10秒以上。
创建一个空控制器操作:
public class HomeController : Controller
{
public ActionResult Test()
{
return new EmptyResult();
}
}
制作一个test.html页面,其中包含一些符合该操作的img标记:
<img src="Home/Test?1" />
<img src="Home/Test?2" />
<img src="Home/Test?3" />
<img src="Home/Test?4" />
运行页面&amp;观察萤火虫的快速加载时间:
执行以下操作之一(两者都有相同的结果):
向Global.asax.cs
添加一个空的Session_Start处理程序public void Session_Start() { }
在会话
中添加内容public class HomeController : Controller
{
public ActionResult Test()
{
HttpContext.Current.Session["Test"] = 1;
return new EmptyResult();
}
}
再次运行该页面&amp;注意响应中的偶然/随机延迟。
如何使用会话但避免这些延迟?有谁知道修复?
如果没有修复,我想我们必须绕过会话和安排。直接使用cookies(session uses cookies)。
答案 0 :(得分:19)
正如Gats所提到的,问题是ASP.NET会锁定会话,因此同一会话的每个请求都必须以串行方式运行。
令人讨厌的部分是如果我连续运行所有5个请求(从示例中),它需要大约40ms。使用ASP.NET锁定它超过1000毫秒。似乎ASP.NET说,“如果正在使用会话,那么睡眠时间为500毫秒,然后再试一次。”
如果您使用StateServer或SqlServer而不是InProc,它将无济于事 - ASP.NET仍会锁定会话。
有一些不同的修复方法。我们最终使用了第一个。
Cookie会在每个请求的标题中发送,因此您应该将其保持清晰;避免敏感信息。话虽这么说,会话默认使用cookie来记住谁是谁通过存储ASPNET_SessionId字符串。我只需要那个id,所以当它只是一个cookie的id包装器时,没有理由忍受ASP.NET的会话锁定。
因此我们完全避开会话&amp;而是在cookie中存储一个guid。 Cookie不会锁定,因此延迟是固定的。
您可以使用会话,但通过将会话设置为只读来避免对某些请求的锁定。
对于MVC 3应用程序,在控制器上使用此属性使会话成为只读(它不适用于特定操作):
[SessionState(SessionStateBehavior.ReadOnly)]
你也可以disable session through MVC routing,但这有点复杂。
对于WebForms,您可以disable session for certain aspx pages。
答案 1 :(得分:9)
我看了一下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();
}
}
}
摘要:如果无法检索会话项,因为它被另一个线程锁定,则会创建一个计时器。它将定期池化会话以尝试再次获取项目(默认情况下每500毫秒)。成功检索项目后,将清除计时器。此外,还要检查以确保GetSessionStateItem()调用之间存在给定的延迟(默认情况下LOCKED_ITEM_POLLING_DELTA
= 250毫秒)。
可以通过在注册表中创建以下密钥来更改LOCKED_ITEM_POLLING_INTERVAL
的默认值(这将影响计算机上运行的所有网站):
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\ASP.NET
SessionStateLockedItemPollInterval (this is a DWORD)
另一种方式(黑客)是通过反射来改变价值:
Type type = typeof(SessionStateModule);
FieldInfo fieldInfo = type.GetField("LOCKED_ITEM_POLLING_INTERVAL",
BindingFlags.NonPublic | BindingFlags.Static);
fieldInfo.SetValue(null, 100); //100ms
免责声明:降低此值的确切后果尚不清楚。它可能会增加服务器上的线程争用,创建潜在的死锁等...更好的解决方案是避免使用会话状态或使用SessionStateBehavior.ReadOnly
属性装饰您的控制器,如其他用户建议的那样。
答案 2 :(得分:2)
有几种方法可以加速ASP.NET中的会话。首先是几个关键点:
会话是线程安全的,这意味着它不适合需要访问它的并发客户端请求。如果您需要异步进程来访问会话类型数据,您可以使用其他形式的临时存储,如缓存或数据库(对于异步进度跟踪,这是必要的)。否则,用户会话信息将锁定请求的上下文,以防止更改问题。因此,为什么请求1没问题,那么以下请求有延迟。 PS这绝对有必要保证会话中数据的完整性......就像电子商务中的用户订单一样。
会话在服务器上进行序列化。 Cookie用于维护会话,虽然它是一种不同类型的cookie,这意味着我不会期望在性能上有太大差异。
所以我最喜欢的加速会话请求的方法总是使用状态服务器。它会消除进程中的问题并且还意味着在测试中您可以重建项目而无需再次登录到您的站点。它还可以在简单的负载平衡方案中实现会话。 http://msdn.microsoft.com/en-us/library/ms972429.aspx
PS我应该补充一点,从技术上来说,进程状态服务应该更慢。根据我的经验,开发速度更快更容易,但我想这取决于它的使用方式。
答案 3 :(得分:1)
我只想在@bendytree的优秀答案中添加一些细节。使用WebForms时,您不仅可以完全禁用会话状态
<%@ Page EnableSessionState="False" %>
但也将其设置为只读:
<%@ Page EnableSessionState="ReadOnly" %>
这解决了我案例中描述的问题。 见documentation