【Redis篇】Set 与 Zset:集合运算与排行榜的终极武器

文章目录

    • [Redis Set 与 Zset:集合运算与排行榜的终极武器](#Redis Set 与 Zset:集合运算与排行榜的终极武器)
    • 一、前言
    • [二、Set 集合](#二、Set 集合)
      • [2.1 Set 是什么](#2.1 Set 是什么)
      • [2.2 三种数据类型对比](#2.2 三种数据类型对比)
    • [三、Set 的基本命令](#三、Set 的基本命令)
      • [3.1 SADD ------ 添加元素](#3.1 SADD —— 添加元素)
      • [3.2 SMEMBERS ------ 获取所有元素](#3.2 SMEMBERS —— 获取所有元素)
      • [3.3 SISMEMBER ------ 判断元素是否存在](#3.3 SISMEMBER —— 判断元素是否存在)
      • [3.4 SCARD ------ 获取元素个数](#3.4 SCARD —— 获取元素个数)
      • [3.5 SPOP ------ 随机弹出元素](#3.5 SPOP —— 随机弹出元素)
      • [3.6 SMOVE ------ 移动元素](#3.6 SMOVE —— 移动元素)
      • [3.7 SREM ------ 删除元素](#3.7 SREM —— 删除元素)
    • 四、集合间运算
      • [4.1 SINTER 和 SINTERSTORE ------ 交集](#4.1 SINTER 和 SINTERSTORE —— 交集)
      • [4.2 SUNION 和 SUNIONSTORE ------ 并集](#4.2 SUNION 和 SUNIONSTORE —— 并集)
      • [4.3 SDIFF 和 SDIFFSTORE ------ 差集](#4.3 SDIFF 和 SDIFFSTORE —— 差集)
    • [五、Set 命令速查表](#五、Set 命令速查表)
    • [六、Set 的内部编码与使用场景](#六、Set 的内部编码与使用场景)
      • [6.1 内部编码](#6.1 内部编码)
      • [6.2 经典场景:标签系统](#6.2 经典场景:标签系统)
      • [6.3 其他典型场景](#6.3 其他典型场景)
    • [七、Zset 有序集合](#七、Zset 有序集合)
      • [7.1 Zset 是什么](#7.1 Zset 是什么)
      • [7.2 Zset vs List vs Set](#7.2 Zset vs List vs Set)
    • [八、Zset 的基本命令](#八、Zset 的基本命令)
      • [8.1 ZADD ------ 添加元素](#8.1 ZADD —— 添加元素)
      • [8.2 ZCARD ------ 获取元素个数](#8.2 ZCARD —— 获取元素个数)
      • [8.3 ZSCORE ------ 获取指定元素的分数](#8.3 ZSCORE —— 获取指定元素的分数)
      • [8.4 ZRANK 和 ZREVRANK ------ 获取元素的排名](#8.4 ZRANK 和 ZREVRANK —— 获取元素的排名)
      • [8.5 ZRANGE 和 ZREVRANGE ------ 按排名获取元素](#8.5 ZRANGE 和 ZREVRANGE —— 按排名获取元素)
      • [8.6 ZRANGEBYSCORE ------ 按分数范围获取元素](#8.6 ZRANGEBYSCORE —— 按分数范围获取元素)
      • [8.7 ZCOUNT ------ 统计分数范围内的元素个数](#8.7 ZCOUNT —— 统计分数范围内的元素个数)
      • [8.8 ZPOPMAX 和 ZPOPMIN ------ 弹出最大/最小元素](#8.8 ZPOPMAX 和 ZPOPMIN —— 弹出最大/最小元素)
      • [8.9 ZINCRBY ------ 增加元素的分数](#8.9 ZINCRBY —— 增加元素的分数)
      • [8.10 ZREM 系列 ------ 删除元素](#8.10 ZREM 系列 —— 删除元素)
    • [九、Zset 集合间运算](#九、Zset 集合间运算)
      • [9.1 聚合方式与权重](#9.1 聚合方式与权重)
      • [9.2 ZINTERSTORE ------ 交集运算](#9.2 ZINTERSTORE —— 交集运算)
      • [9.3 ZUNIONSTORE ------ 并集运算](#9.3 ZUNIONSTORE —— 并集运算)
    • [十、Zset 命令速查表](#十、Zset 命令速查表)
    • [十一、Zset 的内部编码与使用场景](#十一、Zset 的内部编码与使用场景)
      • [11.1 内部编码](#11.1 内部编码)
      • [11.2 经典场景:排行榜系统](#11.2 经典场景:排行榜系统)
      • [11.3 其他典型场景](#11.3 其他典型场景)
    • 十二、总结
      • [12.1 五种数据类型横向对比](#12.1 五种数据类型横向对比)
      • [12.2 使用注意事项](#12.2 使用注意事项)

Redis Set 与 Zset:集合运算与排行榜的终极武器

一、前言

💬 这一篇讲什么:Redis 五种数据类型中的最后两种 ------ Set 集合 和 Zset 有序集合

🚀 核心内容

  • Set 是什么?和 List 有什么区别?
  • 集合间的交集、并集、差集运算怎么用?
  • Zset 有序集合的"分数"机制是什么?
  • 标签系统、共同好友、排行榜 ------ 这些经典场景怎么实现?

上一篇学完了 List 列表,这一篇把剩下的两种数据类型 Set 和 Zset 一起讲完。Set 擅长去重和集合运算,是实现"共同好友""标签系统"的利器;Zset 在 Set 的基础上增加了排序能力,是实现"排行榜"的标准方案。


二、Set 集合

2.1 Set 是什么

Set 类型也是用来存储多个字符串元素的,但和 List 有两个关键不同:

第一,元素之间是无序的。 不能通过索引下标访问元素。

第二,元素不允许重复。 同一个值在集合中只会出现一次。

text 复制代码
集合:user:1:subscribe

       { ESports }
       { IT }
       { Music }
       { Movie }
       { Basketball }

一个 Set 最多可以存储 2³² - 1 个元素。

除了基本的增删查操作,Set 最强大的能力是集合间运算:交集、并集、差集。这让 Set 在很多业务场景下都有独特的优势。

2.2 三种数据类型对比

数据结构 是否允许重复 是否有序 有序依据 典型场景
List 列表 ✅ 是 ✅ 是 插入顺序(索引下标) 时间线、消息队列
Set 集合 ❌ 否 ❌ 否 --- 标签、共同好友
Zset 有序集合 ❌ 否 ✅ 是 分数(score) 排行榜系统

三、Set 的基本命令

3.1 SADD ------ 添加元素

将一个或多个元素添加到 Set 中。重复的元素无法添加成功。

语法

bash 复制代码
SADD key member [member ...]

时间复杂度:O(1)

返回值:本次实际添加成功的元素个数(重复元素不计入)。

示例

bash 复制代码
redis> SADD myset "Hello"
(integer) 1
redis> SADD myset "World"
(integer) 1
redis> SADD myset "World"
(integer) 0          # 已存在,未添加
redis> SMEMBERS myset
1) "Hello"
2) "World"

3.2 SMEMBERS ------ 获取所有元素

获取 Set 中所有的元素。注意:返回的元素顺序是不确定的。

语法

bash 复制代码
SMEMBERS key

时间复杂度:O(N)

示例

bash 复制代码
redis> SADD myset "Hello"
(integer) 1
redis> SADD myset "World"
(integer) 1
redis> SMEMBERS myset
1) "Hello"
2) "World"

⚠️ 和 HGETALL 一样,对于元素很多的 Set,SMEMBERS 会一次性返回所有元素,可能导致 Redis 阻塞。生产环境中遍历大集合应使用 SSCAN(渐进式遍历)。

3.3 SISMEMBER ------ 判断元素是否存在

判断某个元素是否在 Set 中。这是 Set 最快的核心能力之一。

语法

bash 复制代码
SISMEMBER key member

时间复杂度:O(1)

返回值:1 表示存在,0 表示不存在或 key 不存在。

示例

bash 复制代码
redis> SADD myset "one"
(integer) 1
redis> SISMEMBER myset "one"
(integer) 1
redis> SISMEMBER myset "two"
(integer) 0

3.4 SCARD ------ 获取元素个数

获取 Set 的基数(cardinality),也就是元素个数。

语法

bash 复制代码
SCARD key

时间复杂度:O(1)

示例

bash 复制代码
redis> SADD myset "Hello" "World"
(integer) 2
redis> SCARD myset
(integer) 2

3.5 SPOP ------ 随机弹出元素

从 Set 中随机删除并返回一个或多个元素。因为 Set 是无序的,所以取出哪个元素是不确定的,可以视为随机。

语法

bash 复制代码
SPOP key [count]

时间复杂度:O(N),N 为 count。

示例

bash 复制代码
redis> SADD myset "one" "two" "three"
(integer) 3
redis> SPOP myset
"one"
redis> SMEMBERS myset
1) "three"
2) "two"

# 一次弹出多个
redis> SADD myset "four" "five"
(integer) 2
redis> SPOP myset 3
1) "three"
2) "four"
3) "two"

3.6 SMOVE ------ 移动元素

将一个元素从源 Set 移动到目标 Set。

语法

bash 复制代码
SMOVE source destination member

时间复杂度:O(1)

返回值:1 表示移动成功,0 表示元素不存在或失败。

示例

bash 复制代码
redis> SADD myset "one" "two"
(integer) 2
redis> SADD myotherset "three"
(integer) 1
redis> SMOVE myset myotherset "two"
(integer) 1
redis> SMEMBERS myset
1) "one"
redis> SMEMBERS myotherset
1) "three"
2) "two"

3.7 SREM ------ 删除元素

从 Set 中删除指定的一个或多个元素。

语法

bash 复制代码
SREM key member [member ...]

时间复杂度:O(N),N 为要删除的元素个数。

返回值:实际删除的元素个数。

示例

bash 复制代码
redis> SADD myset "one" "two" "three"
(integer) 3
redis> SREM myset "one"
(integer) 1
redis> SREM myset "four"
(integer) 0
redis> SMEMBERS myset
1) "three"
2) "two"

四、集合间运算

这是 Set 最强大的能力。来看三个核心运算:

test 复制代码
key1 = {a, b, c, d, f}
key2 = {a, c, f, h, i}

交集 SINTER key1 key2 = {a, c, f}     共同拥有的元素
并集 SUNION key1 key2 = {a, b, c, d, f, h, i}   两者所有的元素
差集 SDIFF  key1 key2 = {b, d}        key1 有但 key2 没有的元素

4.1 SINTER 和 SINTERSTORE ------ 交集

SINTER 返回多个 Set 的交集元素;SINTERSTORE 把交集结果保存到目标 Set 中。

语法

bash 复制代码
SINTER key [key ...]
SINTERSTORE destination key [key ...]

时间复杂度:O(N × M),N 为最小集合的元素个数,M 为集合个数。

示例

bash 复制代码
redis> SADD key1 "a" "b" "c"
(integer) 3
redis> SADD key2 "c" "d" "e"
(integer) 3

# 直接返回交集
redis> SINTER key1 key2
1) "c"

# 把交集保存到新的 key
redis> SINTERSTORE result key1 key2
(integer) 1
redis> SMEMBERS result
1) "c"

4.2 SUNION 和 SUNIONSTORE ------ 并集

SUNION 返回多个 Set 的并集元素;SUNIONSTORE 把并集结果保存到目标 Set 中。

语法

bash 复制代码
SUNION key [key ...]
SUNIONSTORE destination key [key ...]

时间复杂度:O(N),N 为所有集合的总元素个数。

示例

bash 复制代码
redis> SADD key1 "a" "b" "c"
(integer) 3
redis> SADD key2 "c" "d" "e"
(integer) 3
redis> SUNION key1 key2
1) "a"
2) "c"
3) "e"
4) "b"
5) "d"

4.3 SDIFF 和 SDIFFSTORE ------ 差集

SDIFF 返回第一个 Set 中独有的元素(其他 Set 中不存在的);SDIFFSTORE 把差集结果保存到目标 Set 中。

语法

bash 复制代码
SDIFF key [key ...]
SDIFFSTORE destination key [key ...]

时间复杂度:O(N),N 为所有集合的总元素个数。

示例

bash 复制代码
redis> SADD key1 "a" "b" "c"
(integer) 3
redis> SADD key2 "c" "d" "e"
(integer) 3
redis> SDIFF key1 key2
1) "a"
2) "b"        # c 在 key2 中,被排除

💡 注意顺序:SDIFF key1 key2 返回的是 key1 中有但 key2 中没有的元素,不等于 SDIFF key2 key1


五、Set 命令速查表

命令 执行效果 时间复杂度
SADD key element [...] 添加元素 O(k)
SREM key element [...] 删除元素 O(k)
SCARD key 获取元素个数 O(1)
SISMEMBER key element 判断元素是否存在 O(1)
SMEMBERS key 获取所有元素 O(k)
SPOP key [count] 随机弹出元素 O(n)
SMOVE src dst member 移动元素 O(1)
SINTER key [...] 交集 O(n×m)
SUNION key [...] 并集 O(n)
SDIFF key [...] 差集 O(n)
SINTERSTORE / SUNIONSTORE / SDIFFSTORE 运算结果存入新 key 同上

六、Set 的内部编码与使用场景

6.1 内部编码

Set 的内部编码有两种:

intset(整数集合):当满足以下两个条件时使用:

  • 集合中所有元素都是整数
  • 元素个数小于 set-max-intset-entries 配置(默认 512)

intset 是一种紧凑的整数存储结构,内存利用率非常高。

hashtable(哈希表):不满足 intset 条件时使用,提供 O(1) 的查询性能。

bash 复制代码
# 全是整数且数量少 → intset
127.0.0.1:6379> sadd setkey 1 2 3 4
(integer) 4
127.0.0.1:6379> object encoding setkey
"intset"

# 有非整数元素 → 立即升级为 hashtable
127.0.0.1:6379> sadd setkey "a"
(integer) 1
127.0.0.1:6379> object encoding setkey
"hashtable"

6.2 经典场景:标签系统

标签(tag)是 Set 最经典的使用场景。比如电商网站会给用户打标签来做个性化推荐。

给用户添加标签

bash 复制代码
SADD user:1:tags "数码" "篮球" "电影"
SADD user:2:tags "篮球" "美食" "旅游"
SADD user:3:tags "数码" "美食" "音乐"

反向:给标签添加用户(便于按标签找用户):

bash 复制代码
SADD tag:数码:users "user:1" "user:3"
SADD tag:篮球:users "user:1" "user:2"

计算用户的共同兴趣

bash 复制代码
# user:1 和 user:2 都喜欢什么?
redis> SINTER user:1:tags user:2:tags
1) "篮球"

推荐与用户兴趣相似的好友

bash 复制代码
# 找出所有也喜欢"数码"的用户
redis> SMEMBERS tag:数码:users
1) "user:1"
2) "user:3"

计算用户的所有兴趣(合并多个标签源):

bash 复制代码
SUNION user:1:tags user:1:imported_tags

6.3 其他典型场景

场景一:共同好友

bash 复制代码
SADD user:1:friends "Alice" "Bob" "Charlie"
SADD user:2:friends "Bob" "Charlie" "David"

# 求共同好友
redis> SINTER user:1:friends user:2:friends
1) "Bob"
2) "Charlie"

场景二:UV 统计(独立访客)

bash 复制代码
# 记录今天访问过的用户 ID
SADD uv:2025-11-15 "user:1001" "user:1002" "user:1001"  # 重复的会被自动去重

# 获取今天的 UV 数
redis> SCARD uv:2025-11-15
(integer) 2

场景三:抽奖系统

bash 复制代码
# 添加抽奖参与者
SADD lottery:participants "user:1" "user:2" "user:3" "user:4"

# 随机抽 2 个中奖者
redis> SPOP lottery:participants 2
1) "user:2"
2) "user:4"

七、Zset 有序集合

7.1 Zset 是什么

Zset(Sorted Set)相对前几种类型来说会陌生一些。它保留了 Set 不允许重复元素的特点,但每个元素都关联一个浮点数类型的分数(score),元素按照分数排序。

text 复制代码
有序集合:user:ranking

  元素       分数
  关羽   →   97.8
  张飞   →   97.8
  夏侯惇 →   96.5
  周泰   →   96.2
  甘宁   →   94.2
  黄祖   →   94.0
  严颜   →   93.2

关键规则

  • 元素(member)不能重复
  • 分数(score)可以重复
  • 排序的依据是分数,不是插入顺序

💡 可以类比一次考试:每个学生(元素)必须有唯一的学号(不能重复),但成绩(分数)可以相同。

7.2 Zset vs List vs Set

特性 List Set Zset
是否有序 ✅ 按插入顺序 ❌ 无序 ✅ 按分数排序
是否允许重复 ✅ 允许 ❌ 不允许 ❌ 不允许(元素)
按值查询 O(N) O(1) O(log N)
排名查询 不支持 不支持 支持,O(log N)

八、Zset 的基本命令

8.1 ZADD ------ 添加元素

向 Zset 中添加元素及其分数。分数应为浮点数(double 类型),支持 +inf-inf 表示正负无穷大。

语法

bash 复制代码
ZADD key [NX | XX] [GT | LT] [CH] [INCR] score member [score member ...]

选项说明

选项 含义
NX 仅添加新元素,不更新已存在的元素
XX 仅更新已存在的元素,不添加新元素
CH 默认返回新增元素数;指定 CH 后,返回值还包括分数被修改的元素数
INCR 类似 ZINCRBY,将分数加到现有分数上(只能指定一个元素)

时间复杂度:O(log N)

返回值:本次新增的元素个数。

示例

bash 复制代码
# 添加基本元素
redis> ZADD myzset 1 "one"
(integer) 1
redis> ZADD myzset 2 "two" 3 "three"   # 一次添加多个
(integer) 2
redis> ZRANGE myzset 0 -1 WITHSCORES
1) "one"
2) "1"
3) "two"
4) "2"
5) "three"
6) "3"

# 更新已有元素的分数(默认行为)
redis> ZADD myzset 100 "one"
(integer) 0       # 0 个新增(虽然分数被更新)

# CH 选项:把更新也算进返回值
redis> ZADD myzset CH 200 "one" 300 "four"
(integer) 2       # 1 个更新 + 1 个新增

# XX:只更新,不新增
redis> ZADD myzset XX 999 "five"
(integer) 0       # five 不存在,未操作

# NX:只新增,不更新
redis> ZADD myzset NX 999 "one"
(integer) 0       # one 已存在,未操作

# INCR:增量更新
redis> ZADD myzset INCR 10 "one"
"210"             # 原值 200 + 10

# 支持无穷大
redis> ZADD myzset -inf "negative" +inf "positive"
(integer) 2

8.2 ZCARD ------ 获取元素个数

bash 复制代码
redis> ZADD myzset 1 "one" 2 "two"
(integer) 2
redis> ZCARD myzset
(integer) 2

时间复杂度:O(1)

8.3 ZSCORE ------ 获取指定元素的分数

bash 复制代码
redis> ZADD myzset 1 "one"
(integer) 1
redis> ZSCORE myzset "one"
"1"

时间复杂度:O(1)

8.4 ZRANK 和 ZREVRANK ------ 获取元素的排名

ZRANK 返回元素的升序排名(从 0 开始);ZREVRANK 返回降序排名。

语法

bash 复制代码
ZRANK key member
ZREVRANK key member

时间复杂度:O(log N)

示例

bash 复制代码
redis> ZADD myzset 1 "one" 2 "two" 3 "three"
(integer) 3
redis> ZRANK myzset "three"
(integer) 2     # 升序排名(从小到大),从 0 开始,所以 "three" 排第 3 名
redis> ZREVRANK myzset "one"
(integer) 2     # 降序排名,"one" 是最小的,所以倒数排名第 3
redis> ZRANK myzset "four"
(nil)

8.5 ZRANGE 和 ZREVRANGE ------ 按排名获取元素

ZRANGE 按分数升序获取指定区间的元素;ZREVRANGE 按降序获取。可以带 WITHSCORES 选项同时返回分数。

语法

bash 复制代码
ZRANGE key start stop [WITHSCORES]
ZREVRANGE key start stop [WITHSCORES]

注意 :这里的 start 和 stop 是排名下标(从 0 开始),不是分数。

时间复杂度:O(log N + M),M 为返回元素个数。

示例

bash 复制代码
redis> ZADD myzset 1 "one" 2 "two" 3 "three"
(integer) 3

# 升序,全部元素
redis> ZRANGE myzset 0 -1
1) "one"
2) "two"
3) "three"

# 带分数
redis> ZRANGE myzset 0 -1 WITHSCORES
1) "one"
2) "1"
3) "two"
4) "2"
5) "three"
6) "3"

