第二节: 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);
二. 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 }
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 }
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 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。