Redis开发与运维. 2.5 集合

    xiaoxiao2024-05-12  111

    2.5 集合

    集合(set)类型也是用来保存多个的字符串元素,但和列表类型不一样的是,集合中不允许有重复元素,并且集合中的元素是无序的,不能通过索引下标获取元素。如图2-22所示,集合user:1:follow包含着"it"、"music"、

    "his"、"sports"四个元素,一个集合最多可以存储232-1个元素。Redis除了支持集合内的增删改查,同时还支持多个集合取交集、并集、差集,合理地使用好集合类型,能在实际开发中解决很多实际问题。

    2.5.1 命令

    下面将按照集合内和集合间两个维度对集合的常用命令进行介绍。

    1.?集合内操作

    (1)添加元素

    sadd key element [element ...]

    返回结果为添加成功的元素个数,例如:

    127.0.0.1:6379> exists myset

    (integer) 0

    127.0.0.1:6379> sadd myset a b c

    (integer) 3

    127.0.0.1:6379> sadd myset a b

    (integer) 0

    (2)删除元素

    srem key element [element ...]

    返回结果为成功删除元素个数,例如:

    127.0.0.1:6379> srem myset a b

    (integer) 2

    127.0.0.1:6379> srem myset hello

    (integer) 0

    (3)计算元素个数

    scard key

    scard的时间复杂度为O(1),它不会遍历集合所有元素,而是直接用Redis内部的变量,例如:

    127.0.0.1:6379> scard myset

    (integer) 1

    (4)判断元素是否在集合中

    sismember key element

    如果给定元素element在集合内返回1,反之返回0,例如:

    127.0.0.1:6379> sismember myset c

    (integer) 1

    (5)随机从集合返回指定个数元素

    srandmember key [count]

    [count]是可选参数,如果不写默认为1,例如:

    127.0.0.1:6379> srandmember myset 2

    1) "a"

    2) "c"

    127.0.0.1:6379> srandmember myset

    "d"

    (6)从集合随机弹出元素

    spop key

    spop操作可以从集合中随机弹出一个元素,例如下面代码是一次spop后,集合元素变为"d b a":

    127.0.0.1:6379> spop myset

    "c"

    127.0.0.1:6379> smembers myset

    1) "d"

    2) "b"

    3) "a"

    需要注意的是Redis从3.2版本开始,spop也支持[count]参数。

    srandmember和spop都是随机从集合选出元素,两者不同的是spop命令执行后,元素会从集合中删除,而srandmember不会。

    (7)获取所有元素

    smembers key

    下面代码获取集合myset所有元素,并且返回结果是无序的:

    127.0.0.1:6379> smembers myset

    1) "d"

    2) "b"

    3) "a"

    smembers和lrange、hgetall都属于比较重的命令,如果元素过多存在阻塞Redis的可能性,这时候可以使用sscan来完成,有关sscan命令2.7节会介绍。

    2.?集合间操作

    现在有两个集合,它们分别是user:1:follow和user:2:follow:

    127.0.0.1:6379> sadd user:1:follow it music his sports

    (integer) 4

    127.0.0.1:6379> sadd user:2:follow it news ent sports

    (integer) 4

    (1)求多个集合的交集

    sinter key [key ...]

    例如下面代码是求user:1:follow和user:2:follow两个集合的交集,返回结果是sports、it:

    127.0.0.1:6379> sinter user:1:follow user:2:follow

    1) "sports"

    2) "it"

    (2)求多个集合的并集

    suinon key [key ...]

    例如下面代码是求user:1:follow和user:2:follow两个集合的并集,返回结果是sports、it、his、news、music、ent:

    127.0.0.1:6379> sunion user:1:follow user:2:follow

    1) "sports"

    2) "it"

    3) "his"

    4) "news"

    5) "music"

    6) "ent"

    (3)求多个集合的差集

    sdiff key [key ...]

    例如下面代码是求user:1:follow和user:2:follow两个集合的差集,返回结果是music和his:

    127.0.0.1:6379> sdiff user:1:follow user:2:follow

    1) "music"

    2) "his"

    前面三个命令如图2-23所示。

     

    图2-23 集合求交集、并集、差集

    (4)将交集、并集、差集的结果保存

    sinterstore destination key [key ...]

    suionstore  destination key [key ...]

    sdiffstore  destination key [key ...]

    集合间的运算在元素较多的情况下会比较耗时,所以Redis提供了上面三个命令(原命令 + store)将集合间交集、并集、差集的结果保存在destination key中,例如下面操作将user:1:follow和user:2:follow两个集合的交集结果保存在user:1_2:inter中,user:1_2:inter本身也是集合类型:

    127.0.0.1:6379> sinterstore user:1_2:inter user:1:follow user:2:follow

    (integer) 2

    127.0.0.1:6379> type user:1_2:inter

    set

    127.0.0.1:6379> smembers user:1_2:inter

    1) "it"

    2) "sports"

    至此有关集合的命令基本已经介绍完了,表2-6给出集合常用命令的时间复杂度,开发人员可以根据自身需求进行选择。

    表2-6 集合常用命令时间复杂度

    命  令         时间复杂度

    sadd key element [element ...]        O(k),k是元素个数

    srem key element [element ...]       O(k),k是元素个数

    scard key O(1)

    sismember key element  O(1)

    srandmember key [count]        O(count)

    spop key  O(1)

    smembers key O(n),n是元素总数

    sinter key [key ...] 或者 sinterstore      O(m*k),k是多个集合中元素最少的个数,m是键个数

    suinon key [key ...] 或者 suionstore      O(k),k是多个集合元素个数和

    sdiff key [key ...] 或者 sdiffstore   O(k),k是多个集合元素个数和

     

    2.5.2 内部编码

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

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

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

    下面用示例来说明:

    1)当元素个数较少且都为整数时,内部编码为intset:

    127.0.0.1:6379> sadd setkey 1 2 3 4

    (integer) 4

    127.0.0.1:6379> object encoding setkey

    "intset"

    2.1)当元素个数超过512个,内部编码变为hashtable:

    127.0.0.1:6379> sadd setkey 1 2 3 4 5 6 ... 512 513

    (integer) 509

    127.0.0.1:6379> scard setkey

    (integer) 513

    127.0.0.1:6379> object encoding listkey

    "hashtable"

    2.2)当某个元素不为整数时,内部编码也会变为hashtable:

    127.0.0.1:6379> sadd setkey a

    (integer) 1

    127.0.0.1:6379> object encoding setkey

    "hashtable"

    有关集合类型的内存优化技巧将在8.3节中详细介绍。

    2.5.3 使用场景

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

    下面使用集合类型实现标签功能的若干功能。

    (1)给用户添加标签

    sadd user:1:tags tag1 tag2 tag5

    sadd user:2:tags tag2 tag3 tag5

     ...

    sadd user:k:tags tag1 tag2 tag4

    ...

    (2)给标签添加用户

    sadd tag1:users user:1 user:3

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

    ...

    sadd tagk:users user:1 user:2

    ...

    用户和标签的关系维护应该在一个事务内执行,防止部分命令失败造成的数据不一致,有关如何将两个命令放在一个事务,第3章会介绍事务以及Lua的使用方法。

    (3)删除用户下的标签

    srem user:1:tags tag1 tag5

    ...

    (4)删除标签下的用户

    srem tag1:users user:1

    srem tag5:users user:1

    ...

    (3)和(4)也是尽量放在一个事务执行。

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

    可以使用sinter命令,来计算用户共同感兴趣的标签,如下代码所示:

    sinter user:1:tags user:2:tags

    前面只是给出了使用Redis集合类型实现标签的基本思路,实际上一个标签系统远比这个要复杂得多,不过集合类型的应用场景通常为以下几种:

    sadd = Tagging(标签)

    spop/srandmember = Random item(生成随机数,比如抽奖)

    sadd + sinter = Social Graph(社交需求)?

    相关资源:敏捷开发V1.0.pptx
    最新回复(0)