阿里云-云小站(无限量代金券发放中)
【腾讯云】云服务器、云数据库、COS、CDN、短信等热卖云产品特惠抢购

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

135次阅读
没有评论

共计 40103 个字符,预计需要花费 101 分钟才能阅读完成。

上一篇介绍了如何使用 Nginx+IIS 实现负载均衡 部署一个简单的分布式系统,文章结尾留下了几个问题,其中一个是 ” 如何解决多站点下 Session 共享 ”。这篇文章将会介绍如何使用 Redis,下一篇在此基础上实现 Session。

这里特别说明一下,其实没有必要使用 Redis 来解决 Session 共享。Asp.net 提供了 StateServer 模式来共享 Session,这里重复造轮子的目的 1: 熟悉 Redis 的基本知识和使用 2. 学习和巩固 Session 的实现原理。3. 学习 Redis 应用场景

阅读目录

  • Redis 安装配置
  • 五种数据类型使用
  • 封装拓展
  • 总结 

Redis 安装配置

    redis是一个 key-value 存储系统。和 Memcached 类似,它支持存储的 value 类型相对更多,包括 string(字符串)、list(链表)、set(集合)、zset(sorted set – 有序集合)和 hash(哈希类型)。这些数据类型都支持 push/pop、add/remove 及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,redis 支持各种不同方式的排序。与 memcached 一样,为了保证效率,数据都是缓存在内存中。区别的是 redis 会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了 master-slave(主从)同步。

最新版本的 redis 版本为 3.0.3,支持集群功能。我这下载的是 window 版本的,实际场景都是安装在 linux 系统下的。下载地址:redis-2.8.19.rar。更多下载地址:

    官网:http://redis.io/download  MSOpenTech:https://github.com/MSOpenTech/redis  dmajkic:https://github.com/dmajkic/redis/downloads

    下载完成之后解压运行 redis-server.exe 就启动了 redis 了,启动后会在进程里面看到 reids。

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

  1. 读写分离配置

  redis 的读写分离需要修改配置文件,把解压的文件复制了一份。两份文件是一样的,分别命名为 MasterRedis-2.8.19(主 redis 服务),SlaveRedis-2.8.19(从 redis 服务)。redis 默认绑定的是 6379 端口,

我们保持主服务配置不变,修改从服务配置。

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

  • 修改从服务绑定端口(修改时可以直接搜索 port 关键字)

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

  • 修改从服务对应的主服务地址(修改时可以直接搜索 slaveof 关键字)

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

  • 配置文件修改完成以后,分别启动主服务和从服务

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

    从服务启动以后,主服务会发送一条同步的 sync 命令,同步从服务器的缓存数据。

五种数据类型使用

  服务搭建好以后可以使用.net 版本 redis 操作类库 ServiceStack.Redis 来操作 redis,本文会用到以下三个 dll。

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

  初始化 RedisClient 对象

var client = new RedisClient("120.26.197.185", 6379);

 

1.String

    String 是最常用的一种数据类型,普通的 key/value 存储都可以归为此类,value 其实不仅是 String,也可以是数字:比如想知道什么时候封锁一个 IP 地址(访问超过几次)。INCRBY 命令让这些变得很容易,通过原子递增保持计数。

#region "字符串类型"
client.Set<string>("name", "laowang");
string userName = client.Get<string>("name");
Console.WriteLine(userName);

//访问次数
client.Set<int>("IpAccessCount", 0);
//次数递增
client.Incr("IpAccessCount");
Console.WriteLine(client.Get<int>("IpAccessCount"));
#endregion

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

2.Hash

一个 hashid 可以存储多项信息,每一项信息也有自己的 key

client.SetEntryInHash("userInfoId", "name", "zhangsan");
client.SetEntryInHash("userInfoId", "name1", "zhangsan1");
client.SetEntryInHash("userInfoId", "name2", "zhangsan2");
client.SetEntryInHash("userInfoId", "name3", "zhangsan3");
client.GetHashKeys("userInfoId").ForEach(e => Console.WriteLine(e));
client.GetHashValues("userInfoId").ForEach(e => Console.WriteLine(e));

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

3.List

应用场景: 

  •  Redis list 的应用场景非常多,也是 Redis 最重要的数据结构之一。
  •  我们可以轻松地实现最新消息排行等功能。
  •  Lists 的另一个应用就是消息队列,可以利用 Lists 的 PUSH 操作,将任务存在 Lists 中,然后工作线程再用 POP 操作将任务取出进行执行。
#region "List 类型"

client.AddItemToList("userInfoId1", "123");
client.AddItemToList("userInfoId1", "1234");

Console.WriteLine("List 数据项条数:" + client.GetListCount("userInfoId1"));
Console.WriteLine("List 数据项第一条数据:" + client.GetItemFromList("userInfoId1", 0));
Console.WriteLine("List 所有数据");
client.GetAllItemsFromList("userInfoId1").ForEach(e => Console.WriteLine(e));
#endregion

#region "List 类型做为队列和栈使用"
Console.WriteLine(client.GetListCount("userInfoId1"));
//队列先进先出
//Console.WriteLine(client.DequeueItemFromList("userInfoId1"));
//Console.WriteLine(client.DequeueItemFromList("userInfoId1"));

//栈后进先出
Console.WriteLine("出栈"+client.PopItemFromList("userInfoId1"));
Console.WriteLine("出栈"+client.PopItemFromList("userInfoId1"));
#endregion

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

4.Set

应用场景: 

  • Redis set 对外提供的功能与 list 类似是一个列表的功能,特殊之处在于 set 是可以自动排重的,当你需要存储一个列表数据,又不希望出现重复数据时,set 是一个很好的选择,并且 set 提供了判断某个成员是否在一个 set 集合内的重要接口,这个也是 list 所不能提供的。
  • 比如在微博应用中,每个人的好友存在一个集合(set)中,这样求两个人的共同好友的操作,可能就只需要用求交集命令即可。
  • Redis 还为集合提供了求交集、并集、差集等操作,可以非常方便的实 
client.AddItemToSet("A", "B");
client.AddItemToSet("A", "C");
client.AddItemToSet("A", "D");
client.AddItemToSet("A", "E");
client.AddItemToSet("A", "F");

client.AddItemToSet("B", "C");
client.AddItemToSet("B", "F");

//求差集
Console.WriteLine("A,B 集合差集");
client.GetDifferencesFromSet("A", "B").ToList<string>().ForEach(e => Console.Write(e + ","));

//求集合交集
Console.WriteLine("\nA,B 集合交集");
client.GetIntersectFromSets(new string[] { "A", "B" }).ToList<string>().ForEach(e => Console.Write(e + ","));

//求集合并集
Console.WriteLine("\nA,B 集合并集");
client.GetUnionFromSets(new string[] { "A", "B" }).ToList<string>().ForEach(e => Console.Write(e + ","));

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

5.Sort Set(排序)

应用场景:

  • 以某个条件为权重,比如按顶的次数排序. 
  • ZREVRANGE 命令可以用来按照得分来获取前 100 名的用户,ZRANK 可以用来获取用户排名,非常直接而且操作容易。
  • Redis sorted set 的使用场景与 set 类似,区别是 set 不是自动有序的,而 sorted set 可以通过用户额外提供一个优先级 (score) 的参数来为成员排序,并且是插入有序的,即自动排序。
  • 比如:twitter 的 public timeline 可以以发表时间作为 score 来存储,这样获取时就是自动按时间排好序的。
  • 比如: 全班同学成绩的 SortedSets,value 可以是同学的学号,而 score 就可以是其考试得分,这样数据插入集合的,就已经进行了天然的排序。
  • 另外还可以用 Sorted Sets 来做带权重的队列,比如普通消息的 score 为 1,重要消息的 score 为 2,然后工作线程可以选择按 score 的倒序来获取工作任务。让重要的任务优先执行。
#region "有序 Set 操作"
client.AddItemToSortedSet("SA", "B", 2);
client.AddItemToSortedSet("SA", "C", 1);
client.AddItemToSortedSet("SA", "D", 5);
client.AddItemToSortedSet("SA", "E", 3);
client.AddItemToSortedSet("SA", "F", 4);

//有序集合降序排列
Console.WriteLine("\n 有序集合降序排列");
client.GetAllItemsFromSortedSetDesc("SA").ForEach(e => Console.Write(e + ","));
Console.WriteLine("\n 有序集合升序序排列");
client.GetAllItemsFromSortedSet("SA").ForEach(e => Console.Write(e + ","));

client.AddItemToSortedSet("SB", "C", 2);
client.AddItemToSortedSet("SB", "F", 1);
client.AddItemToSortedSet("SB", "D", 3);

Console.WriteLine("\n 获得某个值在有序集合中的排名,按分数的升序排列");
Console.WriteLine(client.GetItemIndexInSortedSet("SB", "D"));

Console.WriteLine("\n 获得有序集合中某个值得分数");
Console.WriteLine(client.GetItemScoreInSortedSet("SB", "D"));

Console.WriteLine("\n 获得有序集合中,某个排名范围的所有值");
client.GetRangeFromSortedSet("SA", 0, 3).ForEach(e => Console.Write(e + ","));

#endregion
 

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

封装拓展

最后提供一份别人写好的 Redis 操作的帮助类,用到了 PooledRedisClientManager 连接池来获取 RedisClient,同时用到了读写分离的概念,可以直接拿来使用

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ServiceStack.Redis;

namespace Com.Redis
{/// <summary>
    /// 来源:http://blog.wx6.org/2013/349.htm
    /// </summary>
    public class RedisBase
    {private static string[] ReadWriteHosts = System.Configuration.ConfigurationSettings.AppSettings["readWriteHosts"].Split(new char[] { ';' });
        private static string[] ReadOnlyHosts = System.Configuration.ConfigurationSettings.AppSettings["readOnlyHosts"].Split(new char[] { ';' });

        #region -- 连接信息 --
        public static PooledRedisClientManager prcm = CreateManager(ReadWriteHosts, ReadOnlyHosts);

        private static PooledRedisClientManager CreateManager(string[] readWriteHosts, string[] readOnlyHosts)
        {// 支持读写分离,均衡负载  
            return new PooledRedisClientManager(readWriteHosts, readOnlyHosts, new RedisClientManagerConfig
            {MaxWritePoolSize = 5, //“写”链接池链接数  
                MaxReadPoolSize = 5, //“读”链接池链接数  
                AutoStart = true,
            });
        }
        #endregion

        #region -- Item --
        /// <summary> 
        /// 设置单体 
        /// </summary> 
        /// <typeparam name="T"></typeparam> 
        /// <param name="key"></param> 
        /// <param name="t"></param> 
        /// <param name="timeSpan"></param> 
        /// <returns></returns> 
        public static bool Item_Set<T>(string key, T t)
        {try
            {using (IRedisClient redis = prcm.GetClient())
                {return redis.Set<T>(key, t, new TimeSpan(1, 0, 0));
                }
            }
            catch (Exception ex)
            {// LogInfo 
            }
            return false;
        }

        /// <summary> 
        /// 获取单体 
        /// </summary> 
        /// <typeparam name="T"></typeparam> 
        /// <param name="key"></param> 
        /// <returns></returns> 
        public static T Item_Get<T>(string key) where T : class
        {using (IRedisClient redis = prcm.GetClient())
            {return redis.Get<T>(key);
            }
        }

        /// <summary> 
        /// 移除单体 
        /// </summary> 
        /// <param name="key"></param> 
        public static bool Item_Remove(string key)
        {using (IRedisClient redis = prcm.GetClient())
            {return redis.Remove(key);
            }
        }

        #endregion

        #region -- List --

        public static void List_Add<T>(string key, T t)
        {using (IRedisClient redis = prcm.GetClient())
            {var redisTypedClient = redis.GetTypedClient<T>();
                redisTypedClient.AddItemToList(redisTypedClient.Lists[key], t);
            }
        }



        public static bool List_Remove<T>(string key, T t)
        {using (IRedisClient redis = prcm.GetClient())
            {var redisTypedClient = redis.GetTypedClient<T>();
                return redisTypedClient.RemoveItemFromList(redisTypedClient.Lists[key], t) > 0;
            }
        }
        public static void List_RemoveAll<T>(string key)
        {using (IRedisClient redis = prcm.GetClient())
            {var redisTypedClient = redis.GetTypedClient<T>();
                redisTypedClient.Lists[key].RemoveAll();}
        }

        public static int List_Count(string key)
        {using (IRedisClient redis = prcm.GetReadOnlyClient())
            {return redis.GetListCount(key);
            }
        }

        public static List<T> List_GetRange<T>(string key, int start, int count)
        {using (IRedisClient redis = prcm.GetReadOnlyClient())
            {var c = redis.GetTypedClient<T>();
                return c.Lists[key].GetRange(start, start + count - 1);
            }
        }


        public static List<T> List_GetList<T>(string key)
        {using (IRedisClient redis = prcm.GetReadOnlyClient())
            {var c = redis.GetTypedClient<T>();
                return c.Lists[key].GetRange(0, c.Lists[key].Count);
            }
        }

        public static List<T> List_GetList<T>(string key, int pageIndex, int pageSize)
        {int start = pageSize * (pageIndex - 1);
            return List_GetRange<T>(key, start, pageSize);
        }

        /// <summary> 
        /// 设置缓存过期 
        /// </summary> 
        /// <param name="key"></param> 
        /// <param name="datetime"></param> 
        public static void List_SetExpire(string key, DateTime datetime)
        {using (IRedisClient redis = prcm.GetClient())
            {redis.ExpireEntryAt(key, datetime);
            }
        }
        #endregion

        #region -- Set --
        public static void Set_Add<T>(string key, T t)
        {using (IRedisClient redis = prcm.GetClient())
            {var redisTypedClient = redis.GetTypedClient<T>();
                redisTypedClient.Sets[key].Add(t);
            }
        }
        public static bool Set_Contains<T>(string key, T t)
        {using (IRedisClient redis = prcm.GetClient())
            {var redisTypedClient = redis.GetTypedClient<T>();
                return redisTypedClient.Sets[key].Contains(t);
            }
        }
        public static bool Set_Remove<T>(string key, T t)
        {using (IRedisClient redis = prcm.GetClient())
            {var redisTypedClient = redis.GetTypedClient<T>();
                return redisTypedClient.Sets[key].Remove(t);
            }
        }
        #endregion

        #region -- Hash --
        /// <summary> 
        /// 判断某个数据是否已经被缓存 
        /// </summary> 
        /// <typeparam name="T"></typeparam> 
        /// <param name="key"></param> 
        /// <param name="dataKey"></param> 
        /// <returns></returns> 
        public static bool Hash_Exist<T>(string key, string dataKey)
        {using (IRedisClient redis = prcm.GetReadOnlyClient())
            {return redis.HashContainsEntry(key, dataKey);
            }
        }

        /// <summary> 
        /// 存储数据到 hash 表 
        /// </summary> 
        /// <typeparam name="T"></typeparam> 
        /// <param name="key"></param> 
        /// <param name="dataKey"></param> 
        /// <returns></returns> 
        public static bool Hash_Set<T>(string key, string dataKey, T t)
        {using (IRedisClient redis = prcm.GetClient())
            {string value = ServiceStack.Text.JsonSerializer.SerializeToString<T>(t);
                return redis.SetEntryInHash(key, dataKey, value);
            }
        }
        /// <summary> 
        /// 移除 hash 中的某值 
        /// </summary> 
        /// <typeparam name="T"></typeparam> 
        /// <param name="key"></param> 
        /// <param name="dataKey"></param> 
        /// <returns></returns> 
        public static bool Hash_Remove(string key, string dataKey)
        {using (IRedisClient redis = prcm.GetClient())
            {return redis.RemoveEntryFromHash(key, dataKey);
            }
        }
        /// <summary> 
        /// 移除整个 hash 
        /// </summary> 
        /// <typeparam name="T"></typeparam> 
        /// <param name="key"></param> 
        /// <param name="dataKey"></param> 
        /// <returns></returns> 
        public static bool Hash_Remove(string key)
        {using (IRedisClient redis = prcm.GetClient())
            {return redis.Remove(key);
            }
        }
        /// <summary> 
        /// 从 hash 表获取数据 
        /// </summary> 
        /// <typeparam name="T"></typeparam> 
        /// <param name="key"></param> 
        /// <param name="dataKey"></param> 
        /// <returns></returns> 
        public static T Hash_Get<T>(string key, string dataKey)
        {using (IRedisClient redis = prcm.GetReadOnlyClient())
            {string value = redis.GetValueFromHash(key, dataKey);
                return ServiceStack.Text.JsonSerializer.DeserializeFromString<T>(value);
            }
        }
        /// <summary> 
        /// 获取整个 hash 的数据 
        /// </summary> 
        /// <typeparam name="T"></typeparam> 
        /// <param name="key"></param> 
        /// <returns></returns> 
        public static List<T> Hash_GetAll<T>(string key)
        {using (IRedisClient redis = prcm.GetReadOnlyClient())
            {var list = redis.GetHashValues(key);
                if (list != null && list.Count > 0)
                {List<T> result = new List<T>();
                    foreach (var item in list)
                    {var value = ServiceStack.Text.JsonSerializer.DeserializeFromString<T>(item);
                        result.Add(value);
                    }
                    return result;
                }
                return null;
            }
        }
        /// <summary> 
        /// 设置缓存过期 
        /// </summary> 
        /// <param name="key"></param> 
        /// <param name="datetime"></param> 
        public static void Hash_SetExpire(string key, DateTime datetime)
        {using (IRedisClient redis = prcm.GetClient())
            {redis.ExpireEntryAt(key, datetime);
            }
        }
        #endregion

        #region -- SortedSet --
        /// <summary> 
        ///  添加数据到 SortedSet 
        /// </summary> 
        /// <typeparam name="T"></typeparam> 
        /// <param name="key"></param> 
        /// <param name="t"></param> 
        /// <param name="score"></param> 
        public static bool SortedSet_Add<T>(string key, T t, double score)
        {using (IRedisClient redis = prcm.GetClient())
            {string value = ServiceStack.Text.JsonSerializer.SerializeToString<T>(t);
                return redis.AddItemToSortedSet(key, value, score);
            }
        }
        /// <summary> 
        /// 移除数据从 SortedSet 
        /// </summary> 
        /// <typeparam name="T"></typeparam> 
        /// <param name="key"></param> 
        /// <param name="t"></param> 
        /// <returns></returns> 
        public static bool SortedSet_Remove<T>(string key, T t)
        {using (IRedisClient redis = prcm.GetClient())
            {string value = ServiceStack.Text.JsonSerializer.SerializeToString<T>(t);
                return redis.RemoveItemFromSortedSet(key, value);
            }
        }
        /// <summary> 
        /// 修剪 SortedSet 
        /// </summary> 
        /// <param name="key"></param> 
        /// <param name="size">保留的条数</param> 
        /// <returns></returns> 
        public static int SortedSet_Trim(string key, int size)
        {using (IRedisClient redis = prcm.GetClient())
            {return redis.RemoveRangeFromSortedSet(key, size, 9999999);
            }
        }
        /// <summary> 
        /// 获取 SortedSet 的长度 
        /// </summary> 
        /// <param name="key"></param> 
        /// <returns></returns> 
        public static int SortedSet_Count(string key)
        {using (IRedisClient redis = prcm.GetReadOnlyClient())
            {return redis.GetSortedSetCount(key);
            }
        }

        /// <summary> 
        /// 获取 SortedSet 的分页数据 
        /// </summary> 
        /// <typeparam name="T"></typeparam> 
        /// <param name="key"></param> 
        /// <param name="pageIndex"></param> 
        /// <param name="pageSize"></param> 
        /// <returns></returns> 
        public static List<T> SortedSet_GetList<T>(string key, int pageIndex, int pageSize)
        {using (IRedisClient redis = prcm.GetReadOnlyClient())
            {var list = redis.GetRangeFromSortedSet(key, (pageIndex - 1) * pageSize, pageIndex * pageSize - 1);
                if (list != null && list.Count > 0)
                {List<T> result = new List<T>();
                    foreach (var item in list)
                    {var data = ServiceStack.Text.JsonSerializer.DeserializeFromString<T>(item);
                        result.Add(data);
                    }
                    return result;
                }
            }
            return null;
        }


        /// <summary> 
        /// 获取 SortedSet 的全部数据 
        /// </summary> 
        /// <typeparam name="T"></typeparam> 
        /// <param name="key"></param> 
        /// <param name="pageIndex"></param> 
        /// <param name="pageSize"></param> 
        /// <returns></returns> 
        public static List<T> SortedSet_GetListALL<T>(string key)
        {using (IRedisClient redis = prcm.GetReadOnlyClient())
            {var list = redis.GetRangeFromSortedSet(key, 0, 9999999);
                if (list != null && list.Count > 0)
                {List<T> result = new List<T>();
                    foreach (var item in list)
                    {var data = ServiceStack.Text.JsonSerializer.DeserializeFromString<T>(item);
                        result.Add(data);
                    }
                    return result;
                }
            }
            return null;
        }

        /// <summary> 
        /// 设置缓存过期 
        /// </summary> 
        /// <param name="key"></param> 
        /// <param name="datetime"></param> 
        public static void SortedSet_SetExpire(string key, DateTime datetime)
        {using (IRedisClient redis = prcm.GetClient())
            {redis.ExpireEntryAt(key, datetime);
            }
        }
        #endregion
    }
}
 

使用很简单,几行代码

//会往主服务里面写入
RedisBase.Hash_Set<string>("PooledRedisClientManager", "one", "123");

//从服务里面读取信息
RedisBase.Hash_Get<string>("PooledRedisClientManager", "one");

  连接池的初始化

private static string[] ReadWriteHosts = System.Configuration.ConfigurationSettings.AppSettings["readWriteHosts"].Split(new char[] { ';' });
        private static string[] ReadOnlyHosts = System.Configuration.ConfigurationSettings.AppSettings["readOnlyHosts"].Split(new char[] { ';' });

        #region -- 连接信息 --
        public static PooledRedisClientManager prcm = CreateManager(ReadWriteHosts, ReadOnlyHosts);

        private static PooledRedisClientManager CreateManager(string[] readWriteHosts, string[] readOnlyHosts)
        {// 支持读写分离,均衡负载  
            return new PooledRedisClientManager(readWriteHosts, readOnlyHosts, new RedisClientManagerConfig
            {MaxWritePoolSize = 5, //“写”链接池链接数  
                MaxReadPoolSize = 5, //“读”链接池链接数  
                AutoStart = true,
            });
        } 

配置文件

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

总结

1. 其实 php,Java 等多种语言都能使用 redis, 在我接触的项目中见到使用 redis 做为消息队列和缓存组件,当然它的功能远不止于此。后面的文章将详细介绍 redis 的几个使用案例。

      2. 可以使用 redis desktop manager 管理工具查看服务器缓存中的数据

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

      本篇文章用到的 redis_demo 资源打包下载地址

百度网盘下载:http://pan.baidu.com/s/1bnjUSLT

或者

—————————————— 分割线 ——————————————

免费下载地址在 http://linux.linuxidc.com/

用户名与密码都是www.linuxidc.com

具体下载目录在 /2015 年资料 / 8 月 /17 日 /Nginx+IIS 实现负载均衡 /

下载方法见 http://www.linuxidc.com/Linux/2013-07/87684.htm

—————————————— 分割线 ——————————————

svn 下载地址:http://code.taobao.org/svn/ResidSessionDemo/

更多详情见请继续阅读下一页的精彩内容:http://www.linuxidc.com/Linux/2015-08/121791p2.htm

上一篇介绍了一些 Redis 的安装及使用步骤,本篇开始将介绍 redis 的实际应用场景, 先从最常见的 session 开始,刚好也重新学习一遍 session 的实现原理。在阅读之前假设你已经会使用 Nginx+IIS 实现负载均衡搭建负载均衡站点了,这里我们会搭建两个站点来验证 redis 实现的 session 是否能共享。

阅读目录

  • Session 实现原理
  • session 共享实现方案
  • 问题拓展
  • 总结

Session 实现原理

session 和 cookie 是我们做 web 开发中常用到的两个对象,它们之间会不会有联系呢?

Cookie 是什么? Cookie 是一小段文本信息,伴随着用户请求和页面在 Web 服务器和浏览器之间传递。Cookie 包含每次用户访问站点时 Web 应用程序都可以读取的信息。(Cookie 会随每次 HTTP 请求一起被传递服务器端,排除 js,css,image 等静态文件,这个过程可以从 fiddler 或者 ie 自带的网络监控里面分析到,考虑性能的化可以从尽量减少 cookie 着手)

Cookie 写入浏览器的过程:我们可以使用如下代码在 Asp.net 项目中写一个 Cookie 并发送到客户端的浏览器(为了简单我没有设置其它属性)。

HttpCookie cookie = new HttpCookie("RedisSessionId", "string value");Response.Cookies.Add(cookie);

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

我们可以看到在服务器写的 cookie,会通过响应头 Set-Cookie 的方式写入到浏览器。

      Session 是什么?Session 我们可以使用它来方便地在服务端保存一些与会话相关的信息。比如常见的登录信息。

      Session 实现原理?HTTP 协议是无状态的,对于一个浏览器发出的多次请求,WEB 服务器无法区分 是不是来源于同一个浏览器。所以服务器为了区分这个过程会通过一个 sessionid 来区分请求,而这个 sessionid 是怎么发送给服务端的呢?前面说了 cookie 会随每次请求发送到服务端,并且 cookie 相对用户是不可见的,用来保存这个 sessionid 是最好不过了,我们通过下面过程来验证一下。

Session["UserId"] = 123;

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

  通过上图再次验证了 session 和 cookie 的关系,服务器产生了一次设置 cookie 的操作,这里的 sessionid 就是用来区分浏览器的。为了实验是区分浏览器的,可以实验在 IE 下进行登录,然后在用 chrome 打开相同页面,你会发现在 chrome 还是需要你登录的,原因是 chrome 这时没有 sessionid。httpOnly 是表示这个 cookie 是不会在浏览器端通过 js 进行操作的,防止人为串改 sessionid。

  asp.net 默认的 sessionid 的键值是 ASP.NET_SessionId, 可以在 web.config 里面修改这个默认配置

<sessionState mode="InProc" cookieName="MySessionId"></sessionState>

服务器端 Session 读取? 服务器端是怎么读取 session 的值呢,Session[“ 键值 ”]。那么问题来了,为什么在 Defaule.aspx.cs 文件里可以获取到这个 Session 对象,这个 Session 对象又是什么时候被初始化的呢。

  为了弄清楚这个问题,我们可以通过转到定义的方式来查看。

  System.Web.UI.Page ->HttpSessionState(Session)

 
protected internal override HttpContext Context {[System.Runtime.TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
  get {if (_context == null) {_context = HttpContext.Current;
       }
       return _context;
    }
 }
 public virtual HttpSessionState Session {get {if (!_sessionRetrieved) {/* try just once to retrieve it */
                _sessionRetrieved = true;

                try {_session = Context.Session;
                }
                catch {//  Just ignore exceptions, return null.
                }
            }

            if (_session == null) {throw new HttpException(SR.GetString(SR.Session_not_enabled));
            }

            return _session;
        }
    }

上面这一段是 Page 对象初始化 Session 对象的,可以看到 Session 的值来源于 HttpContext.Current,而 HttpContext.Current 又是什么时候被初始化的呢,我们接着往下看。

public sealed class HttpContext : IServiceProvider, IPrincipalContainer
    {internal static readonly Assembly SystemWebAssembly = typeof(HttpContext).Assembly;
        private static volatile bool s_eurlSet;
        private static string s_eurl;

        private IHttpAsyncHandler  _asyncAppHandler;   // application as handler (not always HttpApplication)
        private AsyncPreloadModeFlags _asyncPreloadModeFlags;
        private bool               _asyncPreloadModeFlagsSet;
        private HttpApplication    _appInstance;
        private IHttpHandler       _handler;
        [DoNotReset]
        private HttpRequest        _request;
        private HttpResponse       _response;
        private HttpServerUtility  _server;
        private Stack              _traceContextStack;
        private TraceContext       _topTraceContext;
        [DoNotReset]
        private Hashtable          _items;
        private ArrayList          _errors;
        private Exception          _tempError;
        private bool               _errorCleared;
        [DoNotReset]
        private IPrincipalContainer _principalContainer;
        [DoNotReset]
        internal ProfileBase       _Profile;
        [DoNotReset]
        private DateTime           _utcTimestamp;
        [DoNotReset]
        private HttpWorkerRequest  _wr;
        private VirtualPath        _configurationPath;
        internal bool              _skipAuthorization;
        [DoNotReset]
        private CultureInfo        _dynamicCulture;
        [DoNotReset]
        private CultureInfo        _dynamicUICulture;
        private int                _serverExecuteDepth;
        private Stack              _handlerStack;
        private bool               _preventPostback;
        private bool               _runtimeErrorReported;
        private PageInstrumentationService _pageInstrumentationService = null;
        private ReadOnlyCollection<string> _webSocketRequestedProtocols;
}

    我这里只贴出了一部分源码,HttpContext 包含了我们常用的 Request,Response 等对象。HttpContext 得从 ASP.NET 管道说起,以 IIS 6.0 为例,在工作进程 w3wp.exe 中,利用 Aspnet_ispai.dll 加载.NET 运行时(如果.NET 运行时尚未加载)。IIS 6.0 引入了应用程序池的概念,一个工作进程对应着一个应用程序池。一个应用程序池可以承载一个或多个 Web 应用,每个 Web 应用映射到一个 IIS 虚拟目录。与 IIS 5.x 一样,每一个 Web 应用运行在各自的应用程序域中。如果 HTTP.SYS 接收到的 HTTP 请求是对该 Web 应用的第一次访问,在成功加载了运行时后,会通过 AppDomainFactory 为该 Web 应用创建一个应用程序域(AppDomain)。随后,一个特殊的运行时 IsapiRuntime 被加载。IsapiRuntime 定义在程序集 System.Web 中,对应的命名空间为 System.Web.Hosting。IsapiRuntime 会接管该 HTTP 请求。IsapiRuntime 会首先创建一个 IsapiWorkerRequest 对象,用于封装当前的 HTTP 请求,并将该 IsapiWorkerRequest 对象传递给 ASP.NET 运行时:HttpRuntime,从此时起,HTTP 请求正式进入了 ASP.NET 管道。根据 IsapiWorkerRequest 对象,HttpRuntime 会创建用于表示当前 HTTP 请求的上下文(Context)对象:HttpContext。
    至此相信大家对 Session 初始化过程,session 和 cookie 的关系已经很了解了吧,下面开始进行 Session 共享实现方案。

Session 共享实现方案

一.StateServer 方式

这种是 asp.net 提供的一种方式,还有一种是 SQLServer 方式(不一定程序使用的是 SQLServer 数据库,所以通用性不高,这里就不介绍了)。也就是将会话数据存储到单独的内存缓冲区中,再由单独一台机器上运行的 Windows 服务来控制这个缓冲区。状态服务全称是“ASP.NET State Service”(aspnet_state.exe)。它由 Web.config 文件中的 stateConnectionString 属性来配置。该属性指定了服务所在的服务器,以及要监视的端口。

<sessionState mode="StateServer"      stateConnectionString="tcpip=127.0.0.1:42424"     cookieless="false" timeout="20" />

  在这个例子中,状态服务在当前机器的 42424 端口(默认端口)运行。要在服务器上改变端口和开启远程服务器的该功能,可编辑 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\aspnet_state\Parameters 注册表项中的 Port 值和 AllowRemoteConnection 修改成 1。显然,使用状态服务的优点在于进程隔离,并可在多站点中共享。使用这种模式,会话状态的存储将不依赖于 iis 进程的失败或者重启,然而,一旦状态服务中止,所有会话数据都会丢失(这个问题 redis 不会存在,重新了数据不会丢失)。

  这里提供一段 bat 文件帮助修改注册表,可以复制保存为.bat 文件执行

reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\aspnet_state\Parameters" /v "AllowRemoteConnection" /t REG_DWORD  /d 1 /f

reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\aspnet_state\Parameters" /v "Port" /t REG_DWORD  /d 42424 /f

net stop aspnet_state
net start aspnet_state

pause
 

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

二.redis 实现 session 共享

  下面我们将使用 redis 来实现共享,首先要弄清楚 session 的几个关键点, 过期时间,SessionId,一个 SessionId 里面会存在多组 key/value 数据。基于这个特性我将采用 Hash 结构来存储,看看代码实现。用到了上一篇提供的 RedisBase 帮助类。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.SessionState;
using ServiceStack.Redis;
using Com.Redis;

namespace ResidSessionDemo.RedisDemo
{public class RedisSession
    {private HttpContext context;

        public RedisSession(HttpContext context, bool IsReadOnly, int Timeout)
        {this.context = context;
            this.IsReadOnly = IsReadOnly;
            this.Timeout = Timeout;
            //更新缓存过期时间
            RedisBase.Hash_SetExpire(SessionID, DateTime.Now.AddMinutes(Timeout));
        }

        /// <summary>
        /// SessionId 标识符
        /// </summary>
        public static string SessionName = "Redis_SessionId";

        //
        // 摘要:
        //     获取会话状态集合中的项数。//
        // 返回结果:
        //     集合中的项数。
        public int Count
        {get
            {return RedisBase.Hash_GetCount(SessionID);
            }
        }

        //
        // 摘要:
        //     获取一个值,该值指示会话是否为只读。//
        // 返回结果:
        //     如果会话为只读,则为 true;否则为 false。
        public bool IsReadOnly {get; set; }

        //
        // 摘要:
        //     获取会话的唯一标识符。//
        // 返回结果:
        //     唯一会话标识符。
        public string SessionID
        {get
            {return GetSessionID();}
        }

        //
        // 摘要:
        //     获取并设置在会话状态提供程序终止会话之前各请求之间所允许的时间(以分钟为单位)。//
        // 返回结果:
        //     超时期限(以分钟为单位)。
        public int Timeout {get; set; }

        /// <summary>
        /// 获取 SessionID
        /// </summary>
        /// <param name="key">SessionId 标识符</param>
        /// <returns>HttpCookie 值</returns>
        private string GetSessionID()
        {HttpCookie cookie = context.Request.Cookies.Get(SessionName);
            if (cookie == null || string.IsNullOrEmpty(cookie.Value))
            {string newSessionID = Guid.NewGuid().ToString();
                HttpCookie newCookie = new HttpCookie(SessionName, newSessionID);
                newCookie.HttpOnly = IsReadOnly;
                newCookie.Expires = DateTime.Now.AddMinutes(Timeout);
                context.Response.Cookies.Add(newCookie);
                return "Session_"+newSessionID;
            }
            else
            {return "Session_"+cookie.Value;
            }
        }

        //
        // 摘要:
        //     按名称获取或设置会话值。//
        // 参数:
        //   name:
        //     会话值的键名。//
        // 返回结果:
        //     具有指定名称的会话状态值;如果该项不存在,则为 null。
        public object this[string name]
        {get
            {return RedisBase.Hash_Get<object>(SessionID, name);
            }
            set
            {RedisBase.Hash_Set<object>(SessionID, name, value);
            }
        }

        // 摘要:
        //     判断会话中是否存在指定 key
        //
        // 参数:
        //   name:
        //     键值
        //
        public bool IsExistKey(string name)
        {return RedisBase.Hash_Exist<object>(SessionID, name);
        }

        //
        // 摘要:
        //     向会话状态集合添加一个新项。//
        // 参数:
        //   name:
        //     要添加到会话状态集合的项的名称。//
        //   value:
        //     要添加到会话状态集合的项的值。
        public void Add(string name, object value)
        {RedisBase.Hash_Set<object>(SessionID, name, value);
        }
        //
        // 摘要:
        //     从会话状态集合中移除所有的键和值。
        public void Clear()
        {RedisBase.Hash_Remove(SessionID);
        }

        //
        // 摘要:
        //     删除会话状态集合中的项。//
        // 参数:
        //   name:
        //     要从会话状态集合中删除的项的名称。
        public void Remove(string name)
        {RedisBase.Hash_Remove(SessionID,name);
        }
        //
        // 摘要:
        //     从会话状态集合中移除所有的键和值。
        public void RemoveAll()
        {Clear();
        }
    }
}

  下面是实现类似在 cs 文件中能直接使用 Session[“UserId”]的方式,我的 MyPage 类继承 Page 实现了自己的逻辑主要做了两件事  1: 初始化 RedisSession  2: 实现统一登录认证,OnPreInit 方法里面判断用户是否登录,如果没有登录了则跳转到登陆界面

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;

namespace ResidSessionDemo.RedisDemo
{/// <summary>
    /// 自定义 Page 实现以下功能
    /// 1. 初始化 RedisSession
    /// 2. 实现页面登录验证,继承此类,则可以实现所有页面的登录验证
    /// </summary>
    public class MyPage:Page
    {private RedisSession redisSession;

        /// <summary>
        /// RedisSession
        /// </summary>
        public RedisSession RedisSession
        {get
            {if (redisSession == null)
                {redisSession = new RedisSession(Context, true, 20);
                }
                return redisSession;
            }
        }

        protected override void OnPreInit(EventArgs e)
        {base.OnPreInit(e);
            //判断用户是否已经登录,如果未登录,则跳转到登录界面
            if (!RedisSession.IsExistKey("UserCode"))
            {Response.Redirect("Login.aspx");
            }
        }
    }
}

 我们来看看 Default.aspx.cs 是如何使用 RedisSession 的,至此我们实现了和 Asp.netSession 一模一样的功能和使用方式。

RedisSession.Remove("UserCode");

  相比 StateServer,RedisSession 具有以下优点

  1.redis 服务器重启不会丢失数据  2. 可以使用 redis 的读写分离个集群功能更加高效读写数据  

  测试效果,使用 nginx 和 iis 部署两个站点做负载均衡,iis1 地址 127.0.0.1:8002 iis2 地址 127.0.0.1:9000  nginx 代理服务地址 127.0.0.1:8003, 不懂如何配置的可以去阅读我的 Nginx+IIS 实现负载均衡 这篇文章。我们来看一下测试结果。

  访问 127.0.0.1:8003 需要进行登录  用户名为 admin  密码为 123

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

 登录成功以后,重点关注端口号信息

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

 刷新页面,重点关注端口号信息

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

可以尝试直接访问 iis1 地址 127.0.0.1:8002 iis2 地址 127.0.0.1:9000 这两个站点,你会发现都不需要登录了。至此我们的 redis 实现 session 功能算是大功告成了。

问题拓展

使用 redis 实现 session 告一段落,下面留个问题讨论一下方案。微信开发提供了很多接口, 参考下面截图, 可以看到获取 access_token 接口每日最多调用 2000 次,现在大公司提供的很多接口针对不对级别的用户接口访问次数限制都是不一样的,至于做这个限制的原因应该是防止恶意攻击和流量限制之类的。那么我的问题是怎么实现这个接口调用次数限制功能。大家可以发挥想象力参与讨论哦,或许你也会碰到这个问题。

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

先说下我知道的两种方案:

     1. 使用流量整形中的令牌桶算法,大小固定的令牌桶可自行以恒定的速率源源不断地产生令牌。如果令牌不被消耗,或者被消耗的速度小于产生的速度,令牌就会不断地增多,直到把桶填满。后面再产生的令牌就会从桶中溢出。最后桶中可以保存的最大令牌数永远不会超过桶的大小。

说浅显点:比如上面的获取 access_token 接口,一天 2000 次的频率,即 1 次 / 分钟。我们令牌桶容量为 2000,可以使用 redis 最简单的 key/value 来存储 ,key 为用户 id,value 为整形存储还可使用次数,然后使用一个定时器 1 分钟调用 client.Incr(key) 实现次数自增; 用户每访问一次该接口,相应的 client.Decr(key)来减少使用次数。

但是这里存在一个性能问题,这仅仅是针对一个用户来说,假设有 10 万个用户,怎么使用定时器来实现这个自增操作呢,难道是循环 10 万次分别调用 client.Incr(key)吗?这一点没有考虑清楚。

      2. 直接用户访问一次 先进行总次数判断,符合条件再就进行一次自增

      两种方案优缺点比较
  优点 缺点
令牌桶算法 流量控制精确  实现复杂,并且由于控制精确反而在实际应用中有麻烦,很可能用户在晚上到凌晨期间访问接口次数不多,白天访问次数多些。
简单算法 实现简单可行,效率高  流量控制不精确
 

总结

本篇从实际应用讲解了 redis,后面应该还会有几篇继续介绍 redis 实际应用,敬请期待!

Redis 的详细介绍:请点这里
Redis 的下载地址:请点这里

本文永久更新链接地址:http://www.linuxidc.com/Linux/2015-08/121791.htm

上一篇介绍了如何使用 Nginx+IIS 实现负载均衡 部署一个简单的分布式系统,文章结尾留下了几个问题,其中一个是 ” 如何解决多站点下 Session 共享 ”。这篇文章将会介绍如何使用 Redis,下一篇在此基础上实现 Session。

这里特别说明一下,其实没有必要使用 Redis 来解决 Session 共享。Asp.net 提供了 StateServer 模式来共享 Session,这里重复造轮子的目的 1: 熟悉 Redis 的基本知识和使用 2. 学习和巩固 Session 的实现原理。3. 学习 Redis 应用场景

阅读目录

  • Redis 安装配置
  • 五种数据类型使用
  • 封装拓展
  • 总结 

Redis 安装配置

    redis是一个 key-value 存储系统。和 Memcached 类似,它支持存储的 value 类型相对更多,包括 string(字符串)、list(链表)、set(集合)、zset(sorted set – 有序集合)和 hash(哈希类型)。这些数据类型都支持 push/pop、add/remove 及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,redis 支持各种不同方式的排序。与 memcached 一样,为了保证效率,数据都是缓存在内存中。区别的是 redis 会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了 master-slave(主从)同步。

最新版本的 redis 版本为 3.0.3,支持集群功能。我这下载的是 window 版本的,实际场景都是安装在 linux 系统下的。下载地址:redis-2.8.19.rar。更多下载地址:

    官网:http://redis.io/download  MSOpenTech:https://github.com/MSOpenTech/redis  dmajkic:https://github.com/dmajkic/redis/downloads

    下载完成之后解压运行 redis-server.exe 就启动了 redis 了,启动后会在进程里面看到 reids。

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

  1. 读写分离配置

  redis 的读写分离需要修改配置文件,把解压的文件复制了一份。两份文件是一样的,分别命名为 MasterRedis-2.8.19(主 redis 服务),SlaveRedis-2.8.19(从 redis 服务)。redis 默认绑定的是 6379 端口,

我们保持主服务配置不变,修改从服务配置。

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

  • 修改从服务绑定端口(修改时可以直接搜索 port 关键字)

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

  • 修改从服务对应的主服务地址(修改时可以直接搜索 slaveof 关键字)

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

  • 配置文件修改完成以后,分别启动主服务和从服务

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

    从服务启动以后,主服务会发送一条同步的 sync 命令,同步从服务器的缓存数据。

五种数据类型使用

  服务搭建好以后可以使用.net 版本 redis 操作类库 ServiceStack.Redis 来操作 redis,本文会用到以下三个 dll。

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

  初始化 RedisClient 对象

var client = new RedisClient("120.26.197.185", 6379);

 

1.String

    String 是最常用的一种数据类型,普通的 key/value 存储都可以归为此类,value 其实不仅是 String,也可以是数字:比如想知道什么时候封锁一个 IP 地址(访问超过几次)。INCRBY 命令让这些变得很容易,通过原子递增保持计数。

#region "字符串类型"
client.Set<string>("name", "laowang");
string userName = client.Get<string>("name");
Console.WriteLine(userName);

//访问次数
client.Set<int>("IpAccessCount", 0);
//次数递增
client.Incr("IpAccessCount");
Console.WriteLine(client.Get<int>("IpAccessCount"));
#endregion

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

2.Hash

一个 hashid 可以存储多项信息,每一项信息也有自己的 key

client.SetEntryInHash("userInfoId", "name", "zhangsan");
client.SetEntryInHash("userInfoId", "name1", "zhangsan1");
client.SetEntryInHash("userInfoId", "name2", "zhangsan2");
client.SetEntryInHash("userInfoId", "name3", "zhangsan3");
client.GetHashKeys("userInfoId").ForEach(e => Console.WriteLine(e));
client.GetHashValues("userInfoId").ForEach(e => Console.WriteLine(e));

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

3.List

应用场景: 

  •  Redis list 的应用场景非常多,也是 Redis 最重要的数据结构之一。
  •  我们可以轻松地实现最新消息排行等功能。
  •  Lists 的另一个应用就是消息队列,可以利用 Lists 的 PUSH 操作,将任务存在 Lists 中,然后工作线程再用 POP 操作将任务取出进行执行。
#region "List 类型"

client.AddItemToList("userInfoId1", "123");
client.AddItemToList("userInfoId1", "1234");

Console.WriteLine("List 数据项条数:" + client.GetListCount("userInfoId1"));
Console.WriteLine("List 数据项第一条数据:" + client.GetItemFromList("userInfoId1", 0));
Console.WriteLine("List 所有数据");
client.GetAllItemsFromList("userInfoId1").ForEach(e => Console.WriteLine(e));
#endregion

#region "List 类型做为队列和栈使用"
Console.WriteLine(client.GetListCount("userInfoId1"));
//队列先进先出
//Console.WriteLine(client.DequeueItemFromList("userInfoId1"));
//Console.WriteLine(client.DequeueItemFromList("userInfoId1"));

//栈后进先出
Console.WriteLine("出栈"+client.PopItemFromList("userInfoId1"));
Console.WriteLine("出栈"+client.PopItemFromList("userInfoId1"));
#endregion

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

4.Set

应用场景: 

  • Redis set 对外提供的功能与 list 类似是一个列表的功能,特殊之处在于 set 是可以自动排重的,当你需要存储一个列表数据,又不希望出现重复数据时,set 是一个很好的选择,并且 set 提供了判断某个成员是否在一个 set 集合内的重要接口,这个也是 list 所不能提供的。
  • 比如在微博应用中,每个人的好友存在一个集合(set)中,这样求两个人的共同好友的操作,可能就只需要用求交集命令即可。
  • Redis 还为集合提供了求交集、并集、差集等操作,可以非常方便的实 
client.AddItemToSet("A", "B");
client.AddItemToSet("A", "C");
client.AddItemToSet("A", "D");
client.AddItemToSet("A", "E");
client.AddItemToSet("A", "F");

client.AddItemToSet("B", "C");
client.AddItemToSet("B", "F");

//求差集
Console.WriteLine("A,B 集合差集");
client.GetDifferencesFromSet("A", "B").ToList<string>().ForEach(e => Console.Write(e + ","));

//求集合交集
Console.WriteLine("\nA,B 集合交集");
client.GetIntersectFromSets(new string[] { "A", "B" }).ToList<string>().ForEach(e => Console.Write(e + ","));

//求集合并集
Console.WriteLine("\nA,B 集合并集");
client.GetUnionFromSets(new string[] { "A", "B" }).ToList<string>().ForEach(e => Console.Write(e + ","));

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

5.Sort Set(排序)

应用场景:

  • 以某个条件为权重,比如按顶的次数排序. 
  • ZREVRANGE 命令可以用来按照得分来获取前 100 名的用户,ZRANK 可以用来获取用户排名,非常直接而且操作容易。
  • Redis sorted set 的使用场景与 set 类似,区别是 set 不是自动有序的,而 sorted set 可以通过用户额外提供一个优先级 (score) 的参数来为成员排序,并且是插入有序的,即自动排序。
  • 比如:twitter 的 public timeline 可以以发表时间作为 score 来存储,这样获取时就是自动按时间排好序的。
  • 比如: 全班同学成绩的 SortedSets,value 可以是同学的学号,而 score 就可以是其考试得分,这样数据插入集合的,就已经进行了天然的排序。
  • 另外还可以用 Sorted Sets 来做带权重的队列,比如普通消息的 score 为 1,重要消息的 score 为 2,然后工作线程可以选择按 score 的倒序来获取工作任务。让重要的任务优先执行。
#region "有序 Set 操作"
client.AddItemToSortedSet("SA", "B", 2);
client.AddItemToSortedSet("SA", "C", 1);
client.AddItemToSortedSet("SA", "D", 5);
client.AddItemToSortedSet("SA", "E", 3);
client.AddItemToSortedSet("SA", "F", 4);

//有序集合降序排列
Console.WriteLine("\n 有序集合降序排列");
client.GetAllItemsFromSortedSetDesc("SA").ForEach(e => Console.Write(e + ","));
Console.WriteLine("\n 有序集合升序序排列");
client.GetAllItemsFromSortedSet("SA").ForEach(e => Console.Write(e + ","));

client.AddItemToSortedSet("SB", "C", 2);
client.AddItemToSortedSet("SB", "F", 1);
client.AddItemToSortedSet("SB", "D", 3);

Console.WriteLine("\n 获得某个值在有序集合中的排名,按分数的升序排列");
Console.WriteLine(client.GetItemIndexInSortedSet("SB", "D"));

Console.WriteLine("\n 获得有序集合中某个值得分数");
Console.WriteLine(client.GetItemScoreInSortedSet("SB", "D"));

Console.WriteLine("\n 获得有序集合中,某个排名范围的所有值");
client.GetRangeFromSortedSet("SA", 0, 3).ForEach(e => Console.Write(e + ","));

#endregion
 

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

封装拓展

最后提供一份别人写好的 Redis 操作的帮助类,用到了 PooledRedisClientManager 连接池来获取 RedisClient,同时用到了读写分离的概念,可以直接拿来使用

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ServiceStack.Redis;

namespace Com.Redis
{/// <summary>
    /// 来源:http://blog.wx6.org/2013/349.htm
    /// </summary>
    public class RedisBase
    {private static string[] ReadWriteHosts = System.Configuration.ConfigurationSettings.AppSettings["readWriteHosts"].Split(new char[] { ';' });
        private static string[] ReadOnlyHosts = System.Configuration.ConfigurationSettings.AppSettings["readOnlyHosts"].Split(new char[] { ';' });

        #region -- 连接信息 --
        public static PooledRedisClientManager prcm = CreateManager(ReadWriteHosts, ReadOnlyHosts);

        private static PooledRedisClientManager CreateManager(string[] readWriteHosts, string[] readOnlyHosts)
        {// 支持读写分离,均衡负载  
            return new PooledRedisClientManager(readWriteHosts, readOnlyHosts, new RedisClientManagerConfig
            {MaxWritePoolSize = 5, //“写”链接池链接数  
                MaxReadPoolSize = 5, //“读”链接池链接数  
                AutoStart = true,
            });
        }
        #endregion

        #region -- Item --
        /// <summary> 
        /// 设置单体 
        /// </summary> 
        /// <typeparam name="T"></typeparam> 
        /// <param name="key"></param> 
        /// <param name="t"></param> 
        /// <param name="timeSpan"></param> 
        /// <returns></returns> 
        public static bool Item_Set<T>(string key, T t)
        {try
            {using (IRedisClient redis = prcm.GetClient())
                {return redis.Set<T>(key, t, new TimeSpan(1, 0, 0));
                }
            }
            catch (Exception ex)
            {// LogInfo 
            }
            return false;
        }

        /// <summary> 
        /// 获取单体 
        /// </summary> 
        /// <typeparam name="T"></typeparam> 
        /// <param name="key"></param> 
        /// <returns></returns> 
        public static T Item_Get<T>(string key) where T : class
        {using (IRedisClient redis = prcm.GetClient())
            {return redis.Get<T>(key);
            }
        }

        /// <summary> 
        /// 移除单体 
        /// </summary> 
        /// <param name="key"></param> 
        public static bool Item_Remove(string key)
        {using (IRedisClient redis = prcm.GetClient())
            {return redis.Remove(key);
            }
        }

        #endregion

        #region -- List --

        public static void List_Add<T>(string key, T t)
        {using (IRedisClient redis = prcm.GetClient())
            {var redisTypedClient = redis.GetTypedClient<T>();
                redisTypedClient.AddItemToList(redisTypedClient.Lists[key], t);
            }
        }



        public static bool List_Remove<T>(string key, T t)
        {using (IRedisClient redis = prcm.GetClient())
            {var redisTypedClient = redis.GetTypedClient<T>();
                return redisTypedClient.RemoveItemFromList(redisTypedClient.Lists[key], t) > 0;
            }
        }
        public static void List_RemoveAll<T>(string key)
        {using (IRedisClient redis = prcm.GetClient())
            {var redisTypedClient = redis.GetTypedClient<T>();
                redisTypedClient.Lists[key].RemoveAll();}
        }

        public static int List_Count(string key)
        {using (IRedisClient redis = prcm.GetReadOnlyClient())
            {return redis.GetListCount(key);
            }
        }

        public static List<T> List_GetRange<T>(string key, int start, int count)
        {using (IRedisClient redis = prcm.GetReadOnlyClient())
            {var c = redis.GetTypedClient<T>();
                return c.Lists[key].GetRange(start, start + count - 1);
            }
        }


        public static List<T> List_GetList<T>(string key)
        {using (IRedisClient redis = prcm.GetReadOnlyClient())
            {var c = redis.GetTypedClient<T>();
                return c.Lists[key].GetRange(0, c.Lists[key].Count);
            }
        }

        public static List<T> List_GetList<T>(string key, int pageIndex, int pageSize)
        {int start = pageSize * (pageIndex - 1);
            return List_GetRange<T>(key, start, pageSize);
        }

        /// <summary> 
        /// 设置缓存过期 
        /// </summary> 
        /// <param name="key"></param> 
        /// <param name="datetime"></param> 
        public static void List_SetExpire(string key, DateTime datetime)
        {using (IRedisClient redis = prcm.GetClient())
            {redis.ExpireEntryAt(key, datetime);
            }
        }
        #endregion

        #region -- Set --
        public static void Set_Add<T>(string key, T t)
        {using (IRedisClient redis = prcm.GetClient())
            {var redisTypedClient = redis.GetTypedClient<T>();
                redisTypedClient.Sets[key].Add(t);
            }
        }
        public static bool Set_Contains<T>(string key, T t)
        {using (IRedisClient redis = prcm.GetClient())
            {var redisTypedClient = redis.GetTypedClient<T>();
                return redisTypedClient.Sets[key].Contains(t);
            }
        }
        public static bool Set_Remove<T>(string key, T t)
        {using (IRedisClient redis = prcm.GetClient())
            {var redisTypedClient = redis.GetTypedClient<T>();
                return redisTypedClient.Sets[key].Remove(t);
            }
        }
        #endregion

        #region -- Hash --
        /// <summary> 
        /// 判断某个数据是否已经被缓存 
        /// </summary> 
        /// <typeparam name="T"></typeparam> 
        /// <param name="key"></param> 
        /// <param name="dataKey"></param> 
        /// <returns></returns> 
        public static bool Hash_Exist<T>(string key, string dataKey)
        {using (IRedisClient redis = prcm.GetReadOnlyClient())
            {return redis.HashContainsEntry(key, dataKey);
            }
        }

        /// <summary> 
        /// 存储数据到 hash 表 
        /// </summary> 
        /// <typeparam name="T"></typeparam> 
        /// <param name="key"></param> 
        /// <param name="dataKey"></param> 
        /// <returns></returns> 
        public static bool Hash_Set<T>(string key, string dataKey, T t)
        {using (IRedisClient redis = prcm.GetClient())
            {string value = ServiceStack.Text.JsonSerializer.SerializeToString<T>(t);
                return redis.SetEntryInHash(key, dataKey, value);
            }
        }
        /// <summary> 
        /// 移除 hash 中的某值 
        /// </summary> 
        /// <typeparam name="T"></typeparam> 
        /// <param name="key"></param> 
        /// <param name="dataKey"></param> 
        /// <returns></returns> 
        public static bool Hash_Remove(string key, string dataKey)
        {using (IRedisClient redis = prcm.GetClient())
            {return redis.RemoveEntryFromHash(key, dataKey);
            }
        }
        /// <summary> 
        /// 移除整个 hash 
        /// </summary> 
        /// <typeparam name="T"></typeparam> 
        /// <param name="key"></param> 
        /// <param name="dataKey"></param> 
        /// <returns></returns> 
        public static bool Hash_Remove(string key)
        {using (IRedisClient redis = prcm.GetClient())
            {return redis.Remove(key);
            }
        }
        /// <summary> 
        /// 从 hash 表获取数据 
        /// </summary> 
        /// <typeparam name="T"></typeparam> 
        /// <param name="key"></param> 
        /// <param name="dataKey"></param> 
        /// <returns></returns> 
        public static T Hash_Get<T>(string key, string dataKey)
        {using (IRedisClient redis = prcm.GetReadOnlyClient())
            {string value = redis.GetValueFromHash(key, dataKey);
                return ServiceStack.Text.JsonSerializer.DeserializeFromString<T>(value);
            }
        }
        /// <summary> 
        /// 获取整个 hash 的数据 
        /// </summary> 
        /// <typeparam name="T"></typeparam> 
        /// <param name="key"></param> 
        /// <returns></returns> 
        public static List<T> Hash_GetAll<T>(string key)
        {using (IRedisClient redis = prcm.GetReadOnlyClient())
            {var list = redis.GetHashValues(key);
                if (list != null && list.Count > 0)
                {List<T> result = new List<T>();
                    foreach (var item in list)
                    {var value = ServiceStack.Text.JsonSerializer.DeserializeFromString<T>(item);
                        result.Add(value);
                    }
                    return result;
                }
                return null;
            }
        }
        /// <summary> 
        /// 设置缓存过期 
        /// </summary> 
        /// <param name="key"></param> 
        /// <param name="datetime"></param> 
        public static void Hash_SetExpire(string key, DateTime datetime)
        {using (IRedisClient redis = prcm.GetClient())
            {redis.ExpireEntryAt(key, datetime);
            }
        }
        #endregion

        #region -- SortedSet --
        /// <summary> 
        ///  添加数据到 SortedSet 
        /// </summary> 
        /// <typeparam name="T"></typeparam> 
        /// <param name="key"></param> 
        /// <param name="t"></param> 
        /// <param name="score"></param> 
        public static bool SortedSet_Add<T>(string key, T t, double score)
        {using (IRedisClient redis = prcm.GetClient())
            {string value = ServiceStack.Text.JsonSerializer.SerializeToString<T>(t);
                return redis.AddItemToSortedSet(key, value, score);
            }
        }
        /// <summary> 
        /// 移除数据从 SortedSet 
        /// </summary> 
        /// <typeparam name="T"></typeparam> 
        /// <param name="key"></param> 
        /// <param name="t"></param> 
        /// <returns></returns> 
        public static bool SortedSet_Remove<T>(string key, T t)
        {using (IRedisClient redis = prcm.GetClient())
            {string value = ServiceStack.Text.JsonSerializer.SerializeToString<T>(t);
                return redis.RemoveItemFromSortedSet(key, value);
            }
        }
        /// <summary> 
        /// 修剪 SortedSet 
        /// </summary> 
        /// <param name="key"></param> 
        /// <param name="size">保留的条数</param> 
        /// <returns></returns> 
        public static int SortedSet_Trim(string key, int size)
        {using (IRedisClient redis = prcm.GetClient())
            {return redis.RemoveRangeFromSortedSet(key, size, 9999999);
            }
        }
        /// <summary> 
        /// 获取 SortedSet 的长度 
        /// </summary> 
        /// <param name="key"></param> 
        /// <returns></returns> 
        public static int SortedSet_Count(string key)
        {using (IRedisClient redis = prcm.GetReadOnlyClient())
            {return redis.GetSortedSetCount(key);
            }
        }

        /// <summary> 
        /// 获取 SortedSet 的分页数据 
        /// </summary> 
        /// <typeparam name="T"></typeparam> 
        /// <param name="key"></param> 
        /// <param name="pageIndex"></param> 
        /// <param name="pageSize"></param> 
        /// <returns></returns> 
        public static List<T> SortedSet_GetList<T>(string key, int pageIndex, int pageSize)
        {using (IRedisClient redis = prcm.GetReadOnlyClient())
            {var list = redis.GetRangeFromSortedSet(key, (pageIndex - 1) * pageSize, pageIndex * pageSize - 1);
                if (list != null && list.Count > 0)
                {List<T> result = new List<T>();
                    foreach (var item in list)
                    {var data = ServiceStack.Text.JsonSerializer.DeserializeFromString<T>(item);
                        result.Add(data);
                    }
                    return result;
                }
            }
            return null;
        }


        /// <summary> 
        /// 获取 SortedSet 的全部数据 
        /// </summary> 
        /// <typeparam name="T"></typeparam> 
        /// <param name="key"></param> 
        /// <param name="pageIndex"></param> 
        /// <param name="pageSize"></param> 
        /// <returns></returns> 
        public static List<T> SortedSet_GetListALL<T>(string key)
        {using (IRedisClient redis = prcm.GetReadOnlyClient())
            {var list = redis.GetRangeFromSortedSet(key, 0, 9999999);
                if (list != null && list.Count > 0)
                {List<T> result = new List<T>();
                    foreach (var item in list)
                    {var data = ServiceStack.Text.JsonSerializer.DeserializeFromString<T>(item);
                        result.Add(data);
                    }
                    return result;
                }
            }
            return null;
        }

        /// <summary> 
        /// 设置缓存过期 
        /// </summary> 
        /// <param name="key"></param> 
        /// <param name="datetime"></param> 
        public static void SortedSet_SetExpire(string key, DateTime datetime)
        {using (IRedisClient redis = prcm.GetClient())
            {redis.ExpireEntryAt(key, datetime);
            }
        }
        #endregion
    }
}
 

使用很简单,几行代码

//会往主服务里面写入
RedisBase.Hash_Set<string>("PooledRedisClientManager", "one", "123");

//从服务里面读取信息
RedisBase.Hash_Get<string>("PooledRedisClientManager", "one");

  连接池的初始化

private static string[] ReadWriteHosts = System.Configuration.ConfigurationSettings.AppSettings["readWriteHosts"].Split(new char[] { ';' });
        private static string[] ReadOnlyHosts = System.Configuration.ConfigurationSettings.AppSettings["readOnlyHosts"].Split(new char[] { ';' });

        #region -- 连接信息 --
        public static PooledRedisClientManager prcm = CreateManager(ReadWriteHosts, ReadOnlyHosts);

        private static PooledRedisClientManager CreateManager(string[] readWriteHosts, string[] readOnlyHosts)
        {// 支持读写分离,均衡负载  
            return new PooledRedisClientManager(readWriteHosts, readOnlyHosts, new RedisClientManagerConfig
            {MaxWritePoolSize = 5, //“写”链接池链接数  
                MaxReadPoolSize = 5, //“读”链接池链接数  
                AutoStart = true,
            });
        } 

配置文件

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

总结

1. 其实 php,Java 等多种语言都能使用 redis, 在我接触的项目中见到使用 redis 做为消息队列和缓存组件,当然它的功能远不止于此。后面的文章将详细介绍 redis 的几个使用案例。

      2. 可以使用 redis desktop manager 管理工具查看服务器缓存中的数据

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

      本篇文章用到的 redis_demo 资源打包下载地址

百度网盘下载:http://pan.baidu.com/s/1bnjUSLT

或者

—————————————— 分割线 ——————————————

免费下载地址在 http://linux.linuxidc.com/

用户名与密码都是www.linuxidc.com

具体下载目录在 /2015 年资料 / 8 月 /17 日 /Nginx+IIS 实现负载均衡 /

下载方法见 http://www.linuxidc.com/Linux/2013-07/87684.htm

—————————————— 分割线 ——————————————

svn 下载地址:http://code.taobao.org/svn/ResidSessionDemo/

更多详情见请继续阅读下一页的精彩内容:http://www.linuxidc.com/Linux/2015-08/121791p2.htm

正文完
星哥说事-微信公众号
post-qrcode
 0
星锅
版权声明:本站原创文章,由 星锅 于2022-01-20发表,共计40103字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
【腾讯云】推广者专属福利,新客户无门槛领取总价值高达2860元代金券,每种代金券限量500张,先到先得。
阿里云-最新活动爆款每日限量供应
评论(没有评论)
验证码
【腾讯云】云服务器、云数据库、COS、CDN、短信等云产品特惠热卖中