Redis 核心数据结构(四)——Set 与 Sorted Set,去重与排名神器

73 阅读5分钟

集合帮你自动去重,有序集合还能帮你排个名——排行榜、共同好友、标签系统,Set / Sorted Set 一把搞定!

本次导航

  • Set:无序、唯一
  • Sorted Set:带分数的 Set,按分数排序
  • 核心命令:SADD、SINTER、ZADD、ZRANGE、ZRANK
  • 内部编码:intset vs hashtable,ziplist vs skiplist
  • 实战场景:共同好友、用户标签、实时排行榜、延时队列
  • 图解:交并差集 & 排行榜结构

发车前提醒:Set 和 Sorted Set 是 Redis 里“算法感”最强的两个结构,但用起来特别爽。

一、Set——无序且唯一

Set 就是一个自动去重的集合,里面的元素没有顺序,也不重复。你可以往里塞一堆东西,Redis 帮你保证每个元素只出现一次。

内部编码:

  • 元素都是整数且数量少 → intset(紧凑数组,省内存)
  • 其他情况 → hashtable(和 Hash 一样)

常用命令

命令功能
SADD key member [member...]添加一个或多个元素
SREM key member [member...]移除元素
SISMEMBER key member判断是否存在
SMEMBERS key获取所有元素(慎用,可能慢)
SCARD key获取元素个数
SINTER key1 key2取交集
SUNION key1 key2取并集
SDIFF key1 key2取差集(key1 里有但 key2 没有的)
SRANDMEMBER key count随机取 count 个元素
SPOP key随机弹出一个元素

动手试试:

SADD fruits "apple" "banana" "orange"
SADD tropical "banana" "mango" "coconut"

# 取交集(既是水果又是热带水果)
SINTER fruits tropical   # 返回 "banana"

# 取并集
SUNION fruits tropical   # "apple","banana","orange","mango","coconut"

# 取差集(水果里不属于热带的)
SDIFF fruits tropical    # "apple","orange"

Redis Set 集合运算演示

上图很直观的表达了集合关系。

二、Set 实战场景

场景1:共同好友(社交应用)

假设用户 A 的好友集合是 friends:A,用户 B 的是 friends:B:

SADD friends:A "U1" "U2" "U3" "U4"
SADD friends:B "U3" "U4" "U5" "U6"

# 共同好友
SINTER friends:A friends:B   # U3, U4

# A 可能认识的人(B 的好友减去 A 的好友)
SDIFF friends:B friends:A    # U5, U6

场景2:用户标签 / 兴趣推荐

给用户打标签,然后根据标签交集推荐内容:

SADD user:1001:tags "tech" "gaming" "music"
SADD user:1002:tags "gaming" "sports" "movie"

# 相同标签
SINTER user:1001:tags user:1002:tags   # "gaming"

# 推荐:取 user:1002 有但 user:1001 没有的标签
SDIFF user:1002:tags user:1001:tags    # "sports","movie"

场景3:抽奖 / 随机抽样

抽奖活动场景使用率相当的高,比如从参与用户集合里随机抽一个:

SADD lottery "user1" "user2" "user3" "user4" "user5"
SRANDMEMBER lottery 1        # 随机抽一个,不删除
SPOP lottery                 # 随机抽一个并移除(中奖后出局)

三、Sorted Set——带排名的 Set

Sorted Set(有序集合)在 Set 的基础上,给每个元素绑定了一个 score(分数),然后按照分数从小到大排序。

分数相同则按字典序。

内部编码:

  • 元素少且分数相近 → ziplist(压缩列表)
  • 元素多时 → skiplist + hashtable(跳表保证有序,哈希表保证快速查找)

常用命令

命令功能
ZADD key score member [score member...]添加元素并指定分数
ZRANGE key start stop [WITHSCORES]按分数升序取指定范围
ZREVRANGE按分数降序取
ZRANK key member获取元素排名(从0开始,升序)
ZREVRANK获取元素排名(降序)
ZSCORE key member获取元素分数
ZINCRBY key increment member增加元素分数
ZREM key member删除元素
ZCOUNT key min max统计分数在区间内的元素数量
ZRANGEBYSCORE按分数区间取元素

动手试试:

# 游戏排行榜:玩家分数
ZADD leaderboard 100 "Alice" 200 "Bob" 150 "Charlie"

# 按分数从低到高取全部(带分数)
ZRANGE leaderboard 0 -1 WITHSCORES
# 1) "Alice" 2) "100" 3) "Charlie" 4) "150" 5) "Bob" 6) "200"

# 按分数从高到低(排名靠前的)
ZREVRANGE leaderboard 0 2 WITHSCORES
# "Bob" 200, "Charlie" 150, "Alice" 100

# 查看 Alice 的排名(升序排名,0表示第一名)
ZRANK leaderboard "Alice"     # 0
ZREVRANK leaderboard "Alice"  # 2(倒数第一)

Sorted Set 逻辑结构

四、Sorted Set 实战场景

场景1:实时排行榜(游戏、热度)

每次玩家得分,就用 ZINCRBY 增加分数:

# 玩家 Bob 获得 50 分
ZINCRBY game_scores 50 "Bob"

# 获取 Top 5
ZREVRANGE game_scores 0 4 WITHSCORES

场景2:延时队列(拿分数当时间戳)

把任务作为 member,执行时间戳作为 score。消费者定期用 ZRANGEBYSCORE 取到期的任务:

# 添加延时任务:当前时间戳 + 延迟秒数
ZADD delay_queue 1735689600 "send_email:user1001"
ZADD delay_queue 1735690000 "clean_cache"

# 消费者(每隔几秒执行)
# 取出当前时间戳之前的所有任务
ZRANGEBYSCORE delay_queue 0 1735689700
# 然后删除取出的任务(ZREM)

场景3:商品热度 / 滑动窗口限流

用 Sorted Set 存储用户在某时间窗口内的访问记录,score 是时间戳:

# 用户 1001 在时间戳 1735689600 访问了一次
ZADD rate_limit:1001 1735689600 "req_1"

# 清理 60 秒前的记录
ZREMRANGEBYSCORE rate_limit:1001 0 1735689540

# 统计最近 60 秒内的请求数
ZCOUNT rate_limit:1001 1735689540 +inf

五、Set vs Sorted Set 选择指南

需求用哪个理由
只要去重,不要排序Set内存更省,操作更快
需要判断元素是否存在SetO(1) 时间复杂度
需要按分数排序Sorted Set自带排序,支持范围查询
需要频繁取 Top NSorted SetZREVRANGE 极快
需要按时间戳排序做队列Sorted Set用 score 存时间戳
需要集合运算(交并差)SetSorted Set 不支持直接集合运算

📢 觉得有用?

热烈欢迎:

  • 点赞 + 在看 支持一下
  • 关注公众号,接收第一首博客文章
  • 留言告诉我 你遇到过哪些有意思的排行榜 / 去重场景

我们下期见!🚀