• 周日. 6 月 16th, 2024

    Redis数据类型之Set

    root

    4 月 28, 2022 #Redis数据类型Set

    Set类型是string类型的集合

    特点:

    集合元素无序且不重复,每个集合最多可存储2^32-1个元素(40多亿)

    应用场景:

    1、好友、关注、粉丝、感兴趣的人集合

    集合中的元素数量可能很多,每次全部取出来成本不小,set类型提供了一些很实用的命令用于直接操作这些集合

    a. sinter命令可以获得A和B两个用户的共同好友

    b. sismember命令可以判断A是否是B的好友

    c. scard命令可以获取好友数量

    d. 关注时,smove命令可以将B从A的粉丝集合转移到A的好友集合。

    需要注意的是,如果你用的是Redis Cluster集群,对于sinter, smove这种操作多个key的命令,要求这两个key必须存储在同一个slot(槽位)中,否则会报出(error) CROSSSLOT Keys in request don’t hash to the same slot错误。

    Redis Cluster一共有16384个slot,每个key都是通过哈希算法CRC16(key)获取数值哈希,再模16384来定位slot的。

    要使得两个key处于同一个slot,除了两个key一模一样,还有没有别的办法呢?

    答案是肯定的,Redis提供了一种Hash Tag的功能,在key中使用{}括起key中的一部分,在进行CRC16(key) mod 16384的过程中,只会对{}内的字符串计算,例如friend_set:{123456}和fans_set:{123456}, 分别表示用户123456的好友集和粉丝集合,在定位slot时,只对{}内的123456进行计算,所以这两个集合肯定是同一个slot内的,当用户123456关注某个粉丝时,就可以通过smove命令将粉丝从用户123456的粉丝集合移动到好友集合。

    相比于通过srem命令先将这个粉丝从粉丝集合中删除,再通过sadd命令将这个粉丝加到好友集合,smove命令的优势是它是原子性的,不会出现这个粉丝从粉丝集合中被删除,却没有加到好友集合的情况。

    然而,对于通过sinter获取共同好友而言,Hash Tag则无能为力,例如:要用sinter去获取用户123456和456789两个用户的共同好友,除非我们将key定义为{friend_set}:123456和{friend_set}:456789, 否则不能保证两个key会处于同一个slot,但是如果真这样的话,所有用户的好友集合都会堆积在同一个slot中,数据分布会严重不均匀,所以, 在实战中使用Redis Cluster时,sinter这个命令其实是不适合作用于两上不同用户对应的集合的(同理其它操作多个key的命令).

    2. 随机展示

    通常,app首页的展示区域有限,但是又不能总是展示固定的内容,一种做法是先确定一批需要展示的内容,再从中随机获取。如下图所示,酷狗音乐K歌擂台赛当日的打擂歌曲共29首,首页随机展示5首;昨日打擂金典共200首,首页随机展示30首。

    set类型适合存放所有需要展示的内容,而srandmember命令则可以从中随机获取几个。

    3. 黑名单/白名单

    经常有业务出于安全性方面的考虑,需要设置用户黑名单, ip黑名单, 设备黑名单等。 set类型适合存储这些黑名单数据,sismember命令可用于判断用户,ip, 设备是否处于黑名单中。set中的值是不能重复的。

    127.0.0.1:6379> sadd myset "hello"  //set集合中添加元素
    (integer) 1
    127.0.0.1:6379> sadd myset "kuangshen"
    (integer) 1
    127.0.0.1:6379> sadd myset "lovekangshen
    Invalid argument(s)
    127.0.0.1:6379> smembers myset
    1) "hello"
    2) "kuangshen"
    127.0.0.1:6379> sadd myset "lovekangshen"
    (integer) 1
    127.0.0.1:6379> smembers myset
    1) "hello"
    2) "lovekangshen"
    3) "kuangshen"
    127.0.0.1:6379> sismember myset hello //判断某一个值是不是在set集合中
    (integer) 1
    127.0.0.1:6379> sismember myset world
    (integer) 0
    127.0.0.1:6379> scard myset  获取set集合中的元素个数
    (integer) 3
    127.0.0.1:6379> sadd myset "lovekangheng"
    (integer) 1
    127.0.0.1:6379> sadd myset "lovekangshen2"
    (integer) 1
    127.0.0.1:6379>
    127.0.0.1:6379> srem myset hello  //移除set集合中的指定元素
    (integer) 1
    127.0.0.1:6379> scard myset
    (integer) 4
    127.0.0.1:6379> smembers myset
    1) "lovekangshen"
    2) "kuangshen"
    3) "lovekangheng"
    4) "lovekangshen2"
    127.0.0.1:6379>
    127.0.0.1:6379> srandmember myset //随机抽选出指定个数的元素
    "lovekangshen"
    127.0.0.1:6379> smembers myset
    1) "kuangshen"
    2) "lovekangheng"
    3) "lovekangshen"
    4) "lovekangshen2"
    127.0.0.1:6379> 
    127.0.0.1:6379> spop myset //随机删除一些set集合中的元素
    "lovekangheng"
    127.0.0.1:6379> spop myset
    "kuangshen"
    127.0.0.1:6379> smembers myset
    1) "lovekangshen"
    2) "lovekangshen2"
    127.0.0.1:6379> 
    127.0.0.1:6379> sadd myset "hello"
    (integer) 1
    127.0.0.1:6379> sadd myset "world"
    (integer) 1
    127.0.0.1:6379> sadd myset "world1"
    (integer) 1
    127.0.0.1:6379> sadd myset2 "set2"
    (integer) 1
    127.0.0.1:6379> 
    127.0.0.1:6379> smove myset myset2 "hello"  //将一个集合中的指定元素移动到另一个集合中
    (integer) 1
    127.0.0.1:6379> smembrs myset
    (error) ERR unknown command `smembrs`, with args beginning with: `myset`, 
    127.0.0.1:6379> smembers myset
    1) "world1"
    2) "world"
    3) "lovekangshen"
    4) "lovekangshen2"
    127.0.0.1:6379> smembers myset2
    1) "hello"
    2) "set2"
    127.0.0.1:6379>
    127.0.0.1:6379> sdiff key1 key2  //差集
    1) "d"
    2) "b"
    3) "a"
    4) "v"
    127.0.0.1:6379> sinter key1 key2  //交集  共同好友就可以这样实现
    1) "c"
    127.0.0.1:6379> sunion key1 key2  //并集
    1) "e"
    2) "b"
    3) "d"
    4) "a"
    5) "v"
    6) "c"

    root