我刚刚发现ASP中的每个请求。网络web应用程序在请求开始时获得一个会话锁,然后在请求结束时释放它!
如果你不明白这其中的含义,就像我一开始一样,这基本上意味着:
Any time an ASP.Net webpage is taking a long time to load (maybe due to a slow database call or whatever), and the user decides they want to navigate to a different page because they are tired of waiting, they can't! The ASP.Net session lock forces the new page request to wait until the original request has finished its painfully slow load. Arrrgh.
Anytime an UpdatePanel is loading slowly, and the user decides to navigate to a different page before the UpdatePanel has finished updating... they can't! The ASP.Net session lock forces the new page request to wait until the original request has finished its painfully slow load. Double Arrrgh!
那么有什么选择呢?到目前为止,我想出了:
Implement a Custom SessionStateDataStore, which ASP.Net supports. I haven't found too many out there to copy, and it seems kind of high risk and easy to mess up.
Keep track of all requests in progress, and if a request comes in from the same user, cancel the original request. Seems kind of extreme, but it would work (I think).
Don't use Session! When I need some kind of state for the user, I could just use Cache instead, and key items on the authenticated username, or some such thing. Again seems kind of extreme.
我真不敢相信ASP。Net微软团队在4.0版本的框架中留下了如此巨大的性能瓶颈!我是不是遗漏了什么明显的东西?为会话使用ThreadSafe集合有多难?
对于遇到这个问题并且发现没有任何解决方案有用的Mono用户,您没有做错任何事情。
Mono (Issue #19618)中有一个bug,使得SessionStateModule上的SessionStateBehavior无用,所以在Web上设置SessionStateBehavior并不重要。config/pages, Application_BeginRequest,或者在控制器或动作上设置一个属性。什么都不行。我试过了。
然而,防止锁定的逻辑(在SessionStateModule上调用GetItem而不是GetItemExclusive)有一个限制:HttpHandler必须实现标记接口IReadOnlySessionState。
因此,我没有实现我自己的SessionStateModule,而是采用了一种不同的(有点笨拙的)方法。
供贵方考虑:
// Custom handler that derives from MvcHandler which implements IReadOnlySessionState
public class MvcReadOnlyHandler : MvcHandler, IReadOnlySessionState
{
public MvcReadOnlyHandler(RequestContext requestContext) : base(requestContext)
{
}
}
// Custom RouteHandler that derives from `MvcRouteHandler` which
// returns our very own `MvcReadOnlyHandler`
public class MvcConcurrentRouteHandler : MvcRouteHandler
{
protected override IHttpHandler GetHttpHandler(RequestContext requestContext)
{
return new MvcReadOnlyHandler(requestContext);
}
}
// On Global.asax.cs Application_Start, after all the Routes and Areas are registered
// change only the route handler to the new concurrent route handler
foreach (var routeBase in RouteTable.Routes)
{
// Check if the route handler is of type MvcRouteHandler
if (routeBase is Route { RouteHandler: MvcRouteHandler _ } route)
{
// Replace the route handler
route.RouteHandler = new MvcConcurrentRouteHandler();
}
}
因为现在路由器实现了ireadonlyessionstate,所以没有锁定会话id
希望当这个bug被修复时,我的解决方案将是多余的,但在那之前,我希望它能帮助到别人。
Important note: This solution basically makes storing items on the Session unsafe, I don't use this feature so for me it works. You can still add items since ReadOnly does not prevent writing, it is just not locking.
If you want to guarantee safe writing, you can add another extension method MapRoute to RouteCollection to use the new router, in order to register routes that doesnt lock. Like that you can register your routes to new MvcConcurrentRouteHandler router or to the existing one for writing.
我准备了一个基于这个线程中发布的链接的库。它使用了来自MSDN和CodeProject的例子。感谢詹姆斯。
我还在Joel Mueller的建议下做了一些修改。
代码在这里:
https://github.com/dermeister0/LockFreeSessionState
散列表模块:
Install-Package Heavysoft.LockFreeSessionState.HashTable
ScaleOut StateServer模块:
Install-Package Heavysoft.LockFreeSessionState.Soss
自定义模块:
Install-Package Heavysoft.LockFreeSessionState.Common
如果你想实现对Memcached或Redis的支持,安装这个包。然后继承LockFreeSessionStateModule类并实现抽象方法。
代码还没有在生产环境中进行测试。还需要改进错误处理。在当前实现中不捕获异常。
使用Redis的一些无锁会话提供程序:
https://github.com/angieslist/AL-Redis(由gregmac在本帖子中建议)
https://github.com/welegan/RedisSessionProvider (NuGet: RedisSessionProvider)
https://github.com/efaruk/playground/tree/master/UnlockedStateProvider (NuGet: UnlockedStateProvider.Redis)