1. 程式人生 > >asp.net mvc Session RedisSessionStateProvider鎖的實現

asp.net mvc Session RedisSessionStateProvider鎖的實現

最近專案用到了RedisSessionStateProvider來儲存session,發現比記憶體session慢,後來慢慢了解,發現asp.net session是有鎖的。我在文章 你的專案真的需要Session嗎? redis儲存session效能怎麼樣?也提到一些觀點,本來打算在那篇文章補充一些類容,後來想了一下,還是重寫一個短文吧。有關session 管道流程大家 可以參考 Asp.net Session認識加強-Session究竟是如何儲存你知道嗎?

我們的mvc程式都是有路由資訊,那麼就離不開UrlRoutingModule 該code如下:

namespace System.Web.Routing {
    using
System.Diagnostics; using System.Globalization; using System.Runtime.CompilerServices; using System.Web.Security; [TypeForwardedFrom("System.Web.Routing, Version=3.5.0.0, Culture=Neutral, PublicKeyToken=31bf3856ad364e35")] public class UrlRoutingModule : IHttpModule { private
static readonly object _contextKey = new Object(); private static readonly object _requestDataKey = new Object(); private RouteCollection _routeCollection; [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "
This needs to be settable for unit tests.")] public RouteCollection RouteCollection { get { if (_routeCollection == null) { _routeCollection = RouteTable.Routes; } return _routeCollection; } set { _routeCollection = value; } } protected virtual void Dispose() { } protected virtual void Init(HttpApplication application) { ////////////////////////////////////////////////////////////////// // Check if this module has been already addded if (application.Context.Items[_contextKey] != null) { return; // already added to the pipeline } application.Context.Items[_contextKey] = _contextKey; // Ideally we would use the MapRequestHandler event. However, MapRequestHandler is not available // in II6 or IIS7 ISAPI Mode. Instead, we use PostResolveRequestCache, which is the event immediately // before MapRequestHandler. This allows use to use one common codepath for all versions of IIS. application.PostResolveRequestCache += OnApplicationPostResolveRequestCache; } private void OnApplicationPostResolveRequestCache(object sender, EventArgs e) { HttpApplication app = (HttpApplication)sender; HttpContextBase context = new HttpContextWrapper(app.Context); PostResolveRequestCache(context); } [Obsolete("This method is obsolete. Override the Init method to use the PostMapRequestHandler event.")] public virtual void PostMapRequestHandler(HttpContextBase context) { // Backwards compat with 3.5 which used to have code here to Rewrite the URL } public virtual void PostResolveRequestCache(HttpContextBase context) { // Match the incoming URL against the route table RouteData routeData = RouteCollection.GetRouteData(context); // Do nothing if no route found if (routeData == null) { return; } // If a route was found, get an IHttpHandler from the route's RouteHandler IRouteHandler routeHandler = routeData.RouteHandler; if (routeHandler == null) { throw new InvalidOperationException( String.Format( CultureInfo.CurrentCulture, SR.GetString(SR.UrlRoutingModule_NoRouteHandler))); } // This is a special IRouteHandler that tells the routing module to stop processing // routes and to let the fallback handler handle the request. if (routeHandler is StopRoutingHandler) { return; } RequestContext requestContext = new RequestContext(context, routeData); // Dev10 766875 Adding RouteData to HttpContext context.Request.RequestContext = requestContext; IHttpHandler httpHandler = routeHandler.GetHttpHandler(requestContext); if (httpHandler == null) { throw new InvalidOperationException( String.Format( CultureInfo.CurrentUICulture, SR.GetString(SR.UrlRoutingModule_NoHttpHandler), routeHandler.GetType())); } if (httpHandler is UrlAuthFailureHandler) { if (FormsAuthenticationModule.FormsAuthRequired) { UrlAuthorizationModule.ReportUrlAuthorizationFailure(HttpContext.Current, this); return; } else { throw new HttpException(401, SR.GetString(SR.Assess_Denied_Description3)); } } // Remap IIS7 to our handler context.RemapHandler(httpHandler); } #region IHttpModule Members void IHttpModule.Dispose() { Dispose(); } void IHttpModule.Init(HttpApplication application) { Init(application); } #endregion } }

在PostResolveRequestCache方法中   IHttpHandler httpHandler = routeHandler.GetHttpHandler(requestContext); 這麼一句。這裡的routeHandler其實預設是MvcRouteHandler,所以智力其實是呼叫MvcRouteHandler的GetHttpHandler方法

MvcRouteHandler的code:

 View Code

在MvcRouteHandler中GetHttpHandler設定SessionStateBehavior:

protected virtual IHttpHandler GetHttpHandler(RequestContext requestContext)
{
requestContext.HttpContext.SetSessionStateBehavior(GetSessionStateBehavior(requestContext));
return new MvcHandler(requestContext);
}

SessionStateBehavior的值預設來源於DefaultControllerFactory的GetControllerSessionBehavior方法,有SessionStateAttribute特性就取其值,否者預設的SessionStateBehavior.Default

 View Code

那麼HttpContext.SetSessionStateBehavior方法又是如何實現的:

  internal SessionStateBehavior SessionStateBehavior { get; set; }

        [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate",
          Justification = "An internal property already exists. This method does additional work.")]
        public void SetSessionStateBehavior(SessionStateBehavior sessionStateBehavior) {
            if (_notificationContext != null && _notificationContext.CurrentNotification >= RequestNotification.AcquireRequestState) {
                throw new InvalidOperationException(SR.GetString(SR.Invoke_before_pipeline_event, "HttpContext.SetSessionStateBehavior", "HttpApplication.AcquireRequestState"));
            }

            SessionStateBehavior = sessionStateBehavior;
        }

其實很簡單,就是設定了一個屬性,這裡還有一個ReadOnlySessionState屬性很重要,他需要讀取SessionStateBehavior屬性。由於MvcHandler 預設繼承了IRequiresSessionState介面但是沒有繼承IReadOnlySessionState,

所以預設RequiresSessionState為true,ReadOnlySessionState為false

 View Code

在SessionStateModule的GetSessionStateItem方法裡面有如下code:

這裡我們用的是RedisSessionStateProvider,其code如下:

 View Code

其中GetItem和GetItemExclusive都是呼叫GetItemFromSessionStore方法,如果入口是GetItem呼叫TryCheckWriteLockAndGetData方法(不會發生鎖),入口時GetItemExclusive呼叫TryTakeWriteLockAndGetData方法(會有鎖),但是這2個方法都會修改Session的SessionTimeout值。

TryTakeWriteLockAndGetData方法的實現如下:

執行結果如下:

TryCheckWriteLockAndGetData的實現如下:

在GetItemFromSessionStore方法中如果獲取ISessionStateItemCollection例項為null,我們呼叫 ReleaseItemExclusive(context, id, lockId)方法來釋放鎖(前提是前面呼叫TryTakeWriteLockAndGetData已經獲取lockId),一般這個方法都不會執行的,現在我們知道預設情況下載裝在session的時候就會鎖,如果session例項為null我們會釋放我們的鎖

那麼這個鎖又是是麼時候釋放的了?在 app.ReleaseRequestState += new EventHandler(this.OnReleaseState);會呼叫我們這裡的SetAndReleaseItemExclusive方法,預設情況下它會釋放我們的鎖

執行該方法結果如下:

其實現code如下:

RedisSessionStateProvider為了保證效能,在EndRequest裡面還會嘗試 釋放鎖

到現在我們知道預設載入Session資料的時候會加鎖,在ReleaseRequestState事件預設解鎖。