前段時間 工作需要 生平第一次聽到“無狀態”一詞 隨後 了解了下 怕自己忘記 隨手記錄下來
/*
尚未創建過Session:
InitializeRequest -> CreateNewStoreData
InitializeRequest -> ResetItemTimeout -> InitializeRequest -> GetItemExclusive
(一種假象:http://localhost:29566和http://localhost:29566/Default.aspx)
已經創建過Session:
InitializeRequest -> GetItemExclusive
但如果沒查到 會 CreateUninitializedItem -> GetItemExclusive(不管經過是什麼,都會進入Page_Load)
--------------Page_load--------------
EndRequest(尚未創建過Session)
ReleaseItemExclusive -> EndRequest(已經創建過Session,此次無Update)
SetAndReleaseItemExclusive -> EndRequest (已經創建過Session,且此次有Update)
HttpModule -> RedisSession(InitializeRequest -> ResetItemTimeout只會在進入HttpHandle前調用這兩個方法) -> HttpHandle
不要再HttpModule、HttpHandle中操作Session
HttpModel是因為調用順序問題,HttpHandle是因為就算更新了,也不會進行被同步到Redis
好吧,其實這兩個的context.Session 都是null
Media資源(圖片、css、script)是不會進來的
*/
定義一個數據存儲會話狀態所需的成員
Namespace: System.Web.SessionState
Assembly: System.Web (in System.Web.dll)
1.初始化每個請求對象
public abstract void InitializeRequest(HttpContext context)
Redis:
/// <summary> /// 在異步回發初始化期間提出 /// </summary> /// <param name="context"></param> public override void InitializeRequest(HttpContext context) { if (context != null && context.Request != null) { LogInfoFormat("InitializeRequest invoking: Cookie.ASP.NET_SessionId:{0},RequestRawUrl:{1}" , context.Request.Cookies != null && context.Request.Cookies["ASP.NET_SessionId"] != null ? context.Request.Cookies["ASP.NET_SessionId"].Value : "" , context.Request.RawUrl ); } }
2.創建一個新的對象用對當前的請求
public abstract SessionStateStoreData CreateNewStoreData(HttpContext context,int timeout)
Redis:
public override SessionStateStoreData CreateNewStoreData(HttpContext context, int timeout) { return new SessionStateStoreData(new SessionStateItemCollection(),staticObjectsGetter(context),timeout); }
3.更新此次會話的過期時間
public abstract void ResetItemTimeout(HttpContext context,string id)
Redis:
public override void ResetItemTimeout(HttpContext context, string id) { //throw new NotImplementedException(); }
4.從會話狀態存儲中返回只讀會話狀態
public abstract SessionStateStoreData GetItemExclusive(HttpContext context,string id,out bool locked,out TimeSpan lockAge,out object lockId,out SessionStateActions actions)
Redis:
public override SessionStateStoreData GetItemExclusive(HttpContext context, string id, out bool locked, out TimeSpan lockAge, out object lockId, out SessionStateActions actions) { LogInfoFormat("GetItemExclusive invoking: id:{0},RequestRawUrl:{1}" , id , context != null && context.Request != null ? context.Request.RawUrl : "" ); //讀取redis,從redis獲取session return GetItemFromSessionStore(true, context, id, out locked, out lockAge, out lockId, out actions); } /// <summary> /// 從Redis讀取 /// </summary> /// <param name="isWriteLockRequired"></param> /// <param name="context"></param> /// <param name="id"></param> /// <param name="locked"></param> /// <param name="lockAge"></param> /// <param name="lockId"></param> /// <param name="actions"></param> /// <returns></returns> private SessionStateStoreData GetItemFromSessionStore(bool isWriteLockRequired, HttpContext context, string id, out bool locked, out TimeSpan lockAge, out object lockId, out SessionStateActions actions) { locked = false; lockAge = TimeSpan.Zero; lockId = null; actions = SessionStateActions.None; SessionStateStoreData result = null; var key = GetSessionIdKey(id); var stateRaw = RedisHelper.RedisDB.HashGetAll(key); RedisSessionState state; if (!RedisSessionState.TryParse(stateRaw, out state)) { locked = false; //如果在數據存儲區中未找到會話項數據,GetItemExclusive 方法就會將 locked out 參數設置為 false 並返回 null return null; //CreateUninitializedItem -> GetItemExclusive } actions = state.Flags; if (state.Locked) { locked = true; //如果在數據存儲區中找到會話項數據但是數據被鎖定,GetItemExclusive 方法就會將 locked out 參數設置為 true,將 lockAge out 參數設置為當前日期和時間與項被鎖定時的日期和時間(從數據存儲區中檢索)的差值,將 lockId out 參數設置為從數據存儲區中檢索的鎖定標識符,並返回 null lockId = state.LockId; //lockAge = new TimeSpan((DateTime.UtcNow - state.LockDate).Ticks * 7);//ExecutionTimeout有點長,100s左右 lockAge = DateTime.UtcNow - state.LockDate; return null; } if (isWriteLockRequired) { locked = state.Locked = true; state.LockDate = DateTime.UtcNow; lockAge = TimeSpan.Zero; lockId = ++state.LockId; } state.Flags = SessionStateActions.None; UpdateSessionState(key, state); var items = actions == SessionStateActions.InitializeItem ? new SessionStateItemCollection() : state.Items; result = new SessionStateStoreData(items, staticObjectsGetter(context), state.Timeout); return result; }
5.添加一個新的會話狀態項到會話存儲裡
public abstract void CreateUninitializedItem(HttpContext context,string id,int timeout)
Redis:
// GetItemExclusive如果沒有查到,則會調用該方法(非因為Locked return null) public override void CreateUninitializedItem(HttpContext context, string id, int timeout) { var key = GetSessionIdKey(id); var state = new RedisSessionState() { Timeout = timeout, Flags = SessionStateActions.InitializeItem }; UpdateSessionState(key, state); } private void UpdateSessionState(string key, RedisSessionState state) { UseTransaction(transaction => { transaction.HashSetAsync(key, state.ToHashEntryArr()); transaction.KeyExpireAsync(key, TimeSpan.FromMinutes(state.Timeout)); }); }
6.一個請求結束後回傳
public abstract void EndRequest(HttpContext context)
Redis:
public override void EndRequest(HttpContext context) { //throw new NotImplementedException(); }
7.釋放會話狀態存儲中的鎖定
public abstract void ReleaseItemExclusive(HttpContext context,string id,object lockId)
Redis:
///1、一次Http請求處理完之後(在Page_Load 及 aspx之後),如果沒有發生了Session Update,則會調用此方法 ///2、Page_Load之前,如果反復執行GetItemExclusive始終是locked狀態,一段時間之後系統會自行調用此方法來嘗試釋放. /// 如果 SessionStateModule 對象在調用 GetItemExclusive 或 GetItem 方法期間遇到鎖定的會話數據,它將以半秒為間隔重新請求會話數據,
/// 直到鎖定被釋放或者會話數據的鎖定時間超過 ExecutionTimeout 屬性的值。如果超過了執行超時時間,SessionStateModule 對象將調用 ReleaseItemExclusive 方法以釋放會話存儲區數據並隨即請求會話存儲區數據 public override void ReleaseItemExclusive(HttpContext context, string id, object lockId) { UnlockSessionStateIfLocked(id, (int)lockId); } /// <summary> /// 釋放會話存儲區數據並隨即請求會話存儲區數據 /// </summary> /// <param name="id"></param> /// <param name="lockId"></param> private void UnlockSessionStateIfLocked(string id, int lockId) { var key = GetSessionIdKey(id); var stateRaw = RedisHelper.RedisDB.HashGetAll(key); RedisSessionState state; if (RedisSessionState.TryParse(stateRaw, out state) && state.Locked && state.LockId == lockId) { UnlockSessionState(key, state.Timeout); } } /// <summary> /// 不知道這樣 對比 UpdateSessionState, 性能怎麼樣。這裡雖然分成多項,但會作為一個transaction提交 /// </summary> /// <param name="client"></param> /// <param name="key"></param> /// <param name="state"></param> private void UnlockSessionState(string key, int timeout) { UseTransaction(transaction => { transaction.HashSetAsync(key, "locked", false); transaction.HashSetAsync(key, "lockId", 0); transaction.HashSetAsync(key, "lockDate", 0); //transaction.HashSetAsync(key, "timeout", timeout); //沒必要 transaction.KeyExpireAsync(key, TimeSpan.FromMinutes(timeout)); }); } /// <summary> /// /// </summary> /// <param name="action"></param> private void UseTransaction(Action<ITransaction> action) { try { //Redis的更新 都會進這 var transaction = RedisHelper.RedisDB.CreateTransaction(); action(transaction); transaction.Execute(); } catch (Exception ex) { LogError(ex.Message, ex); } }
8.在更新會話狀態數據存儲與當前請求的值的會話項信息,並清除對數據的鎖定
public abstract void SetAndReleaseItemExclusive(HttpContext context,string id,SessionStateStoreData item,object lockId,bool newItem)
Redis:
/// 一次Http請求處理完之後(在Page_Load 及 aspx之後),如果發生了Session Update,則會調用此方法 public override void SetAndReleaseItemExclusive(HttpContext context, string id, SessionStateStoreData item, object lockId, bool newItem) { if (newItem) //true:定義一個新的狀態 { var state = new RedisSessionState() { Items = (SessionStateItemCollection)item.Items, Timeout = item.Timeout, }; var key = GetSessionIdKey(id); UpdateSessionState(key, state); } else //false 已存在的狀態 { UpdateSessionStateIfLocked(id, (int)lockId, state => { state.Items = (SessionStateItemCollection)item.Items; state.Locked = false; state.Timeout = item.Timeout; }); } } /// <summary> /// /// </summary> /// <param name="id"></param> /// <param name="lockId"></param> /// <param name="stateAction"></param> private void UpdateSessionStateIfLocked(string id, int lockId, Action<RedisSessionState> stateAction) { var key = GetSessionIdKey(id); var stateRaw = RedisHelper.RedisDB.HashGetAll(key); RedisSessionState state; if (RedisSessionState.TryParse(stateRaw, out state) && state.Locked && state.LockId == lockId) { stateAction(state); UpdateSessionState(key, state); } } /// <summary> /// /// </summary> /// <param name="client"></param> /// <param name="key"></param> /// <param name="state"></param> private void UpdateSessionState(string key, RedisSessionState state) { UseTransaction(transaction => { transaction.HashSetAsync(key, state.ToHashEntryArr()); transaction.KeyExpireAsync(key, TimeSpan.FromMinutes(state.Timeout)); }); } /// <summary> /// /// </summary> /// <param name="action"></param> private void UseTransaction(Action<ITransaction> action) { try { //Redis的更新 都會進這 var transaction = RedisHelper.RedisDB.CreateTransaction(); action(transaction); transaction.Execute(); } catch (Exception ex) { LogError(ex.Message, ex); } }
未完,待續...