最近做到个记录访问人员的需求,具体要求是:记录访问者的ID,一个用户只记入一次

刚看到这个需求就想到了集合,ES6 标准向 Javascript 里加入了 Set 这个结构。正好做到了这个,就想着用 Redisset 结构实现,而且 Redis 可以设置 ttl (time to live)。

Set

RedisSet 是一组无序字符串的集合,查找复杂度为 O(1),支持直接向其中插入、删除元素。并且插入前不需要考虑 Set 中是否存在需要插入的元素,这是 Set 的特性:相同的元素只会保留一个。

Redis 中使用 Set 最大的优点是 Redis 内置了许多操作方法,比如交集、并集、差集等等,我们可以直接使用 Redis 命令从既有 Set 生成新的 Set,而不需要书写代码。

Set 的最大容量是 2^32 - 1,足够应对大部分情形了。

常用命令

SADD

  • 操作:向 Set 插入元素

  • 返回:插入成功返回1,元素已存在返回0

    SADD myset "id1" // 1
    SADD myset "id1" // 0

SISMEMBER

  • 操作:查看元素在 Set 中是否存在

  • 返回:是返回1,否返回0

    SISMEMBER myset "id1" // 0
    SADD myset "id1" // 1
    SISMEMBER myset "id1" // 1

SCARD

  • 操作:查看 SetCardinality,表示Set 里存储的元素数量

  • 返回:Set 中存储的元素数量,如果 Set 不存在则返回0

    SCARD myset // 0
    SADD myset "id1" // 1
    SCARD myset // 1

SDIFF

  • 操作:计算第一个参数 Set 与剩余参数 Set 的差集,key 不存在会被当为空 Set

  • 返回:第一个参数 Set 与剩余参数 Set 的差集

    // myset1 {a, b, c, d}
    // myset2 {a}
    // myset3 {c}
    SDIFF myset1 myset2 myset3 // {b, d}

SDIFFSTORE

  • 操作:类似 SDIFF,但第一个参数表示将最终结果存入的 key,剩余的参数与 SDIFF 的参数含义一致

  • 返回:最终 SetCardinality,如果结果的key已存在,则会被覆盖

    // myset1 {a, b, c, d}
    // myset2 {a}
    // myset3 {c}
    SDIFF final myset1 myset2 myset3 // 2,final中的元素为b、d

SINTER

  • 操作:计算所有参数 Set 交集,key 不存在会被当为空 Set

  • 返回:返回参数 Set 的交集

    // myset1 {a, b, c, d}
    // myset2 {b, c, d}
    // myset3 {c, e}
    SINTER myset1 myset2 myset3 // {c}

SINTERSTORE

  • 操作:类似 SINTER,但第一个参数表示将最终结果存入的 key,剩余的参数与 SINTER 的参数含义一致

  • 返回:最终 SetCardinality,如果结果的key已存在,则会被覆盖

SMEMBERS

  • 操作:查看 Set 中存储的所有元素

  • 返回:目标 Set 的所有元素

    SADD myset "id1" // 1
    SADD myset "id2" // 1
    SMEMBERS myset // {"id1", "id2"}

SMOVE

  • 操作:将源 Set 中的一个元素移入目标 Set

  • 返回:设定操作的元素为A,如果源 Set 中不存在A,则不做任何操作,并返回0;如果源 Set 中存在A,则将A移入目标 Set,并返回1;如果源 Set 与 目标 Set 中均存在A,则将源 Set 中的A移除,并返回1;如果源与目标中的任何一个存在但不是 Set 结构,抛出错误

    // myset1 {"id1"}
    SMOVE myset1 myset2 "id1" // 1
    SMOVE myset1 myset2 "id2" // 0
    SET myset3 "string value"
    SMOVE myset2 myset3 "id1" // WRONGTYPE Operation against a key holding the wrong kind of value

SPOP

  • 操作:从 Set 中取出1或多个元素(会改动 Set),第二个参数为要取出的元素个数。

  • 返回:bulk string,表示取出的元素,如果key不存在,返回nil。SPOP 的第二个参数不能为负值,否则直接抛出错误

  • 注释:对应到 IORedis 库中,如果未指定第二个参数,返回一个 stringnull;如果指定了第二个参数,返回一个字符串数组(可能为空,可能 Cardinality 小于指定的参数大小),这块儿还有许多可以学习的地方,建议参考官方文档

  • 文档地址:SPOP-Redis

SRANDMEMBER

  • 操作:从 Set 中取出1或多个元素(不会改动 Set),第二个参数为要取出的元素个数

  • 返回:这个操作比较复杂,需要好好解释一下。

    • 当只有一个参数的时候,随机返回 Set 中的一个元素;Set 不存在或为空则返回 nil
    • 当加入了第二个 count 参数的时候,如果参数为正值,返回一个数组;如果参数为负值,这个命令可能会返回一个元素多次,返回数组的大小为 count 参数的绝对值。
  • 注释:对应到 IORedis 库中,如果未指定第二个参数,返回一个 stringnull;如果指定了第二个参数,返回一个字符串数组(Cardinality 可能会小于指定的参数大小),这块儿官方文档也有很多解释,包括返回元素的制定规则等等,建议参考

  • 文档地址:SRANDMEMBER-Redis

SREM

  • 操作:从 Set 中移除一个或多个元素

  • 返回:移除的元素数量,不包括 Set 中不存在的元素

    SADD myset "id1" // 1
    SADD myset "id2" // 2
    SREM myset "id1" "id3" // 1

SUNION

  • 操作:计算第一个参数 Set 与剩余参数 Set 的并集,key 不存在会被当为空 Set

  • 返回:第一个参数 Set 与剩余参数 Set 的并集

    // myset1 {a, b}
    // myset2 {a, e}
    // myset3 {c, g}
    SUNION myset1 myset2 myset3 // {a, b, c, e, g}

SUNIONSTORE

  • 操作:类似 SUNION,但第一个参数表示将最终结果存入的 key,剩余的参数与 SUNION 的参数含义一致

  • 返回:最终 SetCardinality,如果结果的key已存在,则会被覆盖

    // myset1 {a, b}
    // myset2 {a, e}
    // myset3 {c, g}
    SUNION myset1 myset2 myset3 // 5

SSCAN

  • 操作:对 Set 进行扫描,关于 RedisSCAN,会专门出一篇讲。