分布式中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>
    /// 使用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

  

如果,您认为阅读这篇博客让您有些收获,不妨点击一下右下角的【推荐】按钮。
如果,您希望更容易地发现我的新博客,不妨点击一下绿色通道的【关注我】。
因为,我的写作热情也离不开您的肯定支持。

感谢您的阅读,如果您对我的博客所讲述的内容有兴趣,请继续关注我的后续博客,我是焰尾迭 。

时间: 2024-08-10 02:10:33

分布式中Redis实现Session终结篇的相关文章

PHP分布式中Redis实现Session

方法一:找到配置文件php.ini,修改为下面内容,保存并重启服务 session.save_handler = redis session.save_path = "tcp://127.0.0.1:6379" 方法二:直接在代码中加入以下内容: ini_set("session.save_handler", "redis"); ini_set("session.save_path", "tcp://127.0.0.

JavaScript中的正则表达式(终结篇)

JavaScript中的正则表达式(终结篇) 在之前的几篇文章中,我们了解了正则表达式的基本语法,但那些语法不是针对于某一个特定语言的.这篇博文我们将通过下面几个部分来了解正则表达式在JavaScript中的使用: JavaScript对正则表达式的支持程度 支持正则表达式的RegExp类型 RegExp的实例属性 RegExp的实例方法 RegExp的构造函数属性 简单的应用 第一部分:JavaScript对正则表达式的支持程度 之前我介绍了正则表达式的基本语法,如果大家不是很了解可以先看下面

C# Azure 存储-分布式缓存Redis在session中的配置

1. 开始 对于分布式的缓存,平常的session的处理是一个用户对应一台分布式的机器,如果这台机器中途挂机或者不能处理这个用户session的情况发生,则此用户的session会丢失,会发生不可预知的错误.如下图: 如果用Redis的分布式缓存,则能避免上面的情况.因为session是保存在Redis中,不会有丢失的情况,就算中途有服务器A挂掉.如下图: 2. 代码 1)在包管理器中,输入下面的包,安装 Install-Package Microsoft.Web.RedisSessionSta

分布式中使用Redis实现Session共享(转)

上一篇介绍了如何使用nginx+iis部署一个简单的分布式系统,文章结尾留下了几个问题,其中一个是"如何解决多站点下Session共享".这篇文章将会介绍如何使用Redis,下一篇在此基础上实现Session. 这里特别说明一下,其实没有必要使用Redis来解决Session共享.Asp.net提供了StateServer模式来共享Session,这里重复造轮子的目的1:熟悉Redis的基本知识和使用 2.学习和巩固Session的实现原理. 3.学习Redis应用场景 阅读目录 Re

分布式中使用Redis实现Session共享(一)

上一篇介绍了如何使用nginx+iis部署一个简单的分布式系统,文章结尾留下了几个问题,其中一个是"如何解决多站点下Session共享".这篇文章将会介绍如何使用Redis,下一篇在此基础上实现Session. 这里特别说明一下,其实没有必要使用Redis来解决Session共享.Asp.net提供了StateServer模式来共享Session,这里重复造轮子的目的1:熟悉Redis的基本知识和使用 2.学习和巩固Session的实现原理. 3.学习Redis应用场景 阅读目录 Re

分布式中使用Redis实现Session共享(二)

上一篇介绍了一些redis的安装及使用步骤,本篇开始将介绍redis的实际应用场景,先从最常见的session开始,刚好也重新学习一遍session的实现原理.在阅读之前假设你已经会使用nginx+iis实现负载均衡搭建负载均衡站点了,这里我们会搭建两个站点来验证redis实现的session是否能共享. 阅读目录 Session实现原理 session共享实现方案 问题拓展 总结 回到顶部 Session实现原理 session和cookie是我们做web开发中常用到的两个对象,它们之间会不会

项目分布式部署那些事(1):ONS消息队列、基于Redis的Session共享,开源共享

因业务发展需要现在的系统不足以支撑现在的用户量,于是我们在一周之前着手项目的性能优化与分布式部署的相关动作. 概况 现在的系统是基于RabbitHub(一套开源的开发时框架)和Rabbit.WeiXin(开源的微信开发SDK)开发的一款微信应用类系统,主要业务是围绕当下流行的微信元素,如:微官网.微商城.微分销.营销活动.会员卡等. 关于RabbitHub详情请戳: .NET 平台下的插件化开发内核(Rabbit Kernel) RabbitHub开源情况及计划 关于Rabbit.WeiXin详

java 分布式系统 - 依据redis实现session共享机制 - 初级篇

一.session的基本应用 大家认识session,可能更多是在java web中浏览器和服务器之间的会话交互.那么为什么要有session? 比如你登录一个系统,每次都要输入个人信息,是不是觉得不方便或者是很烦,首先http协议本身是没有状态可言的(同一个会话的2个请求没有识别能力,2个请求都互相不了解,它们都是由最新实例化的环境进行解析,除了应用本身已经存储在全局对象中所有信息,该环境不会保存与会话相关的任何信息.)所以客户端就使用cookie,服务器使用session,典型的应用是解决重

一篇文章让你深透理解cookie和session,附带分布式WEB系统redis共享session方案

cookie和session有什么区别?这是一个很基础的知识点,大家可能都知道一个大概:cookie是存在客户端的,session是存储在服务端,cookie和session用来验证识别用户的登录状态,常见适用场景:用户登录,用户购物车数据等.偶然一次开发中遇到这些基础的知识,还要去baidu一下,今天就做一个完整的记录,便于以后查阅. 1.基础概念 cookie存储在客户端电脑中一般在:C:\Users\***\AppData\Local\Microsoft\Windows\Temporary