Redis实战之集合与有序集合(类型的实现、操作与常用场景)

集合

集合(set)类型也是用来保存多个的字符串元素,但和列表类型不一样的是,集合中不允许有重复元素,并且集合中的元素是无序的,不能通过索引下标获取元素。如图2-22所示,集合user:1:follow包含着"it"、"music"、"his"、"sports"四个元素,一个集合最多可以存储232-1个元素。Redis除了支持集合内的增删改查,同时还支持多个集合取交集、并集、差集,合理地使用好集合类型,能在实际开发中解决很多实际问题。

命令

1.集合内操作
    
    1.sadd key element [element ...]    添加元素,返回结果为添加成功的元素个数
    
        sadd myset a b c
    
    2.srem key element [element ...]    删除元素,返回结果为成功删除元素个数
            
        srem myset a b
    
    3.scard key  计算元素个数,scard的时间复杂度为O(1),它不会遍历集合所有元素,而是直接用
                 Redis内部的变量。
    4.sismember key element     判断元素是否在集合中,如果给定元素element在集合内返回1,反之返回0
    
        sismember myset c
    
    5.srandmember key [count]   随机从集合返回指定个数元素
        
        srandmember myset 2
    
    6.spop key                  从集合随机弹出元素
        
        spop myset
    
    srandmember和spop都是随机从集合选出元素,两者不同的是spop命令执行后,元素会从集合中删除,而         srandmember不会
    
    7.smembers key  获取所有元素 smembers和lrange、hgetall都属于比较重的命令,如果元素过多存在阻塞                    Redis的可能性,这时候可以使用sscan来完成
    
    smembers myset

2.集合间操作
    
    1.sinter key [key ...]  求多个集合的交集,下面代码是求user:1:follow和user:2:follow两个集                            合的交集,返回结果是sports、it
        sinter user:1:follow user:2:follow

    2.suinon key [key ...]  求多个集合的并集
    
    3.sdiff key [key ...]   求多个集合的差集
    
    4.sinterstore destination key [key ...]  将交集、并集、差集的结果保存
    suionstore  destination key [key ...] 
    sdiffstore  destination key [key ...]
    集合间的运算在元素较多的情况下会比较耗时,所以Redis提供了上面三个命令(原命令+store)将集合间交           集、并集、差集的结果保存在destination key中

内部编码

集合类型的内部编码有两种:

·intset(整数集合):当集合中的元素都是整数且元素个数小于set-maxintset-entries配置(默认512个)时,Redis会选用intset来作为集合的内部实现,从而减少内存的使用。

·hashtable(哈希表):当集合类型无法满足intset的条件时,Redis会使用hashtable作为集合的内部实现。

使用场景

集合类型比较典型的使用场景是标签(tag)。例如一个用户可能对娱乐、体育比较感兴趣,另一个用户可能对历史、新闻比较感兴趣,这些兴趣点就是标签。有了这些数据就可以得到喜欢同一个标签的人,以及用户的共同喜好的标签,这些数据对于用户体验以及增强用户黏度比较重要。例如一个电子商务的网站会对不同标签的用户做不同类型的推荐,比如对数码产品比较感兴趣的人,在各个页面或者通过邮件的形式给他们推荐最新的数码产品,通常会为网站带来更多的利益。

1.给用户添加标签

​ sadd user:1:tags tag1 tag2 tag5
​ sadd user:2:tags tag2 tag3 tag5

2.给标签添加用户

​ sadd tag1:users user:1 user:3
​ sadd tag2:users user:1 user:2 user:3

开发提示
用户和标签的关系维护应该在一个事务内执行,防止部分命令失败造成的数据不一致,有关如何将两个命令放在一个事务,
3.删除用户下的标签

​ srem user:1:tags tag1 tag5

4.删除标签下的用户

​ srem tag1:users user:1 srem

5.计算用户共同感兴趣的标签

​ sinter user:1:tags user:2:tags

前面只是给出了使用Redis集合类型实现标签的基本思路,实际上一个标签系统远比这个要复杂得多,不过集合类型的应用场景通常为以下几种:
    ·sadd=Tagging(标签)
    ·spop/srandmember=Random item(生成随机数,比如抽奖)
    ·sadd+sinter=Social Graph(社交需求,推荐好友,共同好友。。。)

有序集合

有序集合相对于哈希、列表、集合来说会有一点点陌生,但既然叫有序集合,那么它和集合必然有着联系,它保留了集合不能有重复成员的特性,但不同的是,有序集合中的元素可以排序。但是它和列表使用索引下标作为排序依据不同的是,它给每个元素设置一个分数(score)作为排序的依据。如图2-24所示,该有序集合包含kris、mike、frank、tim、martin、tom,它们的分数分别1、91、200、220、250、251,有序集合提供了获取指定分数和元素范围查询、计算成员排名等功能,合理的利用有序集合,能帮助我们在实际开发中解决很多问题。

image
开发提示
    有序集合中的元素不能重复,但是score可以重复,就和一个班里的同学学号不能重复,但是考试成绩可以相同.
image

命令

1.集合内
    1.zadd key score member [score member ...]  添加成员,返回结果代表成功添加成员的个数
    
    127.0.0.1:6379> zadd user:ranking 1 kris 91 mike 200 frank 220 tim 250 martin
    (integer) 5

    Redis3.2为zadd命令添加了nx、xx、ch、incr四个选项:
        ·nx:member必须不存在,才可以设置成功,用于添加。
        ·xx:member必须存在,才可以设置成功,用于更新。 
        ·ch:返回此次操作后,有序集合元素和分数发生变化的个数
        ·incr:对score做增加,相当于后面介绍的zincrby。
        ·有序集合相比集合提供了排序字段,但是也产生了代价,zadd的时间复杂度为O(log(n)),sadd的时间          复杂度为O(1)。
    
    2.zcard key 计算成员个数。
    返回有序集合user:ranking的成员数为5,和集合类型的 scard命令一样,zcard的时间复杂度为O(1)

    3.zscore key member  计算某个成员的分数。
        zscore user:ranking tom
        "251"
    4.zrank key member zrevrank 计算成员的排名,zrank是从分数从低到高返回排名,zrevrank反之。例如     下面操作中,tom 在zrank和zrevrank分别排名第5和第0(排名从0开始计算)

    5.zrem key member [member ...]  删除成员
        zrem user:ranking mike      操作将成员mike从有序集合user:ranking中删除
    
    6.zincrby key increment member  增加成员的分数.

    7.zrange key start end [withscores] zrevrange key start end [withscores] 返回指定排名范      围的成员.
    有序集合是按照分值排名的,zrange是从低到高返回,zrevrange反之。下面代码返回排名最低的是三个成员,如    果加上withscores选项,同时会返回成员的分数.
    zrange user:ranking 0 2 withscores
    1)  "kris"
    2)  "1"
    3)  "frank"
    4)  "200"
    5)  "tim"
    6)  "220"

    8.zrangebyscore key min max [withscores] [limit offset count] 返回指定分数范围的成员
      zrevrangebyscore key max min [withscores] [limit offset count]

    其中zrangebyscore按照分数从低到高返回,zrevrangebyscore反之。例如
    下面操作从低到高返回200到221分的成员,withscores选项会同时返回每个成员的分数。[limit offset        count]选项可以限制输出的起始位置和个数.
    127.0.0.1:6379> zrangebyscore user:ranking 200 tinf withscores
    1)  "frank"
    2)  "200"
    3)  "tim"
    4)  "220"
    同时min和max还支持开区间(小括号)和闭区间(中括号),-inf和+inf分别代表无限小和无限大  
    zrangebyscore user:ranking (200 +inf withscores 1) "tim"
    2)  "220"
    3)  "martin"
    4)  "250"
    5)  "tom"
    6)  "260"

    9.zcount key min max    返回指定分数范围成员个数
    
    10.zremrangebyrank key start end    删除指定排名内的升序元素.
    127.0.0.1:6379> zremrangebyrank user:ranking 0 2
    (integer) 3
    
    11.zremrangebyscore key min max     删除指定分数范围的成员.

image
集合间的操作
    1.交集
    zinterstore destination numkeys key [key ...] [weights weight [weight ...]] 
    [aggregate sum|min|max]  
    
    destination:交集计算结果保存到这个键。
    ·numkeys:需要做交集计算键的个数。
    ·key[key...]:需要做交集计算的键。
    ·weights weight[weight...]:每个键的权重,在做交集计算时,每个键中的每个member会将自己分数乘以这    个权重,每个键的权重默认是1。
    ·aggregate sum|min|max:计算成员交集后,分值可以按照sum(和)、min(最小值)、max(最大值)做汇     总,默认值是sum。

    下面操作对user:ranking:1和user:ranking:2做交集,weights和 aggregate使用了默认配置,可以看到目标键user:ranking:1_inter_2对分值做了sum操作:
     127.0.0.1:6379> zinterstore user:ranking:1_inter_2 2 user:ranking:1 user:ranking:2
    (integer) 3
    127.0.0.1:6379> zrange user:ranking:1_inter_2 0 -1 withscores
    1)  "mike"
    2)  "168"
    3)  "martin"
    4)  "875"
    5)  "tom"
    6)  "1139"
    如果想让user:ranking:2的权重变为0.5,并且聚合效果使用max,可以执行如下操作:
    127.0.0.1:6379> zinterstore user:ranking:1_inter_2 2 user:ranking:1   user:ranking:2 weights 1 0.5 aggregate max
    (integer) 3
    127.0.0.1:6379> zrange user:ranking:1_inter_2 0 -1 withscores
    1)  "mike"
    2)  "91"
    3)  "martin"
    4)  "312.5"
    5)  "tom"
    6)  "444"
    
    2.并集
    zunionstore destination numkeys key [key ...] [weights weight [weight ...]]             [aggregate sum|min|max】
    该命令的所有参数和zinterstore是一致的,只不过是做并集计算,例如
    下面操作是计算user:ranking:1和user:ranking:2的并集,weights和 aggregate使用了默认配置,可以看到目标键user:ranking:1_union_2对分值做了sum操作:
    127.0.0.1:6379> zunionstore user:ranking:1_union_2 2 user:ranking:1                 user:ranking:2
    (integer) 7
    127.0.0.1:6379> zrange user:ranking:1_union_2 0 -1 withscores
    1)  "kris"
    2)  "1"
    3)  "james"
    4)  "8"
    5)  "mike"
    6)  "168"
    7)  "frank"
    8)  "200"
    9)  "tim"
    10) "220"
    11) "martin"
    12) "875"
集合间的操作
    1.交集
    zinterstore destination numkeys key [key ...] [weights weight [weight ...]] 
    [aggregate sum|min|max]  
    
    destination:交集计算结果保存到这个键。
    ·numkeys:需要做交集计算键的个数。
    ·key[key...]:需要做交集计算的键。
    ·weights weight[weight...]:每个键的权重,在做交集计算时,每个键中的每个member会将自己分数乘以这    个权重,每个键的权重默认是1。
    ·aggregate sum|min|max:计算成员交集后,分值可以按照sum(和)、min(最小值)、max(最大值)做汇     总,默认值是sum。

    下面操作对user:ranking:1和user:ranking:2做交集,weights和 aggregate使用了默认配置,可以看到目标键user:ranking:1_inter_2对分值做了sum操作:
     127.0.0.1:6379> zinterstore user:ranking:1_inter_2 2 user:ranking:1 user:ranking:2
    (integer) 3
    127.0.0.1:6379> zrange user:ranking:1_inter_2 0 -1 withscores
    1)  "mike"
    2)  "168"
    3)  "martin"
    4)  "875"
    5)  "tom"
    6)  "1139"
    如果想让user:ranking:2的权重变为0.5,并且聚合效果使用max,可以执行如下操作:
    127.0.0.1:6379> zinterstore user:ranking:1_inter_2 2 user:ranking:1   user:ranking:2 weights 1 0.5 aggregate max
    (integer) 3
    127.0.0.1:6379> zrange user:ranking:1_inter_2 0 -1 withscores
    1)  "mike"
    2)  "91"
    3)  "martin"
    4)  "312.5"
    5)  "tom"
    6)  "444"
    
    2.并集
    zunionstore destination numkeys key [key ...] [weights weight [weight ...]]             [aggregate sum|min|max】
    该命令的所有参数和zinterstore是一致的,只不过是做并集计算,例如
    下面操作是计算user:ranking:1和user:ranking:2的并集,weights和 aggregate使用了默认配置,可以看到目标键user:ranking:1_union_2对分值做了sum操作:
    127.0.0.1:6379> zunionstore user:ranking:1_union_2 2 user:ranking:1                 user:ranking:2
    (integer) 7
    127.0.0.1:6379> zrange user:ranking:1_union_2 0 -1 withscores
    1)  "kris"
    2)  "1"
    3)  "james"
    4)  "8"
    5)  "mike"
    6)  "168"
    7)  "frank"
    8)  "200"
    9)  "tim"
    10) "220"
    11) "martin"
    12) "875"
    13) "tom"
    14) "1139"

时间复杂度

image

内部编码

有序集合类型的内部编码有两种:

·ziplist(压缩列表):当有序集合的元素个数小于zset-max-ziplistentries配置(默认128个),同时每个元素的值都小于zset-max-ziplist-value配置(默认64字节)时,Redis会用ziplist来作为有序集合的内部实现,ziplist

可以有效减少内存的使用。

·skiplist(跳跃表):当ziplist条件不满足时,有序集合会使用skiplist作

为内部实现,因为此时ziplist的读写效率会下降。

1)当元素个数较少且每个元素较小时,内部编码为skiplist:
 
127.0.0.1:6379> zadd zsetkey 50 e1 60 e2 30 e3
(integer) 3
127.0.0.1:6379> object encoding zsetkey
"ziplist"
 
2.1)当元素个数超过128个,内部编码变为ziplist:
 
127.0.0.1:6379> zadd zsetkey 50 e1 60 e2 30 e3 12 e4 ...忽略... 84 e129 (integer) 129
127.0.0.1:6379> object encoding zsetkey
"skiplist"
 
2.2)当某个元素大于64字节时,内部编码也会变为hashtable:
 
127.0.0.1:6379> zadd zsetkey 20 "one string is bigger than 64 byte.............
    ..................."
(integer) 1
127.0.0.1:6379> object encoding zsetkey
"skiplist"

使用场景

有序集合比较典型的使用场景就是排行榜系统。例如视频网站需要对用户上传的视频做排行榜,榜单的维度可能是多个方面的:按照时间、按照播放数量、按照获得的赞数。本节使用赞数这个维度,记录每天用户上传视频的排行榜。主要需要实现以下4个功能。

1.添加用户赞数

例如用户mike上传了一个视频,并获得了3个赞,可以使用有序集合的 zadd和zincrby功能:

zadd user:ranking:2016_03_15 mike 3

如果之后再获得一个赞,可以使用zincrby:

zincrby user:ranking:2016_03_15 mike 1

2取消用户赞数

由于各种原因(例如用户注销、用户作弊)需要将用户删除,此时需要将用户从榜单中删除掉,可以使用zrem。例如删除成员tom:

zrem user:ranking:2016_03_15 mike

3.展示获取赞数最多的十个用户

此功能使用zrevrange命令实现:

zrevrangebyrank user:ranking:2016_03_15 0 9

4.展示用户信息以及用户分数

此功能将用户名作为键后缀,将用户信息保存在哈希类型中,至于用户

的分数和排名可以使用zscore和zrank两个功能:

hgetall user:info:tom

zscore user:ranking:2016_03_15 mike

zrank user:ranking:2016_03_15 mike

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念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

推荐阅读更多精彩内容