Redis技巧:有序集合(Sorted Set)的使用

有序集合(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是两个人,但工资算在了一起。这是坑二。

避免踩坑

还记得上面说的坑一和坑二吗?

坑一:

当进行ZINTERSTORE操作时,如果进行聚合操作的源集合中元素不同,则聚合后的结果集仅为交集。如果发现聚合后少了一些元素,请查看源集合元素是否相同。

坑二:

当进行ZUNIONSTORE操作时,如果进行聚合操作的源集合中有相同元素,则聚合后的结果集中,相同元素的score等于源集合元素的score之和。如果发现聚合后某些元素的score异常,请查看源集合是否有相同元素。

我踩过的坑:

做用户的feed(timeline)时,需要将我关注的人和我自己发表的信息聚合起来。

timeline & feed

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

后来有用户反馈说timeline排序错误,自己发表发布的信息永远在最上面。后来查明原因,由于早期的bug,自己竟然可以关注自己,导致关注人和自己重复聚合。踩到了坑二。

为什么踩坑

以坑二为例,为什么有相同元素时,score就会变成原来元素的和?

因为ZINTERSTORE和ZUNIONSTORE有个参数为AGGREGATE,表示结果集的聚合方式,可取SUM、MIN、MAX其中之一。默认值为SUM。

所以不指定聚合方式时,缺省值为SUM,即求和。

<blockquote>
默认使用的参数 SUM ,可以将所有集合中某个成员的 score 值之 和 作为结果集中该成员的 score 值;使用参数 MIN ,可以将所有集合中某个成员的 最小 score 值作为结果集中该成员的 score 值;而参数 MAX 则是将所有集合中某个成员的 最大 score 值作为结果集中该成员的 score 值。
</blockquote>

文档如上。

有序集合之总结

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

算法复杂度:

  • 增删: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.html

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,271评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,725评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,252评论 0 328
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,634评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,549评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,985评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,471评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,128评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,257评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,233评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,235评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,940评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,528评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,623评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,858评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,245评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,790评论 2 339

推荐阅读更多精彩内容

  • Redis 数据结构简介 Redis 可以存储键与5种不同数据结构类型之间的映射,这5种数据结构类型分别为Stri...
    DreamerRzc阅读 236,655评论 26 273
  • 和Sets相比,SortedSets增加了一个权重参数score,使得集合中的元素能够按score进行有序排列, ...
    架构飞毛腿阅读 3,821评论 0 0
  • 本文为笔者对在学习Redis过程中所收集资料的一个总结,目的是为了以后方便回顾相关的知识,大部分为非原创内容。特此...
    EakonZhao阅读 14,403评论 0 9
  • 梁头,今天忙头忙尾,回到家里,想向您唠点闲科,有点感慨。 - 那一年,阳泉站孙东昌来所,联系一个项目,很有意思。我...
    江流今古愁阅读 166评论 0 0
  • 前日子白昼,看了釜山行,到了晚上,总会睡的不安分。一方面,由僵尸勾起的各种恐怖的画面。另一方面,总在想着,这样子胆...
    SylviaDeer阅读 316评论 0 0