# 降序
redis> ZREVRANGE myzset 0 -1
1) "three"
2) "two"
3) "one"

# 倒数两个元素
redis> ZRANGE myzset -2 -1
1) "two"
2) "three"

8.6 ZRANGEBYSCORE ------ 按分数范围获取元素

返回分数在 min, max 区间内的元素,分数升序。默认 min 和 max 都是包含的,使用 ( 前缀表示不包含。

语法

bash 复制代码
ZRANGEBYSCORE key min max [WITHSCORES]

时间复杂度:O(log N + M)

示例

bash 复制代码
redis> ZADD myzset 1 "one" 2 "two" 3 "three"
(integer) 3

# 所有元素
redis> ZRANGEBYSCORE myzset -inf +inf
1) "one"
2) "two"
3) "three"

# 分数在 [1, 2] 之间
redis> ZRANGEBYSCORE myzset 1 2
1) "one"
2) "two"

# 分数在 (1, 2] 之间(开区间用 ( 表示)
redis> ZRANGEBYSCORE myzset (1 2
1) "two"

# 分数在 (1, 2) 之间
redis> ZRANGEBYSCORE myzset (1 (2
(empty array)

8.7 ZCOUNT ------ 统计分数范围内的元素个数

bash 复制代码
redis> ZADD myzset 1 "one" 2 "two" 3 "three"
(integer) 3
redis> ZCOUNT myzset -inf +inf
(integer) 3
redis> ZCOUNT myzset 1 3
(integer) 3
redis> ZCOUNT myzset (1 3
(integer) 2
redis> ZCOUNT myzset (1 (3
(integer) 1

时间复杂度:O(log N)

8.8 ZPOPMAX 和 ZPOPMIN ------ 弹出最大/最小元素

ZPOPMAX 删除并返回分数最高的 count 个元素;ZPOPMIN 删除并返回分数最低的 count 个元素。

语法

bash 复制代码
ZPOPMAX key [count]
ZPOPMIN key [count]

时间复杂度:O(log N × M),M 为 count。

示例

bash 复制代码
redis> ZADD myzset 1 "one" 2 "two" 3 "three"
(integer) 3
redis> ZPOPMAX myzset
1) "three"
2) "3"
redis> ZPOPMIN myzset
1) "one"
2) "1"

类似 List 的阻塞命令,Zset 也有 BZPOPMAXBZPOPMIN 阻塞版本,在 Zset 为空时阻塞等待新元素加入,用法和 BLPOP/BRPOP 完全类似。

8.9 ZINCRBY ------ 增加元素的分数

为指定元素的分数加上一个值(支持负数,相当于减)。

语法

bash 复制代码
ZINCRBY key increment member

时间复杂度:O(log N)

返回值:变化后的新分数。

示例

bash 复制代码
redis> ZADD myzset 1 "one" 2 "two"
(integer) 2
redis> ZINCRBY myzset 2 "one"
"3"
redis> ZRANGE myzset 0 -1 WITHSCORES
1) "two"
2) "2"
3) "one"
4) "3"

