Redis中的zset类型

zset有序集合介绍

Question:这里的有序单纯就是升序/降序,那么排序的规则是啥


Answer:给zset中的member同时引入了一个属性,分数(score),浮点类型,每个member都会安排一个分数,进行排序的时候,就是按照此处的分数大小来进行升序/降序来排序


  • zset中的member仍然要求是唯一的(score则可以重复)
  • zset主要还是用来存member的,score只是辅助

zset常用命令

zadd

  • 添加或者更新指定的元素以及关联的分数到 zset 中,分数应该符合 double 类型,+inf/-inf 作为正负 极限也是合法的。
  • 时间复杂度为O(logN),N表示有序集合中的元素个数,之前的Hash,Set,List很多时候,添加元素,都是O(1),此处,zset则是logN:由于zset是有序结构,要求新增的元素,要放到合适的位置上,同样logN也是利用了有序性(跳表)

ZADD 的相关选项:

  • XX:仅仅⽤于更新已经存在的元素,不会添加新元素(member)。
  • NX:仅⽤于添加新元素,不会更新已经存在的元素(member);如果当前member不存在,此时就会达到"添加新member"的效果,如果当前member已经存在,此时就会更新分数
  • CH:默认情况下,ZADD 返回的是本次添加的元素个数,但指定这个选项之后,就会还包含本次更新的元素的个数。
  • INCR:此时命令类似 ZINCRBY 的效果,将元素的分数加上指定的分数。此时只能指定⼀个元素和 分数
  • LT:只更新已经存在的元素,如果这个元素(要插入的元素)比现在的元素要小,那么就不会添加新的元素
  • GT:只更新已经存在的元素,如果这个元素比现在的元素要大,那么就不会添加新的元素

添加的时候,既要添加元素,又要添加分数:member和score称为是一个"pair"

不要把member和score理解成"键值对",可以理解为属性:对于有序集合来说,既可以通过member来匹配score,也可以通过score来找到member

Question:如果有重复的score,如何解决?


Answer:通过分数来排序,如果分数相同,再按照元素自身字符串的字典序来排序

有序集合排序规则,zset内部就是按照升序方式来排序的

zrange 查看有序集合中的元素详情,类似于lrange,可以指定一对下标构成的区间

这里的有序集合本身就是有先后顺序的,谁在前谁在后,都是很明确的

redis内部存储数据的时候,是按照二进制的方式来存储的,这就意味着redis服务器不负责"字符编码",要把二进制字节对应汉字,还需要客户端来支持,即在启动客户端时使用 --raw命令即可
Question:为什么这样排序?


Answer:因为是按照升序的结构来排序的

修改操作

如果修改的分数,影响到了之前的顺序,就会自动的移动元素位置,保持原有的升序不变

zadd在默认情况下就是返回新增元素的个数

如果已经存在了就不支持这种行为

如果是xx就支持这种存在修改行为

如果不存在就不支持这种行为

ch操作

incr

zcard

获取⼀个 zset 的基数(cardinality),即 zset 中的元素个数

时间复杂度:O(1) 返回值:zset 内的元素个数(member)。

zcount

  • 返回分数在 min 和 max 之间的元素个数,默认情况下,min 和 max 都是包含的,可以通过 ( 排除。
  • 时间复杂度:O(log(N)) :先根据min找到对应的元素,再根据max找到对应的元素,如果进行一个便利,是不是就知道这里的元素个数了呢?如果元素较多,此时要进行遍历,那么时间复杂度为O(N),zset内部会记录每个元素当前的次序,查询到元素就直接知道元素所在的"次序",就可以直接把max对应的元素次序和min对应的元素次序,减法即可
  • min和max是可以写成浮点数的(zset本身就是浮点数),在浮点数中,存在两个特殊的数值:inf(无穷大),-inf(负无穷大)

返回值:满⾜条件的元素列表个数。

zset中的member和score的关系,不能理解成"键值对"

包含97,但不包含95

这里并不符合直觉既然已经这么设定了,只能遵守这样的规则,既然已经这么设定了,就只能将错就错了!而且还要考虑兼容性,一般来说,如果想要做出这种不兼容的修改,可以先把这个修改的内容标记成弃用,隔若干个版本在逐渐的把功能完成修改

  1. 考虑兼容性的案例:C++(兼容C)
  2. 不考虑兼容性的案例:IPv6

zrange

  • 返回指定区间⾥的元素,分数按照升序。带上 WITHSCORES 可以把分数也返回。
  • 时间复杂度:O(log(N)+M) :此处要根据下标找到边界值,start对应的位置,接下来就需要遍历了,start-stop的元素个数为M
  • 返回值:区间内的元素列表。

zrevrange

  • 返回指定区间⾥的元素,分数按照降序。带上 WITHSCORES 可以把分数也返回。
  • 备注:这个命令可能在 6.2.0 之后废弃,并且功能合并到 ZRANGE 中。
  • rev-reverse逆序
  • 时间复杂度:O(log(N)+M) 返回值:区间内的元素列表。
  • 此处的区间0 -1两个参数顺序是不需要变的,也就是降序

zrangebyscore

  • 返回分数在 min 和 max 之间的元素,默认情况下,min 和 max 都是包含的,可以通过 ( 排除。
  • 备注:这个命令可能在 6.2.0 之后废弃,并且功能合并到 ZRANGE 中。
  • 时间复杂度:O(log(N)+M) 返回值:区间内的元素列表。

zpopmax

  • 删除并返回分数最⾼的 count 个元素
  • 时间复杂度:O(log(N) * M) ,N为有序集合的元素个数,M表示count,此处删除的是最大值!!!有序集合,最大值就相当于最后一个元素(尾删),既然是尾删,为什么不把最后一个元素特殊记录下来,后续删除不就可以O(1)了吗,省去了查找的过程?Redis目前并没有这么做,Redis源码中针对有序集合确实是记录了尾部这样的特定位置,但是在实际删除的时候,并没有用上这个特性,而是直接调用了一个"通用的删除函数",此处是存在优化空间的(当然了,优化要优化在刀刃上,一般先找到性能瓶颈,再针对性的优化)
  • 返回值:分数和元素列表。
  • 联想到使用优先级队列来解决topK问题,这里类似

如果存在多个元素分数相同并且同时为最大值,仍然只删除其中一个元素,分数虽然是主要因素,如果分数相同会按照member字典序来排序

zpopmin

bzpopmax

  • ZPOPMAX 的阻塞版本。(联想到针对list的blpop,brpop实现类似于阻塞队列的效果)
  • 这里的有序集合也可以视为"优先级队列",有的时候,也需要一个带有"阻塞功能"的优先级队列,每个key都是一个有序集合,阻塞也是在有序集合为空的时候触发阻塞,阻塞到有其他客户端插入元素,timeout表示超时时间(单位为秒,支持小数形式),最多阻塞多久
  • 时间复杂度:O(log(N)),删除最大值花的时间,如果当前bzpopmax同时监听了多个key,假设key是M个,此时时间复杂度是O(logN*M)?
  • 并不是,是从这若干个key中只删除一次,并不是每个key的max都删除
  • 返回值:元素列表。

zset为空,左侧进行阻塞操作,右侧添加元素时,直接删除,若不为空,直接删除,不论count为多少,有三个属性:key标识了有序集合,以及member和score

zpopmin

删除并返回分数最低的 count 个元素。

时间复杂度:O(log(N) * M),M为count值, 返回值:分数和元素列表。

bzpopmin

ZPOPMIN 的阻塞版本,用法和bzpopmax一致

时间复杂度:O(log(N)),多个有序集合只删除一次,返回值:元素列表。

zrank

返回指定元素的排名,升序。

时间复杂度:O(log(N)),zcount算的时候就是先根据分数找到元素,再根据元素获取到排名,再把排名意见,就得到了元素个数,最主要是有一个查询位置的过程,返回值:排名。

zrank得到的下标,是从前往后算的

zrevrank

返回指定元素的排名,降序。

时间复杂度:O(log(N)) 返回值:排名。

zscore

  • 返回指定元素的分数。
  • 时间复杂度:O(1),前面,根据member找元素,都是logN,这里也是要先找元素?
  • 此处相当于redis对于这样的查询操作做了特殊优化,付出了额外的空间代价,针对这里进行了优化到O(1)实现
  • 返回值:分数。

zrem

  • 删除指定的元素。
  • 时间复杂度:O(M*log(N)),N是整个有序集合中元素个数,M是参数中member的个数
  • 返回值:本次操作删除的元素个数。

zremrangebyrank

  • 按照排序下标,升序删除指定范围的元素,左闭右闭。
  • 时间复杂度:O(log(N)+M),N是整个有序集合的元素个数,M是start-stop之间的元素个数,此处查找位置,只需要进行一次(不需要重复进行)
  • 返回值:本次操作删除的元素个数。

zremrangebyscore

按照分数删除指定范围的元素,左闭右闭。

时间复杂度:O(log(N)+M) 返回值:本次操作删除的元素个数。

zincrby

  • 为指定的元素的关联分数添加指定的分数值
  • 时间复杂度:O(log(N))
  • 返回值:增加后元素的分数。

不光会修改分数内容,也能同时移动元素位置,保证集合有序

同时也可以支持减法和小数

zinterstore

  • 求出给定有序集合中元素的交集并保存进⽬标有序集合中,在合并过程中以元素为单位进⾏合并,元 素对应的分数按照不同的聚合⽅式和权重得到新的分数。

选项

  • numkeys:几个交集,同时也是防止混淆后面的选项和key
  • 此处比较像http协议中的请求头(heade)中的Content-Length,描述了正文的长度,如果这个没有描述好,就可能产生粘包问题:HTTP在传输层是基于TCp,TCp是面向字节流的,粘包问题,是面向字节流这种IO方式中的一个普遍存在的问题
  • destination:要把结果存储到哪个key对应的zset中
  • weights:权重,此处指定的权重,相当于一个系数,会乘以当前的分数
  • 有序集合中,member才是元素的主体,score注释辅助排序的工具,因此在比较"相同"的时候,只要member相同即可,如果member相同,score不同,那么最终结果是多少呢!?
  • aggregate:sum | min | max,如果是max,那么最终结果就是最大的score,以此类推
  • 时间复杂度:O(N*K)+O(M*log(M)),化简得到近似值为N*logN(k不会很多,近似看作1,把N和M看作是近似的),N 是输⼊的有序集合中, N是最⼩的有序集合的元素个数; K 是输⼊了几个有序集合; M 是最终结果的有序集合的元素个数,取决于redis源码咋写的
  • 返回值:目标集合中的元素个数

zunionstore

  • 求出给定有序集合中元素的并集并保存进⽬标有序集合中,在合并过程中以元素为单位进⾏合并,元 素对应的分数按照不同的聚合⽅式和权重得到新的分数,参数和上一个交集命令的参数一致
  • 时间复杂度:O(N)+O(M*log(M)) N 是输⼊的有序集合总的元素个数; M 是最终结果的有序集合的元素个数,近似为O(M*logM)
  • 返回值:⽬标集合中的元素个数

zunionstore用法和zinterstore用法一致

zset命令小结

序号 命令及描述
1 ZADD key score1 member1 [score2 member2] 向有序集合添加一个或多个成员,或者更新已存在成员的分数
2 ZCARD key 获取有序集合的成员数
3 ZCOUNT key min max 计算在有序集合中指定区间分数的成员数
4 ZINCRBY key increment member 有序集合中对指定成员的分数加上增量 increment
5 ZINTERSTORE destination numkeys key [key ...] 计算给定的一个或多个有序集的交集并将结果集存储在新的有序集合 destination 中
6 ZLEXCOUNT key min max 在有序集合中计算指定字典区间内成员数量
7 ZRANGE key start stop [WITHSCORES] 通过索引区间返回有序集合指定区间内的成员
8 ZRANGEBYLEX key min max [LIMIT offset count] 通过字典区间返回有序集合的成员
9 ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT] 通过分数返回有序集合指定区间内的成员
10 ZRANK key member 返回有序集合中指定成员的索引
11 ZREM key member [member ...] 移除有序集合中的一个或多个成员
12 ZREMRANGEBYLEX key min max 移除有序集合中给定的字典区间的所有成员
13 ZREMRANGEBYRANK key start stop 移除有序集合中给定的排名区间的所有成员
14 ZREMRANGEBYSCORE key min max 移除有序集合中给定的分数区间的所有成员
15 ZREVRANGE key start stop [WITHSCORES] 返回有序集中指定区间内的成员,通过索引,分数从高到低
16 ZREVRANGEBYSCORE key max min [WITHSCORES] 返回有序集中指定分数区间内的成员,分数从高到低排序
17 ZREVRANK key member 返回有序集合中指定成员的排名,有序集成员按分数值递减(从大到小)排序
18 ZSCORE key member 返回有序集中,成员的分数值
19 ZUNIONSTORE destination numkeys key [key ...] 计算给定的一个或多个有序集的并集,并存储在新的 key 中
20 ZSCAN key cursor [MATCH pattern] [COUNT count] 迭代有序集合中的元素(包括元素成员和元素分值)

zset编码方式

  • ziplist(压缩列表):当有序集合的元素个数⼩于 zset-max-ziplist-entries 配置(默认 128 个), 同时每个元素的值都⼩于 zset-max-ziplist-value 配置(默认 64 字节)时,Redis 会⽤ ziplist 来作 为有序集合的内部实现,ziplist 可以有效减少内存的使⽤。
  • skiplist(跳表):当 ziplist 条件不满⾜时,有序集合会使⽤ skiplist 作为内部实现,因为此时 ziplist 的操作效率会下降。
  1. 当元素个数较少且每个元素较⼩时,内部编码为 ziplist:
  2. 当元素个数超过 XXX 个,内部编码 skiplist:
  3. 当某个元素⼤于 XXXX 字节时,内部编码 skiplist:

zset的应用场景

最关键的应用场景:排行榜系统

  • 微博热搜
  • 游戏天梯排行
  • 成绩排行

...

  • 关键要点:用来排行的"分数"是实时变化的,虽然是实时变化的,也能够高效的更新排行
  • 使用zset来完成上述操作,就非常简单,比如游戏天梯排行,只要把玩家信息和对应的分数给放到有序集合中即可,自动就形成了一个排行榜,随时可以按照排行(下标),按照分数进行范围查询,随着分数发生改变,也可以比较方便的,zincrby修改分数,排行顺序也能自动调整(logN)
  • 游戏玩家这么多,此时都用这个zset来存(内存),能存下嘛?
  • 在王者农药中,往多了算,就按一亿玩家,userId 4个字节来理解,score 8个字节来理解,表示一个玩家,大概是12个字节,那么一亿玩家就有12亿字节,大约是1.2GB,单位换算,一定要张口就来:thousand kb,million mb,billion gb
  • 对于游戏排行榜,这里的前后顺序非常容易确定,但是有的排行榜就要复杂一些,微博热榜,需要考虑综合数值,浏览量,点赞量,转发量,评论量,这里就是权重位,具体有多少个维度,每个维度怎么分配的,以及怎么设定是最优的...
  • 根据每个维度,计算得到综合得分也就是热度,此时借助 zinterstore/zunionstore按照加权方式处理,此时就可以把上述每个维度的数值都放到有序集合中,member就是微博的Id,score就是个维度的数值,通过zinterstore/zunionstore把上述集合进行加权处理

注意事项:zset是一个选择,但并不是一定要用redis中的zset,要综合考虑

相关推荐
Cachel wood6 分钟前
python round四舍五入和decimal库精确四舍五入
java·linux·前端·数据库·vue.js·python·前端框架
Python之栈13 分钟前
【无标题】
数据库·python·mysql
风_流沙25 分钟前
java 对ElasticSearch数据库操作封装工具类(对你是否适用嘞)
java·数据库·elasticsearch
亽仒凣凣32 分钟前
Windows安装Redis图文教程
数据库·windows·redis
亦世凡华、41 分钟前
MySQL--》如何在MySQL中打造高效优化索引
数据库·经验分享·mysql·索引·性能分析
YashanDB43 分钟前
【YashanDB知识库】Mybatis-Plus调用YashanDB怎么设置分页
数据库·yashandb·崖山数据库
ProtonBase1 小时前
如何从 0 到 1 ,打造全新一代分布式数据架构
java·网络·数据库·数据仓库·分布式·云原生·架构
希忘auto2 小时前
详解Redis的常用命令
redis·1024程序员节
云和数据.ChenGuang6 小时前
Django 应用安装脚本 – 如何将应用添加到 INSTALLED_APPS 设置中 原创
数据库·django·sqlite
woshilys7 小时前
sql server 查询对象的修改时间
运维·数据库·sqlserver