在多数web开发中会使用session来保存会话状态,但是在asp.net的应用中使用session,有可能会对web应用的性能产生不小的影响。为什么会有影响以及会影响什么,下文分析
MSDN对并发请求和会话状态的解释:
并发请求和会话状态
对 ASP.NET 会话状态的访问专属于每个会话,这意味着如果两个不同的用户同时发送请求,则会同时授予对每个单独会话的访问。但是,如果这两个并发请求是针对同一会话的(通过使用相同的 SessionID 值),则第一个请求将获得对会话信息的独占访问权。第二个请求将只在第一个请求完成之后执行。(如果由于第一个请求超过了锁定超时时间而导致对会话信息的独占锁定被释放,则第二个会话也可获得访问权。)如果将 @ Page 指令中的 EnableSessionState 值设置为 ReadOnly,则对只读会话信息的请求不会导致对会话数据的独占锁定。但是,对会话数据的只读请求可能仍需等到解除由会话数据的读写请求设置的锁定。
由此解释可知,如果在asp.net中使用了session,并发请求的时候,实际上只有一个请求在处理,其余请求是在等待会话信息锁的释放。
直接试验一下该影响
public class SessionTestController : Controller
{
// GET: SessionTest
public ActionResult Index()
{
return View();
}
/// <summary>
/// 没使用Session
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
public ActionResult NotUseSession(string name)
{
Thread.Sleep(3000);
return Json(name + "success");
}
/// <summary>
/// 使用了Session
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
public ActionResult UseSession(string name)
{
Thread.Sleep(3000);
Session["mysession"] = "test";
return Json(name + "success");
}
}
<body>
<button id="notusesession">不使用session</button>
<button id="usesession">使用session</button>
<div id="div1">
</div>
<script>
$("#notusesession").click(function () {
var url = '/SessionTest/NotUseSession';
ajaxFunc(url);
})
$("#usesession").click(function () {
var url = '/SessionTest/UseSession';
ajaxFunc(url);
})
function ajaxFunc(url) {
$("#div1").html('');
$.ajax({
url: url,
datatype: "json",
type: 'post',
data: $.parseJSON('{"name":"request1 "}'),
success: function (data) {
$("#div1").append(data.toString() + '<br />');
}
});
$.ajax({
url: url,
datatype: "json",
type: 'post',
data: $.parseJSON('{"name":"request2 "}'),
success: function (data) {
$("#div1").append(data.toString() + '<br />');
}
});
$.ajax({
url: url,
datatype: "json",
type: 'post',
data: $.parseJSON('{"name":"request3 "}'),
success: function (data) {
$("#div1").append(data.toString() + '<br />');
}
});
}
</script>
</body>
下面是页面效果:
step1:点击“不使用session”按钮,即发出3个并发请求/SessionTest/NotUseSession,这时asp.net是可以并发处理三个请求的,页面效果同时出现
step2:点击“使用session”按钮,即发出3个并发请求/SessionTest/UseSession,这次的三个请求的cookie还没有携带sessionId,所以页面效果依然同时出现
step3:现在无论是点击“不使用session”按钮,还是“使用session”按钮,这时的请求的Cookie是携带了sessionId,所以asp.net会对会话信息上锁,其余两个请求等待,实际只有一个请求在处理,所以页面上的效果也是一个个地依次出现
由试验得知,在asp.net中使用session对同个用户的并发请求有比较大的影响!
我们看看再asp.net的处理管道
- 当web应该第一次被访问时,会加载一个特殊的运行时(IsapiRuntime),IsapiRuntime会接管请求改应该的http请求
- IsapiRuntime会封装请求,并传递给应用程序运行时(HttpRuntime),此时请求正式进入ASP.NET管道
- HttpRuntime会创建当前请求的上下文(HttpContext)
- HttpRuntime会利用HttpApplicationFactory从HttpApplication对象池中获取一个HttpApplication
- HttpApplication在它处理Http请求的不同阶段会触发不同的事件
下面看看HttpApplication的19个标准事件
由标准事件中看出,AcquireRequestState事件发生在:取得请求状态之后触发。也就是说如果并发请求,请求的耗时主要是发生在PostMapRequestHandler事件和AcquireRequestState事件之间
这时可使用MiniProfiler记录各个事件之间的耗时情况
代码大概这样
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
MiniProfiler.Settings.IgnoredPaths = new string[] {
"/Assets/",
"/Scripts/",
"/Content/",
"/favicon.ico",
"/html/",
"/Upload/",
"/Template/",
"/signalr/",
};
MiniProfilerEF6.Initialize();
}
protected void Application_BeginRequest()
{
try
{
MiniProfiler.Start();
}
catch { }
}
protected void Application_EndRequest()
{
try
{
MiniProfiler.Stop();
}
catch { }
}
protected void Application_PostMapRequestHandler()
{
MiniProfiler.StepStatic("Application_PostMapRequestHandler");
}
protected void Application_AcquireRequestState()
{
MiniProfiler.StepStatic("Application_AcquireRequestState");
}
protected void Application_PostAcquireRequestState()
{
MiniProfiler.StepStatic("Application_PostAcquireRequestState");
}
protected void Application_PreRequestHandlerExecute()
{
MiniProfiler.StepStatic("Application_PreRequestHandlerExecute");
}
protected void Application_PostRequestHandlerExecute()
{
MiniProfiler.StepStatic("Application_PostRequestHandlerExecute");
}
}
使用session的请求耗时如下
1、第一个请求在PostMapRequestHandler事件之后,AcquireRequestState事件之前只耗时0.1ms
2、第二个请求在PostMapRequestHandler事件之后,AcquireRequestState事件之前耗时3000ms,即等待第一个请求处理完成释放锁花了3000ms
3、第三个请求在PostMapRequestHandler事件之后,AcquireRequestState事件之前耗时6000ms,即等待第一个请求和第二个处理完成释放锁花了6000ms
最后是解决方案的建议
1、在web.config文件中关闭session
<system.web>
<sessionState mode="Off" />
</system.web>
2、改用Customer模式,自己实现会话状态存储提供程序(如何实现可参考:https://msdn.microsoft.com/zh-cn/library/ms178589(v=vs.100).aspx)
<sessionState mode="Custom" customProvider="CustomSessionProvider" >
<providers>
<add name="CustomSessionProvider" type="sessionTest.Extensions.CustomSessionProvider" />
</providers>
</sessionState>
END
参考文献:
http://www.bubuko.com/infodetail-1289270.html
https://msdn.microsoft.com/zh-cn/library/ms178581(v=vs.100).aspx
https://msdn.microsoft.com/zh-cn/library/ms178589(v=vs.100).aspx
https://msdn.microsoft.com/zh-cn/library/ms178587(v=vs.100).aspx