8.10 ZREM 系列 ------ 删除元素

ZREM:删除指定元素
bash 复制代码
redis> ZADD myzset 1 "one" 2 "two" 3 "three"
(integer) 3
redis> ZREM myzset "two"
(integer) 1
redis> ZRANGE myzset 0 -1 WITHSCORES
1) "one"
2) "1"
3) "three"
4) "3"
ZREMRANGEBYRANK:按排名范围删除
bash 复制代码
redis> ZADD myzset 1 "one" 2 "two" 3 "three"
(integer) 3
redis> ZREMRANGEBYRANK myzset 0 1   # 删除排名 0~1 的元素
(integer) 2
redis> ZRANGE myzset 0 -1 WITHSCORES
1) "three"
2) "3"
ZREMRANGEBYSCORE:按分数范围删除
bash 复制代码
redis> ZADD myzset 1 "one" 2 "two" 3 "three"
(integer) 3
redis> ZREMRANGEBYSCORE myzset -inf (2
(integer) 1
redis> ZRANGE myzset 0 -1 WITHSCORES
1) "two"
2) "2"
3) "three"
4) "3"

九、Zset 集合间运算

Zset 也支持类似 Set 的交集、并集运算,但更强大:可以指定权重聚合方式,让分数按规则合并。

9.1 聚合方式与权重

