目录
前言:
set
基本命令
交集并集差集
内部编码和应用场景
zset
基本命令
交集并集差集
内部编码和应用场景
应用场景(AI生成)
排行榜系统
应用背景
设计思路
热榜系统
应用背景
设计思路
热度计算方式
总结对比表
前言:
在前几篇Redis的基本数据类型中,我们已经了解了string list hash,并且了解了对应的内部编码和实际的应用场景,也是对于基本的命令操作花了比较多的文字描述的去介绍。那么在本文呢,既然有了前几个类型多个基础,我们学习set和zset也是会比较轻松的了。
废话不多说,我们直接进入主题吧。
set
基本命令
首先,set它是代表集合的意思,那么对于它来说,存在的几个特点有:无序的,不可重复的,那么因为无序,它也就不存在索引的特点。
既然是集合,我们曾经在高中阶段,或者是大学阶段均接触过集合的概念,对于集合来说有的基本操作有交集并集差集,在Redis中也存在这几个操作,所以这里的set虽然说和C++中的set差别比较大,但是和数学中的集合还是挺有相同点的。
sadd:给集合中添加元素,返回值为添加成功了几个元素
因为集合是去重的,所以我们重复添加元素的话,是会添加失败的。
smembers:查看集合中的元素
sismember: 判断元素是否在集合内部
不过sismember只能判断一个元素,其实对于这种命令来说我们甚至可以通过翻译的方式判断它是什么意思,比如sismember,s代表set is的意思代表是 member代表成员,那么sismember的意思就是是否是set的成员。所以我们也没有必要专门记命令。
scard:查询set中有多少个元素
spop:随机删除一个元素
注意,这里的随机是真的随机,我们也可以通过验证的方式判断是否是随机的,第一次是1 4,第二次是1 3,不存在任何的顺序。这其实就有点意思了,随机删除。
至于为什么是随机的呢,可能也和set本身是无序的有关系吧,而且官方文档也明确说了spop是随机的,并且在源码中也是使用的生成随机数的方式的:
既然我们现在提到了无序,我们可以简单引出一个话题,即什么是有序的?
其实对于有序来说,分为有顺序和排名有序的,比如我们拿set来说,有1 2 3 4和4 1 3 2的两种情况的set都是一样的,拿list来说,1 2 3 4和 1 2 4 3的两种不是一样的,因为list是有序的,但是以上我们两种说的有序的都是指的是有顺序的。对于有顺序的话,比如排名一类的,就是有序的,但是这个有序的是指按照某种权重进行排序的,而非顺序上的有序。
以上是对有序这个词的讨论。
smove:将某个元素从source移动到destination
不过要是没有这个元素,也就返回0了。
srem:将某个元素从key中删除。
这里的rem代表的意思是remove,即移除的意思,它的返回值的意思是删除成功的元素的个数。
交集并集差集
对于集合之间的运算涉及到的命令是:sinter,sunion,sdiff,sinterstore,sunionstore,sdiffstore。
sinter:求多个集合的交集
比如sinter key key1就是求两个key之间的交集,我们也可以求多个,不过我们设置的多个集合之间并没有交集,所以什么也没有返回。
sinterstore:求多个集合之间的交集并存在一个集合中
我们倒是发现了一个有意思的点是,key2原本的数据被清空了,只剩下了求完交集之后的元素。以上两个命令的时间复杂度是O(N*M),N和M分别代表的是最小的集合元素个数和最大的集合元素个数。
sunion,sunionstore:求并集元素(并把并集的元素放在另一个集合中)。
同样,还是具备清空的效果,那么对于以上两个命令的时间复杂度是O(N),这个N是总的元素个数。
有意思的是,不管我们如何更换求交集或者并集的set的次序,结果都是不会改变的,而差集不一样,差集存在一定的次序问题,比如sdiff key1 key2 和sdiff key2 key1之间的结果是不同的,因为差集的定义是key1中有的而key2中没有的,反过来肯定就不一样了:
就像这样。
同理,它的时间复杂度还是O(N),因为要遍历所有的元素,但是具体怎么实现的,就是Redis内部源码的实现了,我们后面更新。
以上就是Redis中set的基本命令了。
内部编码和应用场景
对于set来说,它内部的编码方式分为了两种,一种是intset一种是hashtable,如果set内部的元素是整数并且数据个数不多的就是使用的intset,如果是字符串一类的,那么就是hashtable了:
而且我们也能发现,内部编码方式一旦确定了,也是不太好轻易发生修改的。
它的应用场景的话也是非常显然的,比如使用QQ的时候,我们经常会收到某某某和你有多少个共同好友,是否加他为好友?这其实就是set求交集的结果,set也可以用来保存用户的标签,而用户的标签是多样化的,因为千人千面嘛,所以对于用户数据来说很多都是公司是共享的,比如A软件的用户数据有青年,18岁,喜欢看美女,B软件的用户数据有青年,18岁,喜欢跑车,那么就有一种业务是让两个公司对接一下用户数据。
这样用户的互联网画像也就是越来越完整了。
还有一个非常经典的应用场景是:用Set统计PV和UV数据。同学可自行下来探索~
zset
基本命令
对于zset和set来说,zset的特点是有序的,这里的有序代表的就是用权重来进行排序了
就像这张表一样,不同的三国猛将用武将值来进行排序,这样就构成了一个zset,不过因为引入了分数的概念,那么对于zset来说,它的命令操作自然就要复杂的多了。
不过我们首先引入一个问题,因为zset中元素是不允许重复的,分数是可以重复的,如果分数重复了,如何进行排序呢?实际上就按照元素的字典序来进行排序。
既然引入了分数的概念,我们需要认识到一个点是zset存储的是元素,对于分数来说,它只是一个辅助工具而已。
zadd:向集合添加元素,不过这里涉及到了一些选项,比如NX|XX GT|LT INCR等 。
我们一个一个来,先是最普通的应用:
我们插入了之后,可以通过zrange查看,其中如果要带有分数的查看,就加上withscores就行了,这里默认的是升序,咱们虽然说zset是有序的,有序无非是升序和降序,对于zset来说默认的就是升序了。
对于选项,NX和XX 与之前我们学习string的时候有点差别
string是对key存在性的判断,zset是对成员存在性的判断。
因为两个成员都存在,所以自然就添加失败,对于XX来说,就更像是一种更新了:
而对于zadd来说它的返回值是新增了的元素个数(并不包括更新的元素)
对于LT和GT官方的描述是这样的,如果要更新元素,那么LT代表是less than,GT代表的是greater than,如果分数小于原来的就更新或者分数如果大于原来的就更新。
不过不幸的是,GT和LT是6.2之后才有的,我们的版本没到那里,所以我们先了解一下。
接着是CH,它的作用就是更改返回值,因为zadd的返回值是只返回新增加的元素,对于更新的元素是不管的,那么CH就是加上了更新的元素
最后是incr,它其实就是用来单个增加分数的,和之前的incr hincr没啥区别:
它类似的有这个命令
但是zadd已经可以完成了。
这个命令的时间复杂度是logN:
它不像之前的hash list set一样的时间复杂度为O(1),它因为是有序的,并且要找到对应的位置,所以在它内部实现的时候,使用跳表利用有序的特点找到对应的位置。
zcard:查看集合中的所有元素
返回值是有几个元素。
但是因为引入了分数的概念,所以我们也可以使用zcount指定区间查看对应的元素个数:
那么我们想用zcount实现成zcard的效果,我们就可以:
使用inf,inf代表的是无穷大,那么我们指定闭区间为负无穷大到无穷大,就可以完成所有元素的遍历。
它的时间复杂度是O(logN)其中主要是为了找到Min和max对应的位置,然后因为zset内部会记录每个元素当前的次序,找到了之后做个减法,就可以得到对应的结果了。
不过这里有一个比较反人类的设定,如果我们想要设置为开区间,就在想要设置为开区间的元素前面加一个(即可:
zrevrange:逆序遍历,这个是对上面zrange的补充。
但是对应的索引是不变的。
既然我们可以通过索引来查看元素,我们是否也可以通过分数来查看呢?
使用命令zrangebyscore即可
zpopmax:删除最高分数的count个元素
首先我们先记住,它的时间复杂度是O(M * logN),其中N是key中的总元素,M是count的,我们有基础的话,很难不去想对于删除一个有序的特殊位置,比如尾部,它的时间复杂度是logN而不是O(1),这就让人有点疑惑了,因为按照Redis的技术是完全可以的,但是咱也不知道为啥没有优化,可能是技术人员认为优化这里完全没有必要,因为LogN已经很快了,所以在zpopmax的内部还是调用的通用的删除函数:
Bzpopmax:按照阻塞的方式删除最高分数的count个元素
和前面学习的blpop一摸一样的,它也是能够一次性检测多个key。它的时间复杂度是O(M * logN),这个M不是监测了几个key的M,而是在key上删除了元素的key个数。
zpopmin和bzpopmin:(按照阻塞的方式)删除最低分数的count个元素,因为用法几乎是一样的,所以咱在这里啊 也就不演示了。
zrank和zrevrank:从前往后(从后往前)计算对应的排名,对应的时间复杂度是O(logN)
zscore:返回对应member的分数,时间复杂度是O(1),这里的话就是Redis针对进行特殊的优化的,是采取了空间换时间的做法
zrem和zremrangebyrank:(根据排名)删除元素
有趣的是,根据排名的时候是可以使用负数的,-1代表的就是最后一名,不过用法还是从前往后的,时间复杂度是logN + M,其中N是成员总数,M是区间个数。
zincrby:调整某个元素的分数
但是改变了分数之后,整体还是会保持升序的。
交集并集差集
这里涉及到的命令有:zunion,zinter,zdiff,不过这三个命令都是6.2版本才开始支持,我们这里就暂时不讨论,我们这里讨论两个命令,一个是zinterstore,一个是zunionstore。
destination代表的存储到这个key里面,numbers代表的是有几个集合参与运算,weights代表的是权重,虽然说是不同的key进行运算,但是根据实际情况不同的key自己的份量不同,所以运算的时候分数会乘上我们给定的权重,然后就是合并的时候,是总和呢还是最小的还是最大的,就是最后一个参数了,默认是按照总和。
这里使用到了numkeys来指定集合数,就非常像我们之前学习http的时候,报头里面有一个字段是正文部分的长度,如果这里出现了问题,就会导致粘包问题。
就像这样。
主要涉及到的还是score的计算,那你说,zunionstore的使用是不是一样的?完全一样嘛,所以这里就不演示了。
内部编码和应用场景
它的内部编码方式有ziplist和skiplist,如果元素较少,单个元素体积小,就使用ziplist,反之就使用skiplist,不过对于这里的ziplist还是使用的空间换取时间的做法,自然就不敢元素多了还用ziplist了。
明白啦,下面是为博客撰写的纯文字版内容,格式整洁、内容完整,可直接复制粘贴上传到博客平台(如 CSDN、掘金、个人博客等):
应用场景(AI生成)
排行榜系统
应用背景
排行榜常用于游戏、竞赛、学习平台等系统中,主要用来展示用户在某项指标上的相对排名,例如积分榜、活跃度榜、胜率榜等。系统要求能够快速地更新用户得分、获取前几名用户,以及查询某个用户的实时排名。
设计思路
在排行榜中,每个用户对应一个唯一标识(如用户 ID),而其得分则用于排序依据。Redis 的有序集合能够很好地完成这项任务。它会根据用户得分自动维持从低到高的顺序,同时支持查询、更新、分页等操作。
为了方便扩展,不同类型的排行榜可以用不同的 key 名进行管理,比如设置日榜、周榜、总榜等。
热榜系统
应用背景
在内容平台或社区系统中,文章、帖子或视频的“热度”是衡量内容受欢迎程度的重要指标。平台通常会根据内容的浏览量、点赞数、评论量等因素,计算一个热度得分,并据此展示热门内容排行榜。
与普通排行榜不同,文章热度往往涉及多维因素,还可能加入时间衰减,以保持榜单的新鲜感,避免长期霸榜。
设计思路
在 Redis 中,可以将每篇文章的唯一标识作为 ZSet 成员,将计算后的热度值作为排序依据。每当文章发生交互行为时(例如点赞、被浏览、被评论等),系统即可对其热度进行更新。
为了支持多分类内容推荐,也可以为不同的内容类别创建独立的热度榜,例如科技类、娱乐类、教育类等,每个分类对应一个有序集合。
热度计算方式
热度得分通常不是单一数据,而是由多个指标加权计算而来。例如,可以设定一个公式,综合点击量、点赞量、评论量等数据;还可以为旧文章引入时间衰减机制,防止内容因早期流量过大而长期占据榜单。
最终热度值由业务逻辑层计算完成,再写入 Redis 中的有序集合。
总结对比表
功能需求 | 排行榜系统 | 文章热度榜 |
---|---|---|
目标成员 | 用户 ID | 文章 ID |
分数含义 | 积分、活跃度 | 热度(阅读、点赞等加权计算) |
更新频率 | 实时(如游戏加分) | 实时或周期更新(如点赞、点击) |
查询方式 | Top N、用户排名、分页 | 热榜分页、热度值查询 |
清理策略 | 删除低排名用户,节省空间 | 删除冷文章,保持热榜实时性 |
感谢阅读!