Redis手法:有序集(SortedSet)的操作转载

原创
小哥 3年前 (2022-10-28) 阅读数 6 #大杂烩

有序集合(Sorted Set)是Redis保存需要排序的数据的重要数据结构。例如,排行榜、班级的中文分数、公司员工的工资、论坛的帖子等。在有序的集合中,每个元素都有一个score(权重)对元素进行排序。它有三个要素:key、member和score。以中国的成就为例,key是考试的名称(期中考试、期末考试等),member这是学生的名字,score是成绩。

有序集合有两个基本用途:排序和聚合,如以下几个示例所示。

排序

假设老师需要处理期中考试的语文成绩,他做的第一件事就是把学生的成绩录入系统。

  Li Lei成绩70分
  127.0.0.1:6379> ZADD mid_test 70 "Li Lei"
  (integer) 1

  Han Meimei成绩70分
  127.0.0.1:6379> ZADD mid_test 70 "Han Meimei"
  (integer) 1

  tom成绩99.5分
  127.0.0.1:6379> ZADD mid_test 99.5 "Tom"
  (integer) 1

排行榜

有序的收藏自然是排名的利器。带上就行了score的member将它插入有序的集合中,您可以按正向或反向顺序取出数据。这需要ZREVRANGE(逆序)和ZRANGE(正序)。

  得分排行榜
  127.0.0.1:6379> ZREVRANGE mid_test 0 -1 WITHSCORES
  1) "Tom"
  2) "99.5"
  3) "Li Lei"
  4) "70"
  5) "Han Meimei"
  6) "70"

分段统计

有序集合还支持按score查询间隔:ZREVRANGEBYSCORE对于倒排查询,ZRANGEBYSCORE是一个正序列。例如,要知道90不止一个学霸:

  127.0.0.1:6379> ZREVRANGEBYSCORE mid_test 100 90 WITHSCORES
  1) "Tom"
  2) "99.5"

聚合

有序集合本质上是集合,当然会有交集( ZINTERSTORE )和并集( ZUNIONSTORE )运算。

相交和串联

交集

ZINTERSTORE 取所有集合的交集。有两套A和B例如,要在交叉口C,这就是逻辑:

  • A和B中共有的member,将添加到C中,其score等于A、B中score之和。
  • 不同时在A和B的member,将不会添加C中。

一个班又进行了一次期末考试,同时来了一位新同学。Jerry。

  127.0.0.1:6379> ZADD fin_test 88 "Li Lei"
  (integer) 1
  127.0.0.1:6379> ZADD fin_test 75 "Han Meimei"
  (integer) 1
  127.0.0.1:6379> ZADD fin_test 99.5 "Tom"
  (integer) 1
  127.0.0.1:6379> ZADD fin_test 100 "Jerry"
  (integer) 1

老师应该按照中考和期末考试的总成绩按期安排座位,这是对的。mid_test和fin_test过了一个十字路口。

  127.0.0.1:6379> ZINTERSTORE sum_point 2 mid_test fin_test
  (integer) 3
  127.0.0.1:6379> ZREVRANGE sum_point 0 -1 WITHSCORES
  1) "Tom"
  2) "199"
  3) "Li Lei"
  4) "158"
  5) "Han Meimei"
  6) "145"

结果显示了学生的总成绩。
但结果中没有新来的人。Jerry学生(虽然TA考了100点)。这是一号坑。

并集

ZUNIONSTORE 计算所有集合的并集。有两套A和B例如,获取和设置C,这就是逻辑:

  • A的所有member会加到C中,其score与A中相等
  • B的所有member会加到C中,其score与B中相等
  • A和B中共有的member,其score等于A、B中score之和。

假设某公司要核算工资总支出,先按各部门单独核算,再按财政统一待遇。

程序员的工资

  127.0.0.1:6379> zadd programmer 2000 peter
  (integer) 1
  127.0.0.1:6379> zadd programmer 3500 jack
  (integer) 1
  127.0.0.1:6379> zadd programmer 5000 tom
  (integer) 1

经理工资

  127.0.0.1:6379> zadd manager 2000 herry
  (integer) 1
  127.0.0.1:6379> zadd manager 3500 mary
  (integer) 1
  127.0.0.1:6379> zadd manager 4000 tom
  (integer) 1

财政统一待遇。

  127.0.0.1:6379> zunionstore salary 2 programmer manager
  (integer) 5
  127.0.0.1:6379> zrange salary 0 -1 withscores
   1) "herry"
   2) "2000"
   3) "peter"
   4) "2000"
   5) "jack"
   6) "3500"
   7) "mary"
   8) "3500"
   9) "tom"
  10) "9000"

结果显示,工资总支出。

但结果中的程序员tom和经理tom是两个人,但工资算在一起。这里是2号坑。

避免踩坑

你还记得上面提到的1号坑和2号坑吗?

坑一:

当进行ZINTERSTORE在操作过程中,如果聚合操作的源集合中的元素不同,则聚合结果集只是交集。如果在汇总后发现某些要素丢失,请检查来源收集要素是否相同。

坑二:

当进行ZUNIONSTORE操作,如果在其中执行聚合操作的源集存在相同的元素,则聚合结果集是相同的元素。score等于来源收集要素。score总和。如果发现一些元素经过聚合后。score异常,请检查源集合是否有相同的元素。

我踩到的那个坑:

做用户的feed(timeline),我需要用我自己发布的信息来汇总我关心的人。

timeline & feed

应该用ZUNIONSTORE将所有信息聚合在一起。

后来,用户反馈说timeline排序有误,自己发布的信息总是排在最前面。由于早期的原因,后来确定了原因bug,自己可以关注自己,导致关注和自己的反复聚合。踩2号坑。

为什么要踩在坑上?

以2号坑为例,为什么有相同的元素,score它会成为原始元素的总和吗?

因为ZINTERSTORE和ZUNIONSTORE有一些参数是AGGREGATE,表示结果集的聚合,这是可取的。SUM、MIN、MAX他们中的一个。缺省值为SUM。

因此,如果不指定聚合方法,则缺省值为。SUM,也就是总和。

默认情况下使用的参数 SUM ,则可以将成员放入所有集合中。 score 值之 和 作为结果集的成员。 score 值;使用参数 MIN ,则可以将成员放入所有集合中。 最小 score 值作为结果集的成员。 score 值;以及参数 MAX 是将所有成员放入集合中。 最大 score 值作为结果集的成员。 score 值。

文件如上所述。

有序集的总和

使用场景:排行榜、有序列表、聚合;

算法复杂性:

  • 增删:O(M*log(N)), N 是有序集合的基数, M 成功操作(添加、删除)的成员数。
  • 查询:O(log(N)+M), N 是有序集合的基数,而 M 是结果集的基数。
  • 聚合:O(N)+O(M log(M)), N 是给定有序集的基数之和, M 是结果集的基数。
  • 总数:O(1)

注意事项:

  • ZINTERSTORE操作时,如果在汇总后发现某些要素丢失,请检查来源收集要素是否相同。
  • ZUNIONSTORE操作时,如果发现一些元素经过聚合后。score异常,请检查源集合是否有相同的元素。

博客链接: http://spetacular.github.io/2015/11/01/redis-zunionstore-tip.htm

作者:兔子David
链接:https://www.jianshu.com/p/6c4b7d697f45
资料来源:简讯
版权归作者所有。请联系作者以获得商业转载的授权,并注明非商业转载的来源。

版权声明

所有资源都来源于爬虫采集,如有侵权请联系我们,我们将立即删除