当两个 Zset 中有相同的元素,需要合并到一个新 Zset 中时,这个元素的分数怎么算?Redis 提供了三种聚合方式:

聚合方式 含义
SUM(默认) 多个分数相加
MAX 取最大的分数
MIN 取最小的分数

同时还可以给每个 Zset 设置一个权重:在聚合之前,先把每个 Zset 中元素的分数乘以对应的权重。

9.2 ZINTERSTORE ------ 交集运算

求多个 Zset 的交集,结果存入目标 Zset 中。

语法

bash 复制代码
ZINTERSTORE destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM | MIN | MAX]

参数说明

  • numkeys:参与运算的 key 的个数
  • WEIGHTS:每个 key 的权重,顺序与 key 对应
  • AGGREGATE:聚合方式,默认 SUM

时间复杂度:O(N × K) + O(M × log M)

示例

假设有两个 Zset:

bash 复制代码
zset1: { one: 1, two: 2 }
zset2: { one: 1, two: 2, three: 3 }

求交集,分别使用不同的权重和聚合方式:

bash 复制代码
redis> ZADD zset1 1 "one" 2 "two"
(integer) 2
redis> ZADD zset2 1 "one" 2 "two" 3 "three"
(integer) 3

# 默认 SUM 聚合,无权重
redis> ZINTERSTORE out1 2 zset1 zset2
(integer) 2
redis> ZRANGE out1 0 -1 WITHSCORES
1) "one"
2) "2"          # 1 + 1 = 2
3) "two"
4) "4"          # 2 + 2 = 4

