第二节: Redis之Set类型和SortedSet类型的介绍和案例应用

一. Set类型基础

1. 类型说明

  1个key→多个value,value的值不重复!

  Set一种无序且元素内容不重复的集合,不用做重复性判断了,和我们数学中的集合概念相同,可以对多个集合求交集、并集、差集,key可以理解成集合的名字。

注:set 用哈希表来保持字符串的唯一性,没有先后顺序,是按照自己的一个存储方式来遍历,因为没有保存插入顺序。

2. 常用指令Api说明

3. 常用Api说明

(1).SetAdd:添加数据,可以单独1个key-1个value,也可以1个key-多个value添加

(2).SetLength:求key集合的数量

(3).SetContains:判断key集合中是否包含指定值

(4).SetRandomMember:随机获取指定key集合中的一个值或n个值

(5).SetMembers:获取key中的所有值,数据类型要一致,便于存储

(6).SetRemove:删除key集合中的指定value(1个或多个)

(7).SetPop:随机删除指定key集合中的一个值或n个值,并返回这些值。

(8).SetCombine:求多个元素的交并差

  a.SetOperation.Intersect:交集

  b.SetOperation.Union:并集

  c.SetOperation.Difference:差集

(9).SetCombineAndStore:把多个元素的交并差,放到一个新的元素集合中

 代码分享:

 1             //1.添加,value不重复,添加多个Marren1也只有一个
 2             //单个
 3             db.SetAdd("Keen", "Marren1");
 4             db.SetAdd("Keen", "Marren2");
 5             db.SetAdd("Keen", "Marren3");
 6             db.SetAdd("Keen", "Marren2");
 7             db.SetAdd("Keen", "Marren1");
 8             //多个
 9             string[] arryList = { "Marren4", "Marren5", "Marren6", "Marren3" };
10             RedisValue[] valueList = arryList.Select(u => (RedisValue)u).ToArray();
11             db.SetAdd("Keen", valueList);
12 
13             //2. 获取key集合值的数量
14             long d1 = db.SetLength("Keen");
15 
16             //3. 判断key集合中是否包含指定值
17             bool d2 = db.SetContains("Keen", "Marren2");
18 
19             //4. 随机获取key集合中的一个值
20             var d3 = db.SetRandomMember("Keen");
21             //随机获取key集合中的3个值
22             var d33 = db.SetRandomMembers("Keen", 3).Select(u => (string)u).ToList();
23 
24             //5. 获取key中的所有值,数据类型要一致
25             var rValue = db.SetMembers("Keen");
26             List<string> d4 = new List<string>();
27             foreach (var item in rValue)
28             {
29                 d4.Add(item);
30             }
31 
32             //6. 删除key集合中的指定value
33             //单个value
34             bool d5 = db.SetRemove("Keen", "Marren1");
35             //多个value
36             string[] dDelList = { "Marren2", "Marren3", };
37             RedisValue[] DelList = dDelList.Select(u => (RedisValue)u).ToArray();
38             long d6 = db.SetRemove("Keen", DelList);
39 
40             //7. 随机删除key集合中的一个值,并返回该值
41             var d7 = db.SetPop("Keen");
42             //随机删除key集合中的2个值,并返回这2个值
43             var d77 = db.SetPop("Keen", 2).Select(u => (string)u).ToList();
44 
45 
46             //8. 获取几个集合的交集、并集、差集(重点)
47             //准备数据
48             db.SetAdd("ypf", "h1");
49             db.SetAdd("ypf", "h2");
50             db.SetAdd("ypf", "h3");
51             db.SetAdd("ypf", "h4");
52             db.SetAdd("maru", "h4");
53             db.SetAdd("maru", "h5");
54             db.SetAdd("maru", "h6");
55             //下面求两个元素的交并差,也可以求多个
56             string[] arry = { "maru", "ypf" };
57             RedisKey[] keyList = arry.Select(u => (RedisKey)u).ToArray();
58             //交集(共同的部分 h4)
59             var d8 = db.SetCombine(SetOperation.Intersect, keyList).Select(u => (string)u).ToList();
60             //并集(加到一起,去重, h1-h6)
61             var d9 = db.SetCombine(SetOperation.Union, keyList).Select(u => (string)u).ToList();
62             //差集(差集有两个,上面的是h5 h6, 如果颠倒maru和ypf顺序,差集是h1 h2 h3)
63             var d10 = db.SetCombine(SetOperation.Difference, keyList).Select(u => (string)u).ToList();
64 
65             //获取交集并存到key=ypf1,返回集合元素的个数
66             long d11 = db.SetCombineAndStore(SetOperation.Intersect, "ypf1", keyList);
67             //获取并集并存到key=ypf2 ,返回集合元素的个数
68             long d12 = db.SetCombineAndStore(SetOperation.Union, "ypf2", keyList);
69             //获取差集并存到key=ypf3 ,返回集合元素的个数
70             long d13 = db.SetCombineAndStore(SetOperation.Union, "ypf3", keyList);
View Code

 

二. Set类型案例

1.抽奖

(1) 背景:用户参与抽奖,抽奖大致分两类:

 A:只抽1次,1次抽n个人。

 B:抽多次,比如三等奖抽3名,二等奖抽2名,一等奖抽1名。

(2) 技术分析:

主要利用Set结构元素的不重复性和获取随机数的方法来实现,以“specialPrize”为key,参与用户的id当做value

A:用户点击参与抽奖则执行SetAdd方法。

B:可以获取所有参与用户SetMembers 和 判断某个用户是否参与抽奖了SetContains

C:随机抽一次奖用SetRandomMember或SetRandomMembers,因为不需要删除

      多次抽奖用SetPop,因为抽完要删掉。

(3) 代码分析

 1           //1.模拟用户参与抽奖
 2             for (int i = 100; i <= 115; i++)
 3             {
 4                 db.SetAdd("specialPrize", i.ToString());
 5             }
 6             //2. 获取所有的参与抽奖的用户
 7             var data = db.SetMembers("specialPrize").Select(u => (string)u).ToList();
 8             string idList = "";
 9             foreach (var item in data)
10             {
11                 idList = idList + "," + item;
12             }
13             Console.WriteLine($"参与抽奖的用户有:{idList}");
14             //3. 判断用户103是否参与抽奖了
15             var data2 = db.SetContains("specialPrize", "103");
16             if (data2 == true)
17             {
18                 Console.WriteLine($"用户103参与了抽奖");
19             }
20             else
21             {
22                 Console.WriteLine($"用户103没有参与抽奖");
23             }
24             //4. 抽奖
25             //4.1 只抽一次奖,抽奖人数为两名
26             {
27                 var d1 = db.SetRandomMembers("specialPrize", 2).Select(u => (string)u).ToList();
28                 foreach (var item in d1)
29                 {
30                     Console.WriteLine($"获奖用户为:{item}");
31                 }
32             }
33             //4.2 抽三次奖
34             {
35                 var d1 = db.SetPop("specialPrize", 3).Select(u => (string)u).ToList();
36                 foreach (var item in d1)
37                 {
38                     Console.WriteLine($"三等奖用户为:{item}");
39                 }
40                 var d2 = db.SetPop("specialPrize", 2).Select(u => (string)u).ToList();
41                 foreach (var item in d2)
42                 {
43                     Console.WriteLine($"二等奖用户为:{item}");
44                 }
45                 var d3 = db.SetPop("specialPrize", 1).Select(u => (string)u).ToList();
46                 foreach (var item in d3)
47                 {
48                     Console.WriteLine($"一等奖用户为:{item}");
49                 }
50             }
View Code

2. 微信或微博中消息的点赞(或者某篇文章的收藏)

(1). 背景

  微信朋友圈用户A的某条消息的点赞功能,要实现点赞、取消点赞、获取点赞列表、获取点赞用户数量、判断某用户是否点赞过。

(2). 技术分析

  利用Set结构, 以用户Id-消息id作为key,点赞过该消息的用户id作为value。

  A:点赞 SetAdd方法

  B:取消点赞 SetRemove方法

  C:获取点赞列表 SetMembers方法

  D:获取点赞用户数量 SetLength方法

  E:判断某用户是否点赞过 SetContains方法

  该案例容易理解,此处不写代码了。

3.关注模型

(1).背景

  比如微博关注或者共同好友的问题,以微博关注为例,要实现:同时关注、关注的和、关注A的用户中也关注B的、当A进入B页面,求可能认识的人。

(2). 技术分析

利用Set结构,一个博主对应一个Set结构,博主的id作为key,关注该博主的用户id作为value。

A:关注和取消关注: SetAdd方法 和 SetRemove方法

B:同时关注:求交集

C:关注的和:求并集

D:关注A的用户中也关注B的:遍历A中的用户,利用SetContains判断是否B中也存在

E:当A进入B页面,求可能认识的人:这里指的是关注B中的用户 扣去 里面也关注A的用户,就是A可能认识的人。

  求差集:B-A

(3). 代码分享

 1             //关注lmr的用户有:
 2             db.SetAdd("lmr", "小1");
 3             db.SetAdd("lmr", "小2");
 4             db.SetAdd("lmr", "小3");
 5             db.SetAdd("lmr", "小4");
 6             db.SetAdd("lmr", "小5");
 7             db.SetAdd("lmr", "rbp");
 8 
 9             //关注ypf的用户有:
10             db.SetAdd("ypf", "小4");
11             db.SetAdd("ypf", "小5");
12             db.SetAdd("ypf", "小6");
13             db.SetAdd("ypf", "小7");
14             db.SetAdd("ypf", "小8");
15             db.SetAdd("ypf", "rbp");
16 
17             //同时关注lmr和ypf的用户有:
18             string[] arry1 = { "lmr", "ypf" };
19             RedisKey[] keyList1 = arry1.Select(u => (RedisKey)u).ToArray();
20             var d1 = db.SetCombine(SetOperation.Intersect, keyList1).Select(u => (string)u).ToList();  //交集
21             foreach (var item in d1)
22             {
23                 Console.WriteLine("同时关注lmr和ypf的用户有:" + item);
24             }
25 
26             //关注lmr和ypf的用户有(需要去重):
27             string[] arry2 = { "lmr", "ypf" };
28             RedisKey[] keyList2 = arry2.Select(u => (RedisKey)u).ToArray();
29             var d2 = db.SetCombine(SetOperation.Union, keyList2).Select(u => (string)u).ToList();  //并集
30             foreach (var item in d2)
31             {
32                 Console.WriteLine("关注lmr和ypf的用户有:" + item);
33             }
34 
35             //关注lmr的人中也关注ypf的有:
36             var d3 = db.SetMembers("lmr").Select(u => (string)u).ToList();
37             foreach (var item in d3)
38             {
39                 var isExist = db.SetContains("ypf", item);
40                 if (isExist)
41                 {
42                     Console.WriteLine("关注lmr的人中也关注ypf的有:" + item);
43                 }
44             }
45 
46             //当ypf进入lmr的页面,显示可能认识的人(应该显示:小1,小2,小3)
47             string[] arry4 = { "lmr", "ypf" };  // lmr-ypf
48             RedisKey[] keyList4 = arry4.Select(u => (RedisKey)u).ToArray();
49             var d4 = db.SetCombine(SetOperation.Difference, keyList4).Select(u => (string)u).ToList();  //差集 lmr-ypf
50             foreach (var item in d4)
51             {
52                 Console.WriteLine("当ypf进入lmr的页面,显示可能认识的人:" + item);
53             } 
View Code

4. 利用唯一性,可以统计访问网站的所有IP

 

三. SortedSet类型基础

1. 类型说明

  将Set中的元素增加了一个权重参数 score,使得集合中的元素能够按 score 进行有序排列, 同样元素名称也不能重复 三个字段:key-member-score, key键,member值,score:权重或者打分值

2. 常用Api

(1).SortedSetAdd:增加,可以一次增加一个member,也可以一次增加多个member

(2).SortedSetIncrement 和 SortedSetDecrement:Score值自增或自减,如果不存在这member值,则执行增加操作,并返回当前Score值。

(3).获取相关

  SortedSetRangeByRank:根据索引获取member值,默认是升序,可以获取指定索引内的member值

  SortedSetRangeByScore:根据score获取member值,默认是升序,可以获取指定score开始和结束的member值,后面的skip和take用于分页

  SortedSetRangeByValue:根据member获取member值,默认是升序,可以获取指定member开始和结束的值,后面的skip和take用于分页

  SortedSetRangeByRankWithScores:获取member和score值,可以只返回 start-stop 这个索引排序内的值(默认升序),后面的skip和take用于分页

  SortedSetScore:获取指定key指定member的score值

  SortedSetLength:获取集合的数量

(4).删除相关

  SortedSetRemove:删除指定key和指定member,member可以是1个或多个

  SortedSetRemoveRangeByRank:删除指定索引开始到结束

  SortedSetRemoveRangeByScore:删除指定分开始到结束 (5分-8分)

  SortedSetRemoveRangeByValue::删除指定起始值和结束值(这里指定是member)

 代码分享:

  1             //1.增加
  2             //1.1 SortedSetAdd(RedisKey key, RedisValue member, double score),如果member之前存在,则会覆盖前面的score
  3             db.SortedSetAdd("一年级", "ypf1", 1);
  4             for (int i = 2; i < 10; i++)
  5             {
  6                 db.SortedSetAdd("一年级", "ypf" + i, i);
  7             }
  8             //db.SortedSetAdd("一年级", "ypf1", 120);   会覆盖前面的score
  9 
 10             //1.2 score自增(刚开始如果没有这个member,会默认添加进去)
 11             var dd1 = db.SortedSetIncrement("一年级", "ypf", 1);
 12             //1.3 Score自减(刚开始如果没有这个member,会默认添加进去)
 13             var dd2 = db.SortedSetDecrement("一年级", "ypf", 3);
 14 
 15             //2.获取
 16             {
 17                 //2.1 SortedSetRangeByRank:获取的是member值,可以只返回 start-stop 这个排序内的值
 18                 //2.1.1 默认是升序获取所有member值
 19                 var d1 = db.SortedSetRangeByRank("一年级");
 20                 string[] d1Arry1 = d1.Select(u => (string)u).ToArray();
 21 
 22                 //2.1.2 降序获取所有member值
 23                 var d2 = db.SortedSetRangeByRank("一年级", 0, -1, Order.Descending);
 24                 string[] d1Arry2 = d2.Select(u => (string)u).ToArray();
 25 
 26                 //2.1.3 降序获取排名前4的member值
 27                 var d3 = db.SortedSetRangeByRank("一年级", 0, 3, Order.Descending);
 28                 string[] d1Arry3 = d3.Select(u => (string)u).ToArray();
 29             }
 30             {
 31                 //2.2 SortedSetRangeByScore:获取的是member值,可以只返回 start-stop 这个score内的值,后面的skip和take用于分页
 32                 //SortedSetRangeByScore(RedisKey key, double start = double.NegativeInfinity, double stop = double.PositiveInfinity, Exclude exclude = Exclude.None, Order order = Order.Ascending, long skip = 0, long take = -1, CommandFlags flags = CommandFlags.None);
 33                 //2.2.1 默认是升序获取所有member值
 34                 var d1 = db.SortedSetRangeByScore("一年级");
 35                 string[] d1Arry1 = d1.Select(u => (string)u).ToArray();
 36 
 37                 //2.2.2 降序获取所有member值
 38                 var d2 = db.SortedSetRangeByScore("一年级", double.NegativeInfinity, double.PositiveInfinity, Exclude.None, Order.Descending);
 39                 string[] d1Arry2 = d2.Select(u => (string)u).ToArray();
 40 
 41                 //2.2.3 降序获取score在【2,6】分内的值
 42                 var d3 = db.SortedSetRangeByScore("一年级", 2, 6, Exclude.None, Order.Descending);
 43                 string[] d1Arry3 = d3.Select(u => (string)u).ToArray();
 44             }
 45             {
 46                 //2.3 SortedSetRangeByValue:获取的是member值,可以只返回 member开始到结束的值,后面的skip和take用于分页
 47                 //2.3.1 默认是升序获取所有score值
 48                 var d1 = db.SortedSetRangeByValue("一年级");
 49                 string[] d1Arry1 = d1.Select(u => (string)u).ToArray();
 50 
 51                 //2.3.2 降序获取member在【ypf2,ypf6】分内的值
 52                 var d3 = db.SortedSetRangeByValue("一年级", "ypf2", "ypf6", Exclude.None, Order.Descending);
 53                 string[] d1Arry3 = d3.Select(u => (string)u).ToArray();
 54             }
 55 
 56             {
 57                 //2.4 SortedSetRangeByRankWithScores:获取member和score值,可以只返回 start-stop 这个索引排序内的值,后面的skip和take用于分页
 58                 //2.4.1 默认是升序
 59                 SortedSetEntry[] d1 = db.SortedSetRangeByRankWithScores("一年级");
 60                 Dictionary<string, double> dic = new Dictionary<string, double>();
 61                 foreach (var item in d1)
 62                 {
 63                     dic.Add(item.Element, item.Score);
 64                 }
 65                 //2.4.2 降序获取所有
 66                 SortedSetEntry[] d2 = db.SortedSetRangeByRankWithScores("一年级", 0, -1, Order.Descending);
 67                 Dictionary<string, double> dic2 = new Dictionary<string, double>();
 68                 foreach (var item in d2)
 69                 {
 70                     dic2.Add(item.Element, item.Score);
 71                 }
 72                 //2.4.3 降序获取排名前4的member和score值
 73                 SortedSetEntry[] d3 = db.SortedSetRangeByRankWithScores("一年级", 0, 3, Order.Descending);
 74                 Dictionary<string, double> dic3 = new Dictionary<string, double>();
 75                 foreach (var item in d3)
 76                 {
 77                     dic3.Add(item.Element, item.Score);
 78                 }
 79             }
 80             {
 81                 //2.5 获取指定key指定member的score值
 82                 var d1 = db.SortedSetScore("一年级", "ypf2");
 83             }  
 84             //3. 获取集合的数量
 85             long l1 = db.SortedSetLength("一年级");
 86 
 87 
 88             //4. 删除
 89             //{
 90             //    //4.1.SortedSetRemove:删除指定key和指定member,member可以是1个或多个
 91             //    bool num1 = db.SortedSetRemove("一年级", "ypf1");
 92             //    string[] arry1 = { "ypf2", "ypf3", "ypf4" };
 93             //    RedisValue[] newArry1 = arry1.Select(u => (RedisValue)u).ToArray();
 94             //    long num2 = db.SortedSetRemove("一年级", newArry1);
 95             //}
 96 
 97             //{
 98             //    //4.2.SortedSetRemoveRangeByRank:删除指定索引开始到结束
 99             //    long num = db.SortedSetRemoveRangeByRank("一年级", 0, 2);
100             //}
101 
102             //{
103             //    //4.3.SortedSetRemoveRangeByScore:删除指定分开始到结束  (5分-8分)
104             //    long num = db.SortedSetRemoveRangeByScore("一年级", 5, 8);
105             //}
106 
107             //{
108             //    //4.4.SortedSetRemoveRangeByValue:删除指定起始值和结束值(这里指定是member)
109             //    long num = db.SortedSetRemoveRangeByValue("一年级", "ypf3", "ypf6");
110             //}

 

四. SortedSet案例

1. 热词搜索

(1). 需求:

  统计某个网站热词搜索,并实时显示前5名的词及其搜索次数。

PS:实时显示无非就是每隔几秒查询一次,大部分情况我们不建议直接去Redis里查排名,可以把前五名的相关数据存储到Redis的String结构中, 设置5分钟过期。

下面的案例不考虑上面的PS情况,不考虑IP限制问题(前面访问量案例做过),仅仅做最简单的热词搜索,刷新显示

(2).技术分析:

  利用Redis中SortedSet(key-member-score)这一数据结构,利用SortedSetIncrement方法的原子性,每搜索一个词,Score值加1(member不存在则执行的是增加操作), 然后再利用SortedSetRangeByRankWithScores方法获取Score值前五的memeber和Score数据

代码分享:

        /// <summary>
        /// 热词搜索页面
        /// </summary>
        /// <returns></returns>
        public IActionResult Index()
        {
            //获取排名前5的数据
            var data = _redis.SortedSetRangeByRankWithScores("hotWord", 0, 4, Order.Descending);
            Dictionary<string, double> dic3 = new Dictionary<string, double>();
            foreach (var item in data)
            {
                dic3.Add(item.Element, item.Score);
            }
            ViewBag.data = dic3;

            return View();
        }

        /// <summary>
        /// 查询接口
        /// </summary>
        /// <param name="word"></param>
        /// <returns></returns>
        public string HotSearch(string word)
        {
            try
            {
                _redis.SortedSetIncrement("hotWord", word, 1);
                return "ok";
            }
            catch (Exception ex)
            {
                return "error";
            }
        }

2.宝宝投票

  分析:和上面的热词原理一样,利用SortedSet的1个key对应多个不重复的 member-score,key用来存储一个标记,比如叫做“AllChildren”,代表该标记下存储宝宝的投票情况,member可以存储

宝宝的标记id,score存储该宝宝的投票数量.

同样原理:利用SortedSetIncrement进行自增存储,利用SortedSetRangeByRankWithScores获取排名情况

3.高积分用户排行榜

   类似案例:主播-粉丝刷礼物的排行榜(同时看主播的排行和每个主播下面粉丝的排行), 原理和上面都一样

 

 

 总结: 

  SortedSet和String利用其自增自减(并返回当前值)原子性均可以实现计数器的作用,String是针对单个,Sorted是针对某个类别下的多个或每一个,并且实现排序功能。 Hash类型也能实现某个类别下多个物品的计数,但它不具有排序功能。

 

 

 

 

 

 

!

  • 作       者 : Yaopengfei(姚鹏飞)
  • 博客地址 : http://www.cnblogs.com/yaopengfei/
  • 声     明1 : 本人才疏学浅,用郭德纲的话说“我是一个小学生”,如有错误,欢迎讨论,请勿谩骂^_^。
  • 声     明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。
 
posted @ 2019-11-26 15:58  Yaopengfei  阅读(1744)  评论(1编辑  收藏  举报