它跟Set(集合)一样不能有重复的元素,但是多了排序的功能,而且是自动排序,不需要维护的,也就是你添加或更新元素,底层自动就帮你排序了。所以当你需要一个有序且不重复的集合列表,那么Sorted set将是一个不错的选择。
Zset - 图1注:

  1. 指令中的members实际就是value,这里改为value方便理解。
  2. 分数值可以是整数值双精度浮点数
  3. score保存的数据也可以是double值,但基于浮点数的特征,可能会丢失精度,排序可能错误,使用是需谨慎。score储存范围:-9007199254740992 ~ 9007199254740992。

    基本命令:

    添加数据:

    1. zadd key score1 value1 score2 value2 ...
    注:当多个value的score相同时,会通过value进行排序。例如 adc 和 acc 的score一样,但c在d前,因此是acc在前,adc在后。

    获取数据:

    1. zrange key start stop [withscores] //返回指定索引范围内的数据
    2. zrevrange key start stop [withscores] //倒序返回指定索引范围内的数据
    注:[ withscores ] 为返回 score 的值。

删除数据:

  1. zrem key value1 value2

根据score获取数据:

  1. zrangebyscore key min max [withscores] [limit offset count] //指定score的范围进行数据获取
  2. zrevrangebyscore key min max [withscores] //指定score的范围进行数据倒序获取

注:[ withscores ] 意为返回 score 的值;[ limit offset count ] 意为截取 offset(起始索引)+ count (数量)的数据, limit 3 10 就是返回 3~13

根据条件删除数据:

  1. zremrangebyrank key start stop //删除指定索引范围内的数据
  2. zremrangebyscores key min max //删除指定排序范围内的数据

注:
mix & max 用于限定搜索查询的条件;
start & stop 用于限定查询,作用于索引,表示开始索引和结束索引;

获取集合数量:

  1. zcard key //返回key中的数据总数
  2. zcount key min max //返回指定scores范围内的数据数

交集、并集:

  1. zinterstore newkey numkey key1 key2 ... //将指定的key中的交集存入newkey
  2. zunionstore newkey numkey key1 key2 ... //将指定的key中的并集存入newkey

注:
numkey 的值和 key 的值要一致,例如 要合并3个key: zunionstore newUnion 3 box1 box2 box3;
求交集时,只有所有key中都有交集才会返回数据;
如果需要整合 score 的值,可以添加 aggregate sum|min|max;

  1. //求key1、key2的交集,且score是二者中最大值
  2. zinterstore newkey numkey key1 key2 aggregate max

拓展操作:

获取数据对应的索引:

  1. zrank key value //获取数据的索引
  2. zrevrank key value //倒序获取数据的索引

获取数据对应的score值:

  1. zscore key value

修改数据对应的score值:

  1. zincrby key increment value //自增或者自减increment

实际案例:

计数器组合排序功能(排行榜):

image.png
使用score值作为投票数,通过修改score的值实现排序的变动,索引即为榜单的顺序。由于是根据score的值做榜单,因此需要倒序获取数据。
榜单数据:

  1. zadd hotnew 100 new1 200 new2 300 new3 400 new4 500 new5 600 new6 700 new7

获取数据:

  1. zrevrange hotnew 0 0 //获取榜单第一
  2. zrevrange hotnew 0 -1 //获取排行榜

添加点击量:

  1. zincrby hotnew 888 new6

逾期失效功能:

处理VIP会员、限时讨论等具有时效性的信息。将过期时间设置到 score ,先根据时效性不同进行月、星期、今天、最近一小时分段存放,对一小时内将要到期的数据利用排序特性来管理,通过对比 time 来进行移除,实现会员的过期。( 底层是hash且支持排序,比string、list的过期效率要高)
设置一小时后要到期的会员:

  1. zadd viptoday 1620152111 user:01 1620153111 user:02 1620156111 user:03

查看当前系统的时间:

  1. time

移除到期的会员:

  1. zremrangebyscore viptoday 0 1620155111

权重任务处理:

将权重储存到 score 中,通过倒序进行处理。如果遇到多条件,需要进行数据截断或者补全处理。
例如已知发货权重:外贸101 > 国内102 > 自用10,总裁006 > 经理007 > 员工009,现有外贸员工101009、国内经理102007、国内员工102008、自用经理10007,如何指定 ?

数据储存:由于自用的长度与其他地区不符合,又因为优先级最低,因此需要补全到三位数 :103。

  1. zadd sendbox 101009 order:id:01 102007 order:id:02 102008 order:id:03 103007 order:id:04

发货顺序:

  1. zrange sendbox 0 -1 withscores

通讯APP列表排序:

以 QQ为例,置顶用户为 list 类型,普通用户为 zset 类型,一旦有消息时用户会改变排序进行展示。
Zset - 图3
Zset - 图4
设置置顶用户:

  1. lpush timtop dmbjz xiaoming

设置普通列表用户:

  1. zadd timlist 100 aaa 200 bbb 300 ccc 400 ddd 500 eee

发送一个 ccc 用户的新消息:

  1. zscore timlist ccc //获得ccc
  2. zincrby timlist -300 ccc //减少300,变为首位

发送一个指定用户的新消息:

  1. lrem timtop 1 dmbjz
  2. lpush timtop dmbjz

数据查询底层原理:

zset使用了类似二分法的方式,采用skiplist的数据结构进行处理,时间复杂度是O(logN),与红黑树一致

跳跃表原理:

Zset
Zset - 图5
在这样一个链表中,如果我们要查找某个数据,那么需要从头开始逐个进行比较,直到找到包含数据的那个节点,或者找到第一个比给定数据大的节点为止(没找到)。也就是说,时间复杂度为O(n)。同样,当我们要插入新数据的时候,也要经历同样的查找过程,从而确定插入位置。假如我们每相邻两个节点增加一个指针,让指针指向下下个节点,如下图:
Zset - 图6
这样所有新增加的指针连成了一个新的链表,但它包含的节点个数只有原来的一半(上图中是7, 19, 26)。现在当我们想查找数据的时候,可以先沿着这个新链表进行查找。当碰到比待查数据大的节点时,再回到原来的链表中进行查找。比如,我们想查找23,查找的路径是沿着下图中标红的指针所指向的方向进行的:

Zset - 图7

  • 23首先和7比较,再和19比较,比它们都大,继续向后比较。
  • 但23和26比较的时候,比26要小,因此回到下面的链表(原链表),与22比较。
  • 23比22要大,沿下面的指针继续向后和26比较。23比26小,说明待查数据23在原链表中不存在,而且它的插入位置应该在22和26之间。

在这个查找过程中,由于新增加的指针,我们不再需要与链表中每个节点逐个进行比较了。需要比较的节点数大概只有原来的一半。利用同样的方式,我们可以在上层新产生的链表上,继续为每相邻的两个节点增加一个指针,从而产生第三层链表。如下图:

Zset - 图8
在这个新的三层链表结构上,如果我们还是查找23,那么沿着最上层链表首先要比较的是19,发现23比19大,接下来我们就知道只需要到19的后面去继续查找,从而一下子跳过了19前面的所有节点。可以想象,当链表足够长的时候,这种多层链表的查找方式能让我们跳过很多下层节点,大大加快查找的速度。

下图为 zset 的 skiplist创建过程:
Zset - 图9
从上面skiplist的创建和插入过程可以看出,每一个节点的层数(level)是随机出来的,而且新插入一个节点不会影响其它节点的层数。因此,插入操作只需要修改插入节点前后的指针,而不需要对很多节点都进行调整。这就降低了插入操作的复杂度。
对于数据23的skiplist查询路径