# 指定权重 2 和 3
redis> ZINTERSTORE out2 2 zset1 zset2 WEIGHTS 2 3
(integer) 2
redis> ZRANGE out2 0 -1 WITHSCORES
1) "one"
2) "5"          # 1×2 + 1×3 = 5
3) "two"
4) "10"         # 2×2 + 2×3 = 10

来一个更直观的例子,把两个用户的游戏积分合并:

text 复制代码
zset1(普通积分): { mike: 91, tom: 251, martin: 250 }
zset2(活动积分): { mike: 77, tom: 888, martin: 625 }
元素 聚合方式 计算过程 结果
mike SUM 91 + 77 168
mike MAX max(91, 77) 91
mike MIN min(91, 77) 77
mike SUM with WEIGHTS 0.5, 2 91×0.5 + 77×2 199.5

9.3 ZUNIONSTORE ------ 并集运算

求多个 Zset 的并集,结果存入目标 Zset 中。规则和 ZINTERSTORE 完全一致。

语法

bash 复制代码
ZUNIONSTORE destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM | MIN | MAX]

示例

bash 复制代码
redis> ZADD zset1 1 "one" 2 "two"
(integer) 2
redis> ZADD zset2 1 "one" 2 "two" 3 "three"
(integer) 3
redis> ZUNIONSTORE out 2 zset1 zset2 WEIGHTS 2 3
(integer) 3
redis> ZRANGE out 0 -1 WITHSCORES
1) "one"
2) "5"          # 1×2 + 1×3
3) "three"
4) "9"          # 0×2 + 3×3(zset1 没有 three,按 0 处理)
5) "two"
6) "10"         # 2×2 + 2×3

十、Zset 命令速查表

命令 执行效果 时间复杂度
ZADD key score member [...] 添加元素 O(k × log N)
ZCARD key 获取元素个数 O(1)
ZSCORE key member 获取元素分数 O(1)
ZRANK key member 获取升序排名 O(log N)
ZREVRANK key member 获取降序排名 O(log N)
ZREM key member [...] 删除元素 O(k × log N)
ZINCRBY key inc member 元素分数 +inc O(log N)
ZRANGE key start stop [WITHSCORES] 按排名范围升序查询 O(k + log N)
ZREVRANGE key start stop [WITHSCORES] 按排名范围降序查询 O(k + log N)
ZRANGEBYSCORE key min max 按分数范围查询 O(k + log N)
ZCOUNT key min max 统计分数范围内元素数 O(log N)
ZPOPMAX / ZPOPMIN key [count] 弹出最大/最小元素 O(log N × M)
ZREMRANGEBYRANK key start stop 按排名范围删除 O(k + log N)
ZREMRANGEBYSCORE key min max 按分数范围删除 O(k + log N)
ZINTERSTORE / ZUNIONSTORE 交集/并集运算 见说明

十一、Zset 的内部编码与使用场景

11.1 内部编码

Zset 的内部编码有两种:

ziplist(压缩列表):当满足以下两个条件时使用:

  • 元素个数小于 zset-max-ziplist-entries 配置(默认 128)
  • 每个元素的长度都小于 zset-max-ziplist-value 配置(默认 64 字节)

skiplist(跳表):不满足 ziplist 条件时使用。跳表是一种类似平衡树的数据结构,支持 O(log N) 的查询、插入、删除,是有序集合的标准实现。

bash 复制代码
# 元素少且小 → ziplist
127.0.0.1:6379> zadd zsetkey 50 e1 60 e2 30 e3
(integer) 3
127.0.0.1:6379> object encoding zsetkey
"ziplist"

# 元素超过 128 个 → 升级为 skiplist
127.0.0.1:6379> zadd zsetkey 50 e1 60 e2 ... 82 e129
(integer) 129
127.0.0.1:6379> object encoding zsetkey
"skiplist"

💡 为什么用跳表而不是红黑树? 跳表在范围查询上更高效(支持顺序遍历),实现也比红黑树简单,序列化和反序列化更容易,所以 Redis 选择了跳表。这也是面试中关于 Zset 的高频考点。

11.2 经典场景:排行榜系统

Zset 最经典的应用就是排行榜。比如某网站要按用户的点赞数维护每日热榜:

新用户发布文章获得 3 个赞

bash 复制代码
ZADD user:ranking:2025-11-15 3 "james"

用户文章再获得 1 个赞(增量更新):

bash 复制代码
ZINCRBY user:ranking:2025-11-15 1 "james"
# 返回 "4"

取消用户的赞数(用户注销 / 内容删除):

bash 复制代码
ZREM user:ranking:2025-11-15 "tom"

获取点赞数最多的 Top 10 用户

bash 复制代码
# 降序,排名 0~9
ZREVRANGE user:ranking:2025-11-15 0 9 WITHSCORES

查询某个用户的当前排名

bash 复制代码
# 降序排名(点赞最多的排第 0)
ZREVRANK user:ranking:2025-11-15 "mike"
# 返回比如 (integer) 15,说明 mike 排第 16 名

# 同时获取分数
ZSCORE user:ranking:2025-11-15 "mike"
# 返回 "527"

展示用户的完整信息(结合 Hash):

bash 复制代码
# 用户基本信息存在 Hash 中
HSET user:info:mike name "Mike" age 28 city "Beijing"

# 排行榜只存用户 ID 和分数
ZADD user:ranking:2025-11-15 527 "mike"

# 查询时联合使用
ZRANK user:ranking:2025-11-15 "mike"
ZSCORE user:ranking:2025-11-15 "mike"
HGETALL user:info:mike

11.3 其他典型场景

场景一:延迟队列

利用分数作为执行时间戳(Unix 时间),可以实现简单的延迟队列:

bash 复制代码
# 添加任务,执行时间为 1700000000
ZADD delayed_tasks 1700000000 "task:send_email:1001"

# 消费者每秒检查是否有到期任务
ZRANGEBYSCORE delayed_tasks -inf <当前时间戳>
# 处理后删除
ZREM delayed_tasks "task:send_email:1001"

场景二:滑动窗口限流

bash 复制代码
# 用毫秒时间戳作为分数
ZADD user:1001:requests 1700000000123 "req:1"
ZADD user:1001:requests 1700000000456 "req:2"

# 移除 1 分钟前的请求
ZREMRANGEBYSCORE user:1001:requests -inf <当前时间-60000>

# 统计 1 分钟内的请求数
ZCARD user:1001:requests
# 如果超过阈值,则限流

场景三:带权重的标签搜索

bash 复制代码
# 文章的标签和相关度
ZADD article:1001:tags 0.9 "redis" 0.7 "数据库" 0.5 "教程"

# 查询相关度 ≥ 0.7 的标签
ZRANGEBYSCORE article:1001:tags 0.7 +inf

十二、总结

现在你已经掌握了:

Set 的核心能力:去重 + 集合运算(交集、并集、差集)

Zset 的核心能力:有序 + 按分数排序 + 排名查询

Set 的全部命令:SADD/SREM/SCARD/SISMEMBER/SMEMBERS/SPOP/SMOVE + 三种集合运算

Zset 的全部命令:ZADD(含 NX/XX/CH/INCR)、ZRANGE/ZREVRANGE、ZRANK/ZREVRANK、ZSCORE、ZINCRBY、ZPOPMAX/ZPOPMIN、ZREM 系列、集合间运算

内部编码:Set 用 intset 或 hashtable;Zset 用 ziplist 或 skiplist

典型场景:标签系统、共同好友、UV 统计、排行榜、延迟队列、滑动窗口限流

12.1 五种数据类型横向对比

数据类型 元素特点 核心场景 性能特点
String 单一值 缓存、计数器、Session O(1) 读写
Hash 字段-值映射 对象信息存储 字段级 O(1)
List 有序、可重复 消息队列、时间线 两端 O(1),中间 O(N)
Set 无序、唯一 标签、共同好友、去重 O(1) 查询,支持集合运算
Zset 有序(按分数)、唯一 排行榜、延迟队列 O(log N) 查询和排名

12.2 使用注意事项

注意事项 说明
大 Set/Zset 避免一次性返回所有元素 SMEMBERS、ZRANGE 0 -1 在大集合上会阻塞
集合运算结果存储要谨慎 SINTERSTORE 等操作可能产生很大的 key
Zset 元素不重复但分数可重复 分数相同时按元素字典序排序
跳表 vs 红黑树面试重点 跳表实现简单、范围查询高效
排行榜按天/小时分 key 是常见做法 比如 ranking:2025-11-15

下一篇预告:渐进式遍历 SCDatabase management commands, and a comprehensive review of the core knowledge points from Chapter 2, serving as the concluding chapter for learning the five data types.习的收官篇。

相关推荐
無限進步D1 小时前
MySQL 创建和管理表
数据库·mysql
六月雨滴1 小时前
归档模式配置与切换
数据库·oracle·dba
卡次卡次11 小时前
vibecoding起步注意点:插件、Skills、MCP、Hooks
服务器·数据库·python·oracle
Elastic 中国社区官方博客2 小时前
每次操作一个 API 调用:Elastic Cloud Hosted 如何让大规模部署管理变得可行
大数据·运维·数据库·elasticsearch·搜索引擎·serverless
清溪5492 小时前
pgAdmin4 <= 9.1_RCE(CVE-2025-2945)复现
数据库·后端
清溪5492 小时前
pgAdmin4后台Restore RCE(CVE-2025-13780)复现
数据库·后端
煎饼皮皮侠2 小时前
【设计】设计一个web版的数据库管理平台后端(之三) -- 多数据库通用分页
数据库·web数据库·查询平台
Rick19932 小时前
mysql联合索引经典实例
java·数据库·mysql
anew___2 小时前
《数据库原理》精要解读(七)—— 数据库设计:从蓝图到现实的系统工程
数据库·oracle