Redis--Distributed Implementation of Session Ending

The previous article Although it is feasible to use Redis to implement Session sharing, it is very troublesome to operate in practice. The existing code is already like this, and it is impossible to replace it all! Well, this is a very practical question, so can a non-intrusive distributed session sharing scheme be implemented? mode="InProc" This is the configuration of the session saved by the iis process in web.config. I wonder if you have noticed it. In addition to the commonly used modes such as InProc, SQLServer, and StateServer, there is also a Custom. Here I want to use a custom Session provided by netizens. I need to inherit System.Web.SessionState.SessionStateStoreProviderBase to implement my own SessionStateStoreProvide. The following explains how to implement a custom RedisSessionStateStore.

read table of contents

SessionStateStoreProviderBase

  SessionStateStoreProviderBase is a required member of the session state provider provided by asp.net that defines the data store. Like the commonly used InProcSessionStateStore (mode="InProc"), SqlSessionStateStore (mode="SQLServer"), they are all stores that inherit the implementation of SessionStateStoreProviderBase. Let's take a look at msdn's definition of its members

Member description

InitializeRequest  method

Perform all initialization operations required by the session state storage provider.

EndRequest  method

Perform any cleanup operations required by the session state storage provider.

Dispose  method

Frees any resources that are no longer used by the session state storage provider.

GetItemExclusive  method

Retrieves session values ​​and information from the session datastore, and locks session item data in the datastore for the duration of the request. The GetItemExclusive  method sets several output parameter values ​​that inform the calling  SessionStateModule of the state of the current session state item in the datastore .

If no session item data is found in the data store, the GetItemExclusive  method sets the  locked output parameter to false and returns  null . This will cause the  SessionStateModule to call the CreateNewStoreData method to create a new SessionStateStoreData object for the request.

If session item data is found in the data store but the data is locked, the GetItemExclusive  method sets the  locked output parameter to true , sets the  lockAge output parameter to the difference between the current date and time and the lock date and time for the  item, and outputs lockId The parameter is set to the lock identifier retrieved from the datastore and  null is returned . This will cause the SessionStateModule to call the GetItemExclusive method  again after half a second  in an attempt to retrieve session item information and acquire a lock on the data. If  the set value of the lockAge output parameter exceeds the ExecutionTimeout  value, the SessionStateModule  will call the ReleaseItemExclusive  method to clear the lock on the session item data, and then call  the GetItemExclusive  method again.

If  the regenerateExpiredSessionId  property is set to  true , the  actionFlags parameter is used for sessions whose  Cookieless  property is  true  . The actionFlags value is set to  InitializeItem  (1) to indicate that the item in the session data store is a new session that needs to be initialized.  An uninitialized item in the session data store can be created by calling the CreateUninitializedItem method. If the item in the session data store has already been initialized, the  actionFlags parameter is set to zero.

If the provider supports cookieless sessions,  set the actionFlags output parameter to the value returned from the session data store for the current item. If the value of the actionFlags parameter of the requested session store item  is equal to the InitializeItem  enumeration value (1), the  GetItemExclusive  method   shall set the value in the data store to zero after setting the actionFlags  parameter.out

GetItem  method

This method does the same thing as the GetItemExclusive method , except that it does not attempt to lock the session item in the datastore  . The GetItem  method is called when the  EnableSessionState  property is set to  ReadOnly  .

SetAndReleaseItemExclusive 方法

Takes as input the current request's  HttpContext  instance, the current request's SessionID  value, a SessionStateStoreData  object containing the current session value to store, the current request's lock identifier, and a value indicating whether the data to store belongs to a new session or an existing session.

If  the newItem parameter is  true , the SetAndReleaseItemExclusive  method inserts a new item into the data store using the provided value. Otherwise, the existing item in the datastore is updated with the provided value and any locks on the data are released. Note that only   session data for the current application that matches the provided SessionID value and lock identifier value will be updated.

After calling  the SetAndReleaseItemExclusive  method, the SessionStateModule  calls  the ResetItemTimeout  method to update the expiration date and time of the session item data.

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>
    /// 使用Redis实现SessionStateStoreProviderBase
    /// </summary>
    public class RedisSessionStateStore : SessionStateStoreProviderBase
    {
        /// <summary>
        /// 创建新的Session执行
        /// </summary>
        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);
        }

        /// <summary>
        /// 取值的时候执行
        /// </summary>
        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);
        }

        /// <summary>
        /// 新增 修改 移除键值时执行
        /// </summary>
        /// <param name="item">item.Items为当前所有的键值对</param>
        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, object> Dict;
        public int Timeout;
    }

    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, object> dict = new Dictionary<string, object>(_sessionItems.Count);

            string key;
            NameObjectCollectionBase.KeysCollection keys = _sessionItems.Keys;
            for (int i = 0; i < keys.Count; i++)
            {
                key = keys[i];
                dict.Add(key, _sessionItems[key]);
            }

            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();

                foreach (KeyValuePair<string, object> kvp in item.Dict)
                {
                    collections[kvp.Key] = kvp.Value;
                }

                return new RedisSessionState(collections, null, item.Timeout);
            }
            catch
            {
                return null;
            }
        }
    }
复制代码
使用也很简单,修改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

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=326799285&siteId=291194637