zoukankan      html  css  js  c++  java
  • 用Redis实现Session功能

    【原文】http://www.cnblogs.com/ddyq/p/3151284.html

    0.什么是Redis

    Redis是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API

    ---维基百科

    1.与其他用户状态保存方案比较

    一般开发中用户状态使用session或者cookie,两种方式各种利弊。

    Session:在InProc模式下容易丢失,并且引起并发问题。如果使用SQLServer或者SQLServer模式又消耗了性能

    Cookie则容易将一些用户信息暴露,加解密同样也消耗了性能。

    Redis采用这样的方案解决了几个问题,

    1.Redis存取速度快。

    2.用户数据不容易丢失。

    3.用户多的情况下容易支持集群。

    4.能够查看在线用户。

    5.能够实现用户一处登录。(通过代码实现,后续介绍)

    6.支持持久化。(当然可能没什么用)

    2.实现思路

    1.我们知道session其实是在cookie中保存了一个sessionid,用户每次访问都将sessionid发给服务器,服务器通过ID查找用户对应的状态数据。

    在这里我的处理方式也是在cookie中定义一个sessionid,程序需要取得用户状态时将sessionid做为key在Redis中查找。

    2.同时session支持用户在一定时间不访问将session回收。

    借用Redis中Keys支持过期时间的特性支持这个功能,但是在续期方面需要程序自行拦截请求调用这个方法(demo有例子)

    下面开始代码说明

    3.Redis调用接口

    首先引用ServiceStack相关DLL。

    在web.config添加配置,这个配置用来设置Redis调用地址每台服务用【,】隔开。主机写在第一位

    1 <appSettings>
    2 
    3     <!--每台Redis之间用,分割.第一个必须为主机-->
    4     <add key="SessionRedis" value="127.0.0.1:6384,127.0.0.1:6384"/>
    5 
    6 </appSettings>

    初始化配置

    static Managers()
            {
                string sessionRedis= ConfigurationManager.AppSettings["SessionRedis"];
                string timeOut = ConfigurationManager.AppSettings["SessionRedisTimeOut"];
    
                if (string.IsNullOrEmpty(sessionRedis))
                {
                    throw new Exception("web.config 缺少配置SessionRedis,每台Redis之间用,分割.第一个必须为主机");
                }
    
                if (string.IsNullOrEmpty(timeOut)==false)
                {
                    TimeOut = Convert.ToInt32(timeOut);
                }
    
                var host = sessionRedis.Split(char.Parse(","));
                var writeHost = new string[] { host[0] };
                var readHosts = host.Skip(1).ToArray();
    
                ClientManagers = new PooledRedisClientManager(writeHost, readHosts, new RedisClientManagerConfig
                {
                    MaxWritePoolSize = writeReadCount,//“写”链接池链接数
                    MaxReadPoolSize = writeReadCount,//“读”链接池链接数
                    AutoStart = true
                });
            }

    为了控制方便写了一个委托

     /// <summary>
            /// 写入
            /// </summary>
            /// <typeparam name="F"></typeparam>
            /// <param name="doWrite"></param>
            /// <returns></returns>
            public F TryRedisWrite<F>(Func<IRedisClient, F> doWrite)
            {
                PooledRedisClientManager prcm = new Managers().GetClientManagers();
                IRedisClient client = null;
                try
                {
                    using (client = prcm.GetClient())
                    {
                        return doWrite(client);
                    }
                }
                catch (RedisException)
                {
                    throw new Exception("Redis写入异常.Host:" + client.Host + ",Port:" + client.Port);
                }
                finally
                {
                    if (client != null)
                    {
                        client.Dispose();
                    }
                }
            }

     一个调用的例子其他的具体看源码

            /// <summary>
            /// 以Key/Value的形式存储对象到缓存中
            /// </summary>
            /// <typeparam name="T">对象类别</typeparam>
            /// <param name="value">要写入的集合</param>
            public void KSet(Dictionary<string, T> value)
            {
                Func<IRedisClient, bool> fun = (IRedisClient client) =>
                {
                    client.SetAll<T>(value);
                    return true;
                };
    
                TryRedisWrite(fun);
            }

    4.实现Session

    按上面说的给cookie写一个sessionid

        /// <summary>
        /// 用户状态管理
        /// </summary>
        public class Session
        {
            /// <summary>
            /// 初始化
            /// </summary>
            /// <param name="_context"></param>
            public Session(HttpContextBase _context)
            {
                var context = _context;
                var cookie = context.Request.Cookies.Get(SessionName);
                if (cookie == null || string.IsNullOrEmpty(cookie.Value))
                {
                    SessionId = NewGuid();
                    context.Response.Cookies.Add(new HttpCookie(SessionName, SessionId));
                    context.Request.Cookies.Add(new HttpCookie(SessionName, SessionId));
                }
                else
                {
                    SessionId = cookie.Value;
                }
            }
    
        }

    去存取用户的方法

            /// <summary>
            /// 获取当前用户信息
            /// </summary>
            /// <typeparam name="T"></typeparam>
            /// <returns></returns>
            public object Get<T>() where T:class,new()
            {
                return new RedisClient<T>().KGet(SessionId);
            }
    
            /// <summary>
            /// 用户是否在线
            /// </summary>
            /// <returns></returns>
            public bool IsLogin()
            {
                return new RedisClient<object>().KIsExist(SessionId);
            }
    
            /// <summary>
            /// 登录
            /// </summary>
            /// <typeparam name="T"></typeparam>
            /// <param name="obj"></param>
            public void Login<T>(T obj) where T : class,new()
            {
                new RedisClient<T>().KSet(SessionId, obj, new TimeSpan(0, Managers.TimeOut, 0));
            }

    6.续期

    默认用户没访问超过30分钟注销用户的登录状态,所以用户每次访问都要将用户的注销时间推迟30分钟

    这需要调用Redis的续期方法

            /// <summary>
            /// 延期
            /// </summary>
            /// <param name="key"></param>
            /// <param name="expiresTime"></param>
            public void KSetEntryIn(string key, TimeSpan expiresTime)
            {
                Func<IRedisClient, bool> fun = (IRedisClient client) =>
                {
                    client.ExpireEntryIn(key, expiresTime);
                    return false;
                };
    
                TryRedisWrite(fun);
            }

     
    封装以后
    /// <summary>
    /// 续期
    /// </summary>
    public void Postpone()
    {
    new RedisClient<object>().KSetEntryIn(SessionId, new TimeSpan(0, Managers.TimeOut, 0));
    }
    
     
    这里我利用了MVC3中的ActionFilter,拦截用户的所有请求

    namespace Test
    {
        public class SessionFilterAttribute : ActionFilterAttribute
        {
            /// <summary>
            /// 每次请求都续期
            /// </summary>
            /// <param name="filterContext"></param>
            public override void OnActionExecuting(ActionExecutingContext filterContext)
            {
                new Session(filterContext.HttpContext).Postpone();
            }
        }
    }

    在Global.asax中要注册一下

    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
            {
                filters.Add(new SessionFilterAttribute());
            }
    
            protected void Application_Start()
            {
                RegisterGlobalFilters(GlobalFilters.Filters);
            }
     

    5.调用方式

    为了方便调用借用4.0中的新特性,把Controller添加一个扩展属性

    public static class ExtSessions
    {public static Session SessionExt(this Controller controller)
        {
            return new Session(controller.HttpContext);
        }
    }

    调用方法

        public class HomeController : Controller
        {
            public ActionResult Index()
            {
                this.SessionExt().IsLogin();
                return View();
            }
        }

    6.代码下载

    点击下载

    7.后续

    SessionManager包含 获取用户列表数量,注销某个用户,根据用户ID获取用户信息,在线用户对象列表,在线用户SessionId列表等方法

    后续将实现用户一处登录功能


    <script type="text/javascript"><!-- google_ad_client = "ca-pub-1944176156128447"; /* cnblogs 首页横幅 */ google_ad_slot = "5419468456"; google_ad_width = 728; google_ad_height = 90; //--></script><script type="text/javascript" src="http://pagead2.googlesyndication.com/pagead/show_ads.js"></script>

  • 相关阅读:
    0593. Valid Square (M)
    0832. Flipping an Image (E)
    1026. Maximum Difference Between Node and Ancestor (M)
    0563. Binary Tree Tilt (E)
    0445. Add Two Numbers II (M)
    1283. Find the Smallest Divisor Given a Threshold (M)
    C Primer Plus note9
    C Primer Plus note8
    C Primer Plus note7
    C Primer Plus note6
  • 原文地址:https://www.cnblogs.com/licin/p/5960726.html
Copyright © 2011-2022 走看看