Redis远程字典服务器(8)—— zset类型详解

农民  金牌会员 | 2024-10-7 17:51:28 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 873|帖子 873|积分 2619

目录
一,基本情况
二,常用命令
2.1 zadd
2.2 zcard,zcount
2.3 zrange,zrevrange,zrangebyscore
2.4 zpopmax,bzpopmax
2.5 zpopmin,bzpopmin
2.6 zrank,zrevrank,zscore
2.7 zrem,zremrangebyrank,zremrangebyscore
2.8 zincrby
2.9 zinterstore,zunionstore
三,内部编码
四,应用场景
4.1 排行榜系统


一,基本情况

Zset,叫做“有序聚集”


  • Set(聚集):唯一性,无序性(孙行者,行者孙,者行孙 ==> 同一只猴 )
  • List(列表):有序性,可重复(孙行者,行者孙,者行孙 ==> 不同的猴)
  • Zset(有序聚集):这里的“有序”,单指 “升序/降序 ”
   问题:那么排序的顺序是啥?
  解答:我们给 zset 中的 member 同时引入了一个属性 => 分数(score),浮点类型,进行排序的时间,就是按照此处的分数大小来进行 “ 升序/降序 ”的,如下图:
  
 留意: “升序/降序”只是为了解释“有序”这个词的广泛性,现实上zset内部还是按照“升序”来排列的
  zset也是一个聚集,以是member仍旧要求是唯一的,score可以重复,因为 zset主要还是用来存member的,score只是辅助 

  数据结构
是否答应重复元素是否有序有序依据应用场景list(列表)是是索引下标时间轴,消息队列等set(聚集)否否标签,外交等zset(有序聚集)否是分数排行榜系统,外交等 二,常用命令

2.1 zadd

  1. ZADD key [NX | XX] [GT | LT] [CH] [INCR] score member [score member
  2.   ...]
复制代码
zadd作用是往聚会合添加或修改元素和分数,首先指定key,方括号为可选选项,score为添加的分数,member为添加的值,其中 member 和 score 称为是一个“pair”类型,类似于C++里的std:pair,类似于键值对,但又不是键值对,因为对于有序聚集来说,是既可以通过member找到score,也可以通过score找到member的。
C++的pair使用可以参考:C++——map和set的基本使用_c++中关联式容器-CSDN博客
   然后是对于方括号内里选项的解释:
  

  • NX:当member不存在的时间,添加新member,如果member存在什么都不做
  • XX:当member存在的时间,更新member的值,如果member不存在什么都不做
  • 不加 NX|XX 时:如果当前member不存在,此时就会达到“添加新member”的效果,如果当前member已经存在,就会更新分数
  • LT:在要更新分数的条件下,如果新分数比旧分数小,就更新成功,否则不更新,不会阻止添加新member
  • GT:在要更新分数的条件下,如果新分数比旧分数大,就更新成功,否则不更新,不会阻止添加新member
  • CH:“ C ” 是“ change ”的缩写,作用是指定返回值要返回什么信息,本来zadd返回的是新增元素的个数,添加CH选项后返回被修改的元素个数
  • INCR:作用是能针对现有的member的score进行运算
    问题:之前的Hash,Set,List 很多时间,添加一个元素的时间复杂度都是O(1),但为什么Zset是O(logN)?
  解答:因为zset是有序结构,以是新增元素时需要计算合适的位置再添加,当然之以是是logN不是N,也是使用了有序如许的特定。
  但是也有分数类似的情况,以是当分数类似时,再按照元素自身的字典序来排列,分数不同仍旧按照分数来排
  下面是zadd的使用:
 但是上面的只能表现member,不表现score,以是我们可以在背面加上“ withscores ”选项使其表现分数:

我们也可以使用zadd修改对应member的分数:
 如果修改的分数影响到了之前的顺序,就会主动移动元素的位置,从而保持原有的升序顺序不变:

最后我们使用以下zadd的选项,NX|XX 作用和之前一样,这里就不先容了:


   留意:
  

  • LT和GT选项要在高版本的Redis才会有,但是使用起来也非常简单
  • C++中的std:set也可以存pair,同时也是有序的,但是和Redis的set最大的不同是,std:set不能修改,只能添加或者删除
  2.2 zcard,zcount

  1. ZCARD key
  2. ZCOUNT key min max
复制代码


  • zcard作用是获取zset里元素的个数
  • zcount作用是找出score符合区间[min, max]的member的个数,以返回值表现,[min, max]是闭区间:

上面的是闭区间,如果我们想清除界限值,需要用括号,但是这里的写法有点奇怪,如下图:

   问题:一般来说,一个好的设计,是符合直觉的,因为如许学习和使用成本就越低;但是像这个添加括号就显着不符合直觉,那么为什么不改呢?
  解答:既然已经如许设定了,只能将错就错,服从如许的规则了,因为需要思量兼容性,只要新的Redis一改,大量的代码直接就不能用了,需要大量的修改,成本是非常非常高的,会导致大量的服务器直接罢工
  

  • C++近来火起来的原因,就是因为C++兼容C,而当代大多数的热门操作系统都是用C写的
  • 而且其实为了解决IPv4地点不敷的问题,我国早就开始高IPv6了,而且几近成熟,但是仍旧没有大量推广,就是因为这玩意儿是和设备绑定的,要换IPv6,就需要摒弃IPv4的设备,引入IPv6的设备,这其实是一件非常耗时烧钱耗精力的事变,需要恒久逐步推进
  zcount的时间复杂度是 O(logN)
   问题:假设是先根据min和max找到对应的元素,如果要进行一个遍历,是不是就知道这里的元素个数了呢? 
  解答:那样的话时间复杂度就是O(N)了,就不是O(logN)了,根据找min和max就是O(logN)了,再加上遍历就是O(logN + M)了,M是区间中元素的个数,N是整个有序聚集的个数
  现实上Zset内部,会纪录每个元素当前的 “排行” / “次序”,查询到元素,就直接知道了元素所在的“次序”,就可以直接把max对应的元素次序和min对应的元素次序做减法即可。 
   留意:zcount的min和max是支持分数的,以是可以写成浮点数,而且其中还有两个特别值:inf表现无穷大,-inf表现负无穷大
  

  负无穷大不是无穷小,无穷小应该指的是无穷靠近于0的数
  2.3 zrange,zrevrange,zrangebyscore



  • zrange作用是检察指定的一对下标构成的区间的值,前面已经用过很多次了
  • zrevrange,z背面的三个字母“ rev ”其实是“ reverse ”的缩写,意思为“ 逆序 ”,以是zrevrange作用是按照分数降序进行遍历
  • 前面两个都是按照下标区间来找元素的,zrangebyscore作用是按照分数来找元素的,和zcount类似:

   
 留意:zrangebyscor已经标志成“废弃”,大概在Redis 6.2.0 之后废弃,并且功能会合并到zrange中
  2.4 zpopmax,bzpopmax

  1. zpopmax key [count]
复制代码
  1. BZPOPMAX key [key ...] timeout
复制代码



  • zpopmax作用是删除并返回分数最高的count个元素,就是 topK 问题,如果存在多个分数类似的元素,同时为最大值,zpopmax仍旧只删除其中一个 
  • 咱们这里的“有序聚集”也可以视为一个“优先级队列”,有的时间,也需要一个带有“阻塞功能”的优先级队列,就可以用bzpopmax实现阻塞功能,timeout表现超时时间,其他的具体使用和细节再list的blpop和brpop已经讲解过,这里不再赘述:Redis远程字典服务器(6) —— list类型详解-CSDN博客

   引出:zpopmax的时间复杂度是O(logN + M),其中N是有序聚集的元素个数,M表现coun他,要删除的元素个数;此处zpopmax删除最大的元素,由于聚集是有序的,最大值相当于最后一个元素,也就相当于“尾删”
  问题:既然是尾删,那为什么不把这最后一个元素的位置特别纪录下来,省去了查找的过程,后续删除不就可以O(1)了吗?
  解答:可以做到,但是Redis没有这么做,而且现实上,在Redis源码中,针对有序聚集,确实是纪录了“尾部”如许的特定位置,但是现实删除的时间,是直接调用了一个“通用的删除函数”,给定一个member的值,进行查找,找到位置在删除
  以是是存在优化空间的,但是优化一般是要先找到性能瓶颈的,再针对性的优化
  bzpopmax的效果和blpop一样,这里就不展示了:Redis远程字典服务器(6) —— list类型详解-CSDN博客 
2.5 zpopmin,bzpopmin

  1. ZPOPMIN key [count]
复制代码
  1. BZPOPMIN key [key ...] timeout
复制代码
 zpopmin就是和zpopmax反过来的,删除最小的count个元素:

此处zpopming和zpopmax的逻辑是一致的,bzpopmin也是一样的,删除的时间是使用的通用的删除函数,以是有了查找的过程,时间复杂度是O(logN + M) ,M是要删除的个数,以是可以不记,最后的时间复杂度可以为O(logN)
2.6 zrank,zrevrank,zscore



  • zrank作用是获取指定元素的排名,这个“排名”就是“下标”
  • zrevrank作用也是获取member的下标,但是是反着算的
  • zscore作用是查询指定member的分数
zrank: 
 zrank得到的下标是从前往后(升序)算的。
zrevrank

 zscore

   问题:zscore明显也有“根据member找score”的查询操作,为什么它的时间复杂度是O(1)?
  解答:此处可以认为是Redis对查询操作做了特别优化,付出了额外的空间代价,针对这里进行优化到了O(1)的,因为Redis的设计者发现这个查询操作是“高频”的,以是做了针对性的优化
  2.7 zrem,zremrangebyrank,zremrangebyscore



  • zrem作用是删除指定的member,时间复杂度尾O(logN * M),N是整个zset的元素个数,M是指定的member的个数
  • zremrangebyrank作用是范围式删除,通过在命令背面带上一个范围,对这个范围进行删除,是闭区间
  • zremrangebyscore作用也是范围式删除,上面的是指定下标,这个就是指定score的范围删除
这三个命令联合解释能很快明白和使用,就不进行演示了~
背面两个命令的时间复杂度都是O(logN + M)  
2.8 zincrby

  1. zincrby key increment member
复制代码
作用是对指定member的score进行一个加法的操作,可以通过加负数来实现减法的效果 

这个命令的效果和zadd的INCR选项效果是一样的 
2.9 zinterstore,zunionstore

在学习set类型的时间,我们直到聚集操作还有交集,并集,差集,对应的命令是sinter,zunion,zdiff,那么zset有序聚集有没有呢?有是有,因为这几个命令是从Redis 6.2 版本才开始支持的,咱们现在是5版本,临时不涉及
临时zset还是提供了两个命令求聚集间操作的:
  1. zinterstore destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX]
复制代码
  1. zunionstore destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX]
复制代码


  • zinterstore作用是求交集,结构保存到另一个key中
  • zunionstore作用是求并集,结构保存到另一个key中
下面解释下选项:


  • destination:最后的结果存储到哪个key对应的zset中
  • numkeys:就是一个整数,形貌了后续有几个key参与聚集间运算,参加这个的原因是因为背面还有其他的选项,Redis解析命令的时间,需要知道有多少个key参与运算,避免选项和key混淆(此处的设定很像HTTP协议报头的“Content-Length”,形貌了正文的长度,避免了“粘包问题”)
  •  weights:权重,咱们这个聚集是有序聚集,带有分数,以是现在好几个有序聚集做操作,它们的职位不肯定对等;权重相当于一个系数,会乘以当前的分数,具体使用可以观看背面的截图
  • aggregate:zset求交集时,不仅仅只思量member,它还有score,如果member类似,score不同,以是需要一个选项来处置惩罚score,就三种方式:“SUM求和”,“MIN取小的值”,“MAX取大的值”,通过这三种方式对score合并
zinterstore: 
 然后我们可以带上权重,可以写成小数:

 最后的AGGREGATE的对score三种操作都很简单,这里就不演示了~
 zunionstore:求并集和求交集步骤一样:

   对于zinterstore的时间复杂度
  


  • N表现输入若干个有序聚集内里元素最少的个数
  • K表现有序聚集的个数
  • M表现终极结果的有序聚集元素的个数
这个东西取决于Redis的源码咋写的,我们临时不思量~
表达式太复杂了,我们可以简化以下得到近似值:


  • 首先K不会很多,近似看作1
  • 再化简以下,可以认为N和M是靠近的(不严谨,只是近似来看),
  • 可以化简为O(M) + O(M * logM) ==> O(M * logM)
    对于zunionstore的时间复杂度
  
 除了N变为了:参与运算的zset的个数,其他的和上面一致
三,内部编码



  • ziplist(压缩列表):当有序聚集的元素个数小于 zset-max-ziplist-entries 配置(默认 128 个), 同时每个元素的值都小于 zset-max-ziplist-value 配置(默认 64 字节)时,Redis 会用ziplist 来作 为有序聚集的内部实现,ziplist 可以有用淘汰内存的使用。
  • skiplist(跳表):当 ziplist 条件不满意时,有序聚集会使用 skiplist 作为内部实现,因为此时 ziplist 的操作效率会下降。
关于跳表skiplist的结构可以参考下这道标题:LCR 154. 复杂链表的复制 - 力扣(LeetCode) 
要具体了解跳表,还是得联合源码来看,这里不做过多讨论 
四,应用场景

4.1 排行榜系统

最关键的应用场景就是这个了,好比应用热搜,游戏积分,成绩排行等等。
关键要点就是:用来排行的“分数”是频繁变化的,因此我们就需要在实时变化的环境下高效地更新排行,以是使用zset来实现就非常简单,因为可以高效地修改“分数”,排行顺序也能主动调整(logN)
   Thousands(千):1KB
  million(百万):1MB
  billion(十亿):1GB 
  对于游戏排行榜之类的,它的前后顺序就非常容易确定,但是有的排行榜就要复杂很多,好比某博热度(欣赏量,点赞量,转发量,批评数),但是对于这部门,一般的大公司都有专门的团队来做这块的事变(通过一些人工智能的方式来进行计算),根据每个维度。计算得到综合得分 ==> 热度
   总结:zset只是一个选择,不是说非得用zset,有些场景确实可以用到有序聚集,但是不方便使用Redis,就可以思量使用其他方式的有序聚集 

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

农民

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表