分布式中Redis实现Session终结篇
上一篇使用Redis實現Session共享方式雖然可行,但是實際操作起來卻很麻煩,現有代碼已經是這個樣子了,總不可能全部換掉吧!好吧,這是個很實際的問題,那么能不能實現無侵入式的分布式Session共享方案呢?mode="InProc"這是web.config里面使用iis進程保存Session的配置,不知你注意過沒,mode除了InProc,SQLServer,StateServer這幾個常用的,還有一個Custom。這里我要使用的是網友提供給的一種方自定義Session,需要繼承System.Web.SessionState.SessionStateStoreProviderBase實現自己的SessionStateStoreProvide,下面講解如何實現自定義的RedisSessionStateStore。
閱讀目錄
- SesstionStateProvider
- 實現自己的RedisSessionStateStore
- 總結
SessionStateStoreProviderBase
SessionStateStoreProviderBase是asp.net提供的定義數據存儲區的會話狀態提供程序所需的成員。像常用InProcSessionStateStore(mode="InProc"),SqlSessionStateStore(mode="SQLServer"),都是繼承SessionStateStoreProviderBase實現的存儲。我們來看看msdn對其成員的定義
| InitializeRequest?方法 | 執行會話狀態存儲提供程序必需的所有初始化操作。 |
| EndRequest?方法 | 執行會話狀態存儲提供程序必需的所有清理操作。 |
| Dispose?方法 | 釋放會話狀態存儲提供程序不再使用的所有資源。 |
| GetItemExclusive?方法 | 從會話數據存儲區中檢索會話的值和信息,并在請求持續期間鎖定數據存儲區中的會話項數據。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
如果,您認為閱讀這篇博客讓您有些收獲,不妨點擊一下右下角的【推薦】按鈕。
如果,您希望更容易地發現我的新博客,不妨點擊一下綠色通道的【關注我】。
因為,我的寫作熱情也離不開您的肯定支持。
感謝您的閱讀,如果您對我的博客所講述的內容有興趣,請繼續關注我的后續博客,我是焰尾迭 。
轉載于:https://www.cnblogs.com/yanweidie/p/4763556.html
總結
以上是生活随笔為你收集整理的分布式中Redis实现Session终结篇的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: MySQL管理员篇
- 下一篇: [Linux] 使用noatime属性优