【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.习的收官篇。

相关推荐
倔强的石头_3 小时前
KingbaseES 新版MySQL 兼容版体验:旧版迁移 + 功能实测
数据库
小bo波17 小时前
从"任意文件复制"深挖Java I/O:字符流与字节流的本质抉择
java·nio·io流·后端开发·文件复制
用户3169353811833 天前
Java连接Redis
redis
倔强的石头_3 天前
《Kingbase护城河》——数据库存储空间全景探测与精细化瘦身实战
数据库
冬奇Lab4 天前
每日一个开源项目(第134篇):Zvec - 阿里开源的嵌入式向量数据库,向量搜索界的 SQLite
数据库·人工智能·llm
ClouGence4 天前
Oracle CDC 架构优化:从主库直连到 DataGuard 备库同步
数据库·后端·oracle
无响应de神4 天前
三、用户与权限管理
数据库·mysql
小小工匠5 天前
Redis - 事务机制:能实现 ACID 属性吗
数据结构·redis·性能优化·并发·持久化
麦聪聊数据5 天前
数据服务化时代:企业数据能力输出的核心路径
数据库
shushangyun_5 天前
2026年快消品B2B系统推荐:支持终端门店订货、促销政策自动化的工具?
java·运维·网络·数据库·人工智能·spring·自动化