文章目录
-
- [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 也有 BZPOPMAX 和 BZPOPMIN 阻塞版本,在 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.习的收官篇。