分佈式中Redis實現Session終結篇

  上一篇使用Redis實現Session共享方式雖然可行,可是實際操做起來卻很麻煩,現有代碼已是這個樣子了,總不可能所有換掉吧!好吧,這是個很實際的問題,那麼能不能實現無侵入式的分佈式Session共享方案呢?mode="InProc"這是web.config裏面使用iis進程保存Session的配置,不知你注意過沒,mode除了InProc,SQLServer,StateServer這幾個經常使用的,還有一個Custom。這裏我要使用的是網友提供給的一種方自定義Session,須要繼承System.Web.SessionState.SessionStateStoreProviderBase實現本身的SessionStateStoreProvide,下面講解如何實現自定義的RedisSessionStateStore。git

閱讀目錄web

SessionStateStoreProviderBase

  SessionStateStoreProviderBase是asp.net提供的定義數據存儲區的會話狀態提供程序所需的成員。像經常使用InProcSessionStateStore(mode="InProc"),SqlSessionStateStore(mode="SQLServer"),都是繼承SessionStateStoreProviderBase實現的存儲。咱們來看看msdn對其成員的定義redis

成員 說明

InitializeRequest 方法json

執行會話狀態存儲提供程序必需的全部初始化操做。跨域

EndRequest 方法瀏覽器

執行會話狀態存儲提供程序必需的全部清理操做。cookie

Dispose 方法session

釋放會話狀態存儲提供程序再也不使用的全部資源。併發

GetItemExclusive 方法asp.net

從會話數據存儲區中檢索會話的值和信息,並在請求持續期間鎖定數據存儲區中的會話項數據。GetItemExclusive 方法設置幾個輸出參數值,這些參數值將數據存儲區中當前會話狀態項的狀態通知給執行調用的 SessionStateModule

若是數據存儲區中未找到任何會話項數據,則GetItemExclusive 方法將 locked 輸出參數設置爲false,並返回 null。這將致使 SessionStateModule調用 CreateNewStoreData 方法來爲請求建立一個新的SessionStateStoreData 對象。

若是在數據存儲區中找到會話項數據但該數據已鎖定,則GetItemExclusive 方法將 locked 輸出參數設置爲true,將 lockAge 輸出參數設置爲當前日期和時間與該項鎖定日期和時間的差,將 lockId 輸出參數設置爲從數據存儲區中檢索的鎖定標識符,並返回 null。這將致使SessionStateModule 隔半秒後再次調用GetItemExclusive 方法,以嘗試檢索會話項信息和獲取對數據的鎖定。若是 lockAge 輸出參數的設置值超過ExecutionTimeout 值,SessionStateModule 將調用ReleaseItemExclusive 方法以清除對會話項數據的鎖定,而後再次調用 GetItemExclusive 方法。

若是 regenerateExpiredSessionId 屬性設置爲 true,則 actionFlags 參數用於其 Cookieless 屬性爲 true 的會話。actionFlags 值設置爲 InitializeItem (1) 則指示會話數據存儲區中的項是須要初始化的新會話。經過調用CreateUninitializedItem 方法能夠建立會話數據存儲區中未初始化的項。若是會話數據存儲區中的項已經初始化,則 actionFlags 參數設置爲零。

若是提供程序支持無 Cookie 會話,請將 actionFlags 輸出參數設置爲當前項從會話數據存儲區中返回的值。若是被請求的會話存儲項的 actionFlags 參數值等於InitializeItem 枚舉值 (1),則 GetItemExclusive 方法在設置 actionFlags out 參數以後應將數據存儲區中的值設置爲零。

GetItem 方法

除了不嘗試鎖定數據存儲區中的會話項之外,此方法與GetItemExclusive 方法執行的操做相同。GetItem 方法在 EnableSessionState 屬性設置爲 ReadOnly 時調用。

SetAndReleaseItemExclusive 方法

採用當前請求的 HttpContext 實例、當前請求的SessionID 值、包含要存儲的當前會話值的SessionStateStoreData 對象、當前請求的鎖定標識符以及指示要存儲的數據是屬於新會話仍是現有會話的值做爲輸入。

若是 newItem 參數爲 true,則SetAndReleaseItemExclusive 方法使用提供的值將一個新項插入到數據存儲區中。不然,數據存儲區中的現有項使用提供的值進行更新,並釋放對數據的任何鎖定。請注意,只有與提供的 SessionID 值和鎖定標識符值匹配的當前應用程序的會話數據纔會更新。

調用 SetAndReleaseItemExclusive 方法後,SessionStateModule 調用 ResetItemTimeout 方法來更新會話項數據的過時日期和時間。

ReleaseItemExclusive 方法

採用當前請求的 HttpContext 實例、當前請求的SessionID 值以及當前請求的鎖定標識符做爲輸入,並釋放對會話數據存儲區中的項的鎖定。在調用 GetItem 或GetItemExclusive 方法,而且數據存儲區指定被請求項已鎖定,但鎖定時間已超過 ExecutionTimeout 值時會調用此方法。此方法清除鎖定,釋放該被請求項以供其餘請求使用。

RemoveItem 方法

此方法在 Abandon 方法被調用時調用。

CreateUninitializedItem 方法

採用當前請求的 HttpContext 實例、當前請求的SessionID 值以及當前請求的鎖定標識符做爲輸入,並向會話數據存儲區添加一個 actionFlags 值爲InitializeItem 的未初始化項。

若是 regenerateExpiredSessionId 屬性設置爲 true,則 CreateUninitializedItem 方法用於無 Cookie 會話,這將致使遇到過時會話 ID 時,SessionStateModule 會生成一個新的 SessionID值。

生成新的 SessionID 值的過程須要瀏覽器重定向到包含新生成的會話 ID 的 URL。在包含過時的會話 ID 的初始請求期間,會調用 CreateUninitializedItem 方法。SessionStateModule 獲取一個新的 SessionID 值來替換過時的會話 ID 以後,它會調用CreateUninitializedItem 方法以將一個未初始化項添加到會話狀態數據存儲區中。而後,瀏覽器重定向到包含新生成的 SessionID 值的 URL。若是會話數據存儲區中存在未初始化項,則能夠確保包含新生成的 SessionID 值的重定向請求被視爲新的會話,而不會被誤認爲是對過時會話的請求。

會話數據存儲區中未初始化的項與新生成的 SessionID值關聯,而且僅包含默認值,其中包括到期日期和時間以及與 GetItem 和 GetItemExclusive 方法的actionFlags 參數相對應的值。會話狀態存儲區中的未初始化項應包含一個與 InitializeItem 枚舉值 (1) 相等的actionFlags 值。此值由 GetItem 和GetItemExclusive 方法傳遞給SessionStateModule,並針對 SessionStateModule指定當前會話是新會話。而後,SessionStateModule將初始化該新會話,並引起 Session_OnStart 事件。

CreateNewStoreData 方法

採用當前請求的 HttpContext 實例和當前會話的Timeout 值做爲輸入,並返回帶有空ISessionStateItemCollection 對象的新的SessionStateStoreData 對象、一個HttpStaticObjectsCollection 集合和指定的 Timeout值。使用 GetSessionStaticObjects 方法能夠檢索 ASP.NET 應用程序的 HttpStaticObjectsCollection 實例。

上面的定義有點長,其實不少都在說明一點那就是原生了Session是單線程的方式實現的,當多個進行讀的時候會加鎖後面的會等待。咱們下面實現的去掉了這些鎖,加快併發讀寫。

實現本身的RedisSessionStateStore

    繼承SessionStateStoreProviderBase實現本身的RedisSessionStateStore也很簡單,只需繼承SessionStateStoreProviderBase重寫CreateNewStoreData,CreateUninitializedItem,GetItem等幾個方法便可,下面貼出代碼,參考InProcSessionStateStore實現。

/// <summary>
    /// 使用Cookie實現SessionStateStoreProviderBase
    /// 注意:它只適合保存簡單的基元類型數據。
    /// </summary>
    public class RedisSessionStateStore : SessionStateStoreProviderBase
    {
        public override SessionStateStoreData CreateNewStoreData(HttpContext context, int timeout)
        {
            return CreateLegitStoreData(context, null, null, timeout);
        }

        internal static SessionStateStoreData CreateLegitStoreData(HttpContext context, ISessionStateItemCollection sessionItems, HttpStaticObjectsCollection staticObjects, int timeout)
        {
            if (sessionItems == null)
                sessionItems = new SessionStateItemCollection();
            if (staticObjects == null && context != null)
                staticObjects = SessionStateUtility.GetSessionStaticObjects(context);
            return new SessionStateStoreData(sessionItems, staticObjects, timeout);
        }

        public override void CreateUninitializedItem(HttpContext context, string id, int timeout)
        {
            RedisSessionState state = new RedisSessionState(null, null, timeout);
            RedisBase.Item_Set<string>(id, state.ToJson(), timeout);
        }

        private SessionStateStoreData DoGet(HttpContext context, string id, bool exclusive, out bool locked, out TimeSpan lockAge, out object lockId, out SessionStateActions actionFlags)
        {
            locked = false;
            lockId = null;
            lockAge = TimeSpan.Zero;
            actionFlags = SessionStateActions.None;
            RedisSessionState state = RedisSessionState.FromJson(RedisBase.Item_Get<string>(id));
            if (state == null)
            {
                return null;
            }
            RedisBase.Item_SetExpire(id, state._timeout);
            return CreateLegitStoreData(context, state._sessionItems, state._staticObjects, state._timeout);
        }

        public override SessionStateStoreData GetItem(HttpContext context, string id, out bool locked, out TimeSpan lockAge, out object lockId, out SessionStateActions actionFlags)
        {
            return this.DoGet(context, id, false, out locked, out lockAge, out lockId, out actionFlags);
        }

        public override SessionStateStoreData GetItemExclusive(HttpContext context, string id, out bool locked, out TimeSpan lockAge, out object lockId, out SessionStateActions actionFlags)
        {
            return this.DoGet(context, id, true, out locked, out lockAge, out lockId, out actionFlags);
        }

        public override void SetAndReleaseItemExclusive(HttpContext context, string id, SessionStateStoreData item, object lockId, bool newItem)
        {
            ISessionStateItemCollection sessionItems = null;
            HttpStaticObjectsCollection staticObjects = null;

            if (item.Items.Count > 0)
                sessionItems = item.Items;
            if (!item.StaticObjects.NeverAccessed)
                staticObjects = item.StaticObjects;

            RedisSessionState state2 = new RedisSessionState(sessionItems, staticObjects, item.Timeout);

            RedisBase.Item_Set<string>(id, state2.ToJson(), item.Timeout);
        }

        #region "未實現方法"

        public override void Dispose()
        {

        }

        public override void EndRequest(HttpContext context)
        {

        }

        public override void InitializeRequest(HttpContext context)
        {

        }

        public override void ReleaseItemExclusive(HttpContext context, string id, object lockId)
        {
        }

        public override void RemoveItem(HttpContext context, string id, object lockId, SessionStateStoreData item)
        {
            RedisBase.Item_Remove(id);
        }

        public override void ResetItemTimeout(HttpContext context, string id)
        {

        }

        public override bool SetItemExpireCallback(SessionStateItemExpireCallback expireCallback)
        {
            return true;
        }

        #endregion

    }
    internal sealed class SessionStateItem
    {
        public Dictionary<string, SaveValue> Dict;
        public int Timeout;
    }

    internal sealed class SaveValue
    {
        public object Value { get; set; }

        public Type Type { get; set; }
    }

    internal sealed class RedisSessionState
    {
        internal ISessionStateItemCollection _sessionItems;
        internal HttpStaticObjectsCollection _staticObjects;
        internal int _timeout;

        internal RedisSessionState(ISessionStateItemCollection sessionItems, HttpStaticObjectsCollection staticObjects, int timeout)
        {
            this.Copy(sessionItems, staticObjects, timeout);
        }

        internal void Copy(ISessionStateItemCollection sessionItems, HttpStaticObjectsCollection staticObjects, int timeout)
        {
            this._sessionItems = sessionItems;
            this._staticObjects = staticObjects;
            this._timeout = timeout;
        }

        public string ToJson()
        {
            // 這裏忽略_staticObjects這個成員。

            if (_sessionItems == null || _sessionItems.Count == 0)
            {
                return null;
            }

            Dictionary<string, SaveValue> dict = new Dictionary<string, SaveValue>(_sessionItems.Count);

            NameObjectCollectionBase.KeysCollection keys = _sessionItems.Keys;
            string key;
            object objectValue = string.Empty;
            Type type = null;
      //2016-09-04解決存入值沒有類型致使讀取值是jobject問題  
for (int i = 0; i < keys.Count; i++) { key = keys[i]; objectValue = _sessionItems[key]; if (objectValue != null) { type = objectValue.GetType(); } else { type = typeof(object); } dict.Add(key, new SaveValue { Value = objectValue, Type = type }); } SessionStateItem item = new SessionStateItem { Dict = dict, Timeout = this._timeout }; return JsonConvert.SerializeObject(item); } public static RedisSessionState FromJson(string json) { if (string.IsNullOrEmpty(json)) { return null; } try { SessionStateItem item = JsonConvert.DeserializeObject<SessionStateItem>(json); SessionStateItemCollection collections = new SessionStateItemCollection(); SaveValue objectValue = null;           //2016-09-04解決讀取出來的值 沒有類型的問題 JsonSerializer serializer = new JsonSerializer(); StringReader sr = null; JsonTextReader tReader = null; foreach (KeyValuePair<string, SaveValue> kvp in item.Dict) { objectValue = kvp.Value as SaveValue; if (objectValue.Value == null) { collections[kvp.Key] = null; } else { if (!IsValueType(objectValue.Type)) { sr = new StringReader(objectValue.Value.ToString()); tReader = new JsonTextReader(sr); collections[kvp.Key] = serializer.Deserialize(tReader, objectValue.Type); } else { collections[kvp.Key] = objectValue.Value; } } } return new RedisSessionState(collections, null, item.Timeout); } catch { return null; } } /// <summary> /// 判斷是否爲值類型 /// </summary> /// <param name="type">Type</param> /// <returns></returns> public static bool IsValueType(Type type) { if (type.IsValueType) { return true; } //基礎數據類型 if (type == typeof(string) || type == typeof(char) || type == typeof(ushort) || type == typeof(short) || type == typeof(uint) || type == typeof(int) || type == typeof(ulong) || type == typeof(long) || type == typeof(double) || type == typeof(decimal) || type == typeof(bool) || type == typeof(byte)) { return true; } //可爲null的基礎數據類型 if (type.IsGenericType && !type.IsGenericTypeDefinition) { Type genericType = type.GetGenericTypeDefinition(); if (Object.ReferenceEquals(genericType, typeof(Nullable<>))) { var actualType = type.GetGenericArguments()[0]; return IsValueType(actualType); } } return false; } }

 

使用 也很簡單,修改web.config,以下圖
 
而後能夠保持原先代碼不變,像Session["UserCode"]="admin"方式進行使用,可是如今的Session已經具有了分佈式的特徵,支持跨域。這裏得說一下該方式的缺點,在GetItem和SetAndReleaseItemExclusive時須要對鍵值對進行反序列化和序列化操做,對於保存數據量大的狀況反而性能相對於系統提供的方式大打折扣,因此使用的時候須要考慮本身的實際場景。
 

總結  

    原本分佈式Session共享到上篇就完結了,可是因爲方案的可行性差,還有更好的方案,因此花了點時間參考了前面MSND中的說明,和ASP.net源碼中InProcSessionStateStore的實現,解決GetItemExclusive等方法的併發鎖定問題,最終實現了Redis的存儲方式,更加靈活方便。這裏要感謝你們提的意見,讓我又學會了一個知識點!

      資源下載地址:redis_demo

  svn下載地址:http://code.taobao.org/svn/ResidSessionDemo/

  本文參考:

  sessionstatestoreproviderbase定義:https://msdn.microsoft.com/zh-cn/library/system.web.sessionstate.sessionstatestoreproviderbase(VS.80).aspx

  sessionstatestoreproviderbase成員:https://msdn.microsoft.com/zh-cn/library/ms178587(v=vs.80).aspx

  

相關文章
相關標籤/搜索