Redis 常见数据类型-Zset 类型
类型简介
有序集合保留了集合不能有重复成员的特点,但与集合不同的是,有序集合中的每个元素都有⼀个唯⼀的浮点类型的分数(score)与之关联,这使得有序集合中的元素是可以维护有序性的,但这个有序不是用下标作为排序依据而是用这个分数。
下面是列表、集合、有序集合三者的异同点
| 数据结构 | 是否允许重复元素 | 是否有序 | 有序依据 | 应用场景 |
|---|---|---|---|---|
| 列表 | 是 | 是 | 索引下标 | 时间轴、消息队列等 |
| 集合 | 否 | 否 | - | 标签、社交等 |
| 有序集合 | 否 | 是 | 分数 | 排行榜系统、社交等 |
相关指令
ZADD 指令深度解析
将一个或多个成员元素及其分数值加入到有序集(sorted set)中。如果成员已经存在于有序集合中,则会更新其分数值,并根据新的分数值重新排序。
命令格式:
ZADD key [NX|XX] [GT|LT] [CH] [INCR] score member [score member ...]
选项说明
- NX:仅添加新元素,不更新已存在的元素
- XX:仅更新已存在的元素,不添加新元素
- GT:只有当新分数大于当前分数时才更新(Redis 7.0+)
- LT:只有当新分数小于当前分数时才更新(Redis 7.0+)
- CH:返回被修改的元素总数(包括新增和更新的)
- INCR:将指定成员的分数增加给定值(相当于ZINCRBY)
时间复杂度:
- O(log(N)):添加单个元素(N是有序集的基数)
- O(M*log(N)):添加M个元素
返回值:
- 不使用CH选项时:新增的元素数量
- 使用CH选项时:被修改的元素总数(包括新增和更新的)
- 使用INCR选项时:成员的新分数值(字符串形式)
使用示例
- 基本用法:
# 添加三个成员
127.0.0.1:6379> zadd myzset 1 "one" 2 "two" 3 "three"
(integer) 3
# 查看有序集
127.0.0.1:6379> zrange myzset 0 -1 withscores
1) "one"
2) "1"
3) "two"
4) "2"
5) "three"
6) "3"
- 使用NX选项(仅添加新元素):
# 尝试更新已存在的元素"one"(失败)
127.0.0.1:6379> zadd myzset NX 4 "one" 5 "four"
(integer) 1 # 只新增了"four"
# 查看结果
127.0.0.1:6379> zrange myzset 0 -1 withscores
1) "one"
2) "1"
3) "two"
4) "2"
5) "three"
6) "3"
7) "four"
8) "5"
- 使用XX选项(仅更新已存在元素):
# 尝试更新已存在的"one"和新增"five"
127.0.0.1:6379> zadd myzset XX 10 "one" 5 "five"
(integer) 1 # 只更新了"one"
# 查看结果
127.0.0.1:6379> zrange myzset 0 -1 withscores
1) "two"
2) "2"
3) "three"
4) "3"
5) "four"
6) "5"
7) "one"
8) "10" # "one"的分数被更新
- 使用CH选项(返回修改总数):
# 更新一个已存在元素,添加一个新元素
127.0.0.1:6379> zadd myzset CH 15 "one" 20 "five"
(integer) 2 # 一个更新,一个新增
# 查看结果
127.0.0.1:6379> zrange myzset 0 -1 withscores
1) "two"
2) "2"
3) "three"
4) "3"
5) "four"
6) "5"
7) "one"
8) "15"
9) "five"
10) "20"
- 使用INCR选项(分数递增):
# 将"one"的分数增加5
127.0.0.1:6379> zadd myzset INCR 5 "one"
"20" # 返回新分数
# 查看结果
127.0.0.1:6379> zrange myzset 0 -1 withscores
1) "two"
2) "2"
3) "three"
4) "3"
5) "four"
6) "5"
7) "five"
8) "20"
9) "one"
10) "20"
- 使用GT/LT选项:
# 只有当新分数大于当前分数时才更新
127.0.0.1:6379> zadd myzset GT 25 "one" # 当前分数20 < 25,会更新
(integer) 0
# 尝试用更小的分数更新
127.0.0.1:6379> zadd myzset GT 10 "one" # 当前分数25 > 10,不会更新
(integer) 0
# 使用LT选项(只有当新分数小于当前分数时才更新)
127.0.0.1:6379> zadd myzset LT 8 "two" # 当前分数2 < 8,不会更新
(integer) 0
127.0.0.1:6379> zadd myzset LT 1 "two" # 当前分数2 > 1,会更新
(integer) 0
注意事项
- 分数值是64位双精度浮点数,可以表示的范围为-(2^53)到+(2^53)
- 相同分数的成员按字典序排序
- 从Redis 3.0.2开始,ZADD支持多参数语法
- GT/LT选项需要Redis 7.0及以上版本
- INCR选项不能与其他选项(NX/XX/GT/LT)一起使用
zcard
该指令获取—个 zset 的基数(cardinality),即 zset 中的元素个数
命令格式:
ZCARD key
时间复杂度:O(1)
返回值:zset 内的元素个数
使用示例:
127.0.0.1:6379> zadd k1 10 a 20 b
(integer) 2
# 查询 k1 中的元素个数
127.0.0.1:6379> zcard k1
(integer) 2
zcount
命令格式:
ZCOUNT key min max
返回有序集合中分数值在min和max之间的成员数量(默认包含边界值)
时间复杂度:O(log(N)),其中N是有序集合的基数
返回值:指定分数范围内的成员数量
开区间功能
- 使用
(前缀表示排除边界值(开区间) (min表示大于min(不包含min)(max表示小于max(不包含max)- 可以组合使用,如
(min (max表示大于min且小于max
使用示例
- 基本用法(闭区间):
# 创建有序集合
127.0.0.1:6379> ZADD scores 100 "Alice" 200 "Bob" 300 "Charlie" 400 "David"
(integer) 4
# 统计200-400分(包含边界)的成员
127.0.0.1:6379> ZCOUNT scores 200 400
(integer) 3 # 包含Bob(200), Charlie(300), David(400)
- 开区间用法:
# 统计大于200且小于400的成员(排除边界)
127.0.0.1:6379> ZCOUNT scores (200 (400
(integer) 1 # 仅Charlie(300)
# 仅排除下限
127.0.0.1:6379> ZCOUNT scores (200 400
(integer) 2 # Charlie(300), David(400)
# 仅排除上限
127.0.0.1:6379> ZCOUNT scores 200 (400
(integer) 2 # Bob(200), Charlie(300)
- 无限区间:
# 统计所有小于300分的成员
127.0.0.1:6379> ZCOUNT scores -inf 300
(integer) 3 # Alice(100), Bob(200), Charlie(300)
# 统计所有大于200分的成员(不包含200)
127.0.0.1:6379> ZCOUNT scores (200 +inf
(integer) 2 # Charlie(300), David(400)
注意事项
- 默认范围是闭区间(包含边界值)
-inf和+inf表示无限小和无限大- 键不存在时返回0
- 分数值使用双精度浮点数存储
zrange
命令格式:
ZRANGE key start stop [BYSCORE | BYLEX] [REV] [LIMIT offset count] [WITHSCORES]
返回有序集合中指定区间内的成员,支持按索引、分数或字典序查询
核心功能
- 索引范围查询(默认):
start和stop为从0开始的索引(包含边界)- 支持负数索引(-1表示最后一个元素)
# 获取前3个元素
127.0.0.1:6379> ZRANGE scores 0 2
1) "Alice"
2) "Bob"
3) "Charlie"
- 分数范围查询(BYSCORE):
- 使用
(表示开区间 - 支持
-inf和+inf表示无限
- 使用
# 获取分数在(100,300]之间的元素
127.0.0.1:6379> ZRANGE scores (100 300 BYSCORE
1) "Bob"
2) "Charlie"
- 字典序查询(BYLEX):
- 要求所有元素分数相同
- 使用
[或(指定闭/开区间
127.0.0.1:6379> ZADD k1 1 a 1 b 1 c 1 d 1 e 1 f
(integer) 6
# 获取字典序在[a,e)之间的元素
127.0.0.1:6379> ZRANGE k1 [a (e BYLEX
1) "a"
2) "b"
3) "c"
4) "d"
关键选项:
REV:反向排序(从高到低)LIMIT:分页查询(类似SQL的LIMIT)WITHSCORES:返回成员及其分数
时间复杂度:
- O(log(N)+M),N是有序集合基数,M是返回元素数
使用示例:
- 基本范围查询:
127.0.0.1:6379> ZADD scores 100 "Alice" 200 "Bob" 300 "Charlie"
(integer) 3
127.0.0.1:6379> ZRANGE scores 0 -1
1) "Alice"
2) "Bob"
3) "Charlie"
- 带分数的反向查询:
127.0.0.1:6379> ZRANGE scores 0 -1 REV WITHSCORES
1) "Charlie"
2) "300"
3) "Bob"
4) "200"
5) "Alice"
6) "100"
- 开区间查询:
127.0.0.1:6379> ZRANGE scores (100 (300 BYSCORE
1) "Bob"
注意事项
- Redis 6.2+版本开始替代了ZREVRANGE等命令
- 使用BYLEX时所有成员分数必须相同
- 大范围查询可能影响性能(时间复杂度O(log(N)+M))
ZREVRANGE
命令格式:
ZREVRANGE key start stop [WITHSCORES]
返回有序集合中指定索引范围内的成员,按分数从高到低排序(与ZRANGE排序相反)
核心特性
- 反向排序:默认按分数降序排列(最高分在前)
- 索引范围:
start和stop为基于0的索引- 支持负数索引(-1表示最后一个元素)
- 分数相同时按字典序降序排列
时间复杂度:O(log(N)+M),N是有序集合基数,M是返回元素数
返回值:指定范围内的成员列表(可选包含分数)
使用示例
- 基本用法:
127.0.0.1:6379> ZADD scores 100 "Alice" 200 "Bob" 300 "Charlie"
(integer) 3
127.0.0.1:6379> ZREVRANGE scores 0 -1 # 获取全部元素(降序)
1) "Charlie"
2) "Bob"
3) "Alice"
- 带分数返回:
127.0.0.1:6379> ZREVRANGE scores 0 1 WITHSCORES # 获取前两名
1) "Charlie"
2) "300"
3) "Bob"
4) "200"
注意事项
- 已弃用:Redis 6.2+建议改用
ZRANGE key start stop REV - 索引越界时自动截断
- 键不存在时返回空列表
典型应用场景
- 排行榜(显示前N名)
127.0.0.1:6379> ZREVRANGE leaderboard 0 9 WITHSCORES
- 优先级队列(处理高优先级任务)
- 时间线(最新内容优先)
ZPOPMAX
命令格式:
ZPOPMAX key [count]
移除并返回有序集合中分数最高的成员(默认返回1个)
核心特性
- 原子性操作:同时完成查询和删除操作
- 排序规则:按分数降序返回(最高分在前)
- 默认行为:未指定count时只返回1个成员
时间复杂度:O(log(N)*M),N是有序集合基数,M是返回元素数
返回值:被移除的成员及其分数(数组形式)
使用示例
- 基本用法:
127.0.0.1:6379> ZADD scores 100 "Alice" 200 "Bob" 300 "Charlie"
(integer) 3
127.0.0.1:6379> ZPOPMAX scores # 移除并返回最高分成员
1) "Charlie"
2) "300"
- 批量移除:
127.0.0.1:6379> ZPOPMAX scores 2 # 移除前两名
1) "Bob"
2) "200"
3) "Alice"
4) "100"
- 异常情况:
# 键不存在时
127.0.0.1:6379> ZPOPMAX not_exists
(empty array)
# 错误数据类型时
127.0.0.1:6379> SET wrong_type "value"
OK
127.0.0.1:6379> ZPOPMAX wrong_type
(error) WRONGTYPE Operation against a key holding the wrong kind of value
注意事项
- count限制:
- 必须为非负整数
- 超过集合大小时返回全部元素
- 数据修改:操作后成员会从集合中永久移除
- 替代命令:Redis 6.2+推荐使用
ZRANGE+ZREM组合实现类似功能
典型应用场景
- 任务队列(处理高优先级任务)
127.0.0.1:6379> ZPOPMAX pending_tasks
- 实时排行榜(获取当日冠军)
127.0.0.1:6379> ZPOPMAX daily_ranking WITHSCORES
- 流量控制(处理最高权重请求)
ZPOPMIN
用法和 ZPOPMAX 类似,这里不再赘述
ZRANK、ZREVRANK
1. 基本功能
- ZRANK:返回成员在有序集合中的升序排名(分数从低到高),排名从0开始计算,最低分成员排名为0
- ZREVRANK:返回成员在有序集合中的降序排名(分数从高到低),排名从0开始计算,最高分成员排名为0
2. 命令格式
ZRANK key member [WITHSCORE]
ZREVRANK key member [WITHSCORE]
3. 共同特性
- 时间复杂度:O(log(N))
- 返回值类型:
- 成员存在时返回整数排名
- 成员不存在或key不存在时返回nil
- Redis 7.2+支持WITHSCORE选项可同时返回分数(返回数组格式[排名,分数])
4. 核心区别
| 特性 | ZRANK | ZREVRANK |
|---|---|---|
| 排序方向 | 分数升序(低→高) | 分数降序(高→低) |
| 最高分排名 | 最大排名值 | 0 |
| 最低分排名 | 0 | 最大排名值 |
5. 使用示例
# 创建测试数据
127.0.0.1:6379> ZADD leaderboard 100 "Alice" 200 "Bob" 300 "Charlie"
# ZRANK示例(升序排名)
127.0.0.1:6379> ZRANK leaderboard "Alice" # 最低分→排名0
(integer) 0
127.0.0.1:6379> ZRANK leaderboard "Charlie" # 最高分→排名2
(integer) 2
# ZREVRANK示例(降序排名)
127.0.0.1:6379> ZREVRANK leaderboard "Alice" # 最低分→排名2
(integer) 2
127.0.0.1:6379> ZREVRANK leaderboard "Charlie" # 最高分→排名0
(integer) 0
# WITHSCORE选项(Redis 7.2+)
127.0.0.1:6379> ZREVRANK leaderboard "Bob" WITHSCORE
1) (integer) 1
2) "200"
6. 应用场景
ZRANK适用场景:
- 需要知道成员在整体中的相对位置(如成绩百分位)
- 按成本/价格从低到高排序的场景
ZREVRANK适用场景:
- 排行榜系统(显示TOP N用户)
- 按热度/评分从高到低排序的场景
7. 注意事项
- 两个命令都采用0-based排名体系
- 分数相同的成员按字典序决定排名
- 大集合中使用时要注意O(log(N))的时间复杂度
ZSCORE 指令详解
1. 基本功能
返回有序集合中指定成员的分数值。如果成员不存在或key不存在,返回nil
2. 命令格式
ZSCORE key member
3. 核心特性
- 时间复杂度:O(1)
- 返回值类型:
- 字符串形式返回双精度浮点数分数值
- 成员不存在时返回nil
- Redis 7.2+支持WITHSCORE选项可同时返回排名和分数
4. 使用示例
# 创建测试数据
127.0.0.1:6379> ZADD scores 100 "Alice" 200 "Bob" 300 "Charlie"
# 查询成员分数
127.0.0.1:6379> ZSCORE scores "Alice"
"100"
# 不存在的成员
127.0.0.1:6379> ZSCORE scores "David"
(nil)
# 错误数据类型
127.0.0.1:6379> SET wrong_type "value"
OK
127.0.0.1:6379> ZSCORE wrong_type "test"
(error) WRONGTYPE Operation against a key holding the wrong kind of value
5. 应用场景
- 排行榜系统(查看用户得分)
- 优先级任务检查(确认任务优先级)
- 游戏计分系统(查询玩家分数)
ZREM 指令详解
1. 基本功能
从有序集合中移除一个或多个成员,不存在的成员会被忽略
2. 命令格式
ZREM key member [member ...]
3. 核心特性
- 时间复杂度:O(M*log(N)),M为移除成员数,N为集合基数
- 返回值:实际移除的成员数量(不包含不存在的成员)
- 支持批量操作(Redis 2.4+)
4. 使用示例
# 创建测试数据
127.0.0.1:6379> ZADD scores 100 "Alice" 200 "Bob" 300 "Charlie"
# 移除单个成员
127.0.0.1:6379> ZREM scores "Alice"
(integer) 1
# 批量移除
127.0.0.1:6379> ZREM scores "Bob" "David" # David不存在
(integer) 1
# 错误数据类型
127.0.0.1:6379> SET wrong_type "value"
OK
127.0.0.1:6379> ZREM wrong_type "test"
(error) WRONGTYPE Operation against a key holding the wrong kind of value
5. 应用场景
- 排行榜更新(移除过期用户)
- 任务队列管理(完成的任务移出队列)
- 实时数据清理(移除过期数据)
6. 注意事项
- 与ZREMRANGEBYRANK不同,ZREM是按成员值移除而非排名
- 大集合批量移除时建议使用管道(pipeline)提高效率
ZREMRANGEBYRANK
1. 基本功能
移除有序集合中指定排名范围内的所有成员,按升序排名(分数从低到高)计算范围。
2. 命令格式
ZREMRANGEBYRANK key start stop
3. 核心特性
- 时间复杂度:
O(log(N) + M),其中N是集合大小,M是被移除的成员数量。 - 排名规则:
- 排名从
0开始(最低分成员的排名为0)。 - 支持负数索引(
-1表示最后一个成员,-2表示倒数第二个,依此类推)。
- 排名从
- 返回值:被移除的成员数量(若
key不存在或范围无效,返回0)。
4. 使用示例
# 创建测试数据
127.0.0.1:6379> ZADD scores 100 "Alice" 200 "Bob" 300 "Charlie" 400 "David" 500 "Eve"
# 移除排名 0-2(最低分的3个成员)
127.0.0.1:6379> ZREMRANGEBYRANK scores 0 2
(integer) 3 # 移除了 Alice(100), Bob(200), Charlie(300)
# 检查剩余成员
127.0.0.1:6379> ZRANGE scores 0 -1 WITHSCORES
1) "David"
2) "400"
3) "Eve"
4) "500"
# 使用负数索引(移除倒数2个成员)
127.0.0.1:6379> ZREMRANGEBYRANK scores -2 -1
(integer) 2 # 移除了 David(400), Eve(500)
# 检查是否为空
127.0.0.1:6379> ZRANGE scores 0 -1
(empty array)
5. 应用场景
- 排行榜清理(定期移除排名最低的N个用户)
- 数据分页删除(批量移除指定范围的记录)
- 缓存淘汰策略(移除低优先级数据)
6. 注意事项
- 与
ZREMRANGEBYSCORE的区别:ZREMRANGEBYRANK按排名移除(基于排序后的位置)。ZREMRANGEBYSCORE按分数范围移除(如ZREMRANGEBYSCORE scores 100 300)。
- 索引越界:
- 若
start超过最大排名,不会移除任何成员。 - 若
stop超过最大排名,会截断到最后一个成员。
- 若
- 键不存在时:返回
0,不会报错。
7. 替代方案(Redis 6.2+)
# 使用 ZRANGE + ZREM 组合(适用于复杂条件删除)
127.0.0.1:6379> ZRANGE scores 0 2 BYSCORE | xargs redis-cli ZREM scores
总结:ZREMRANGEBYRANK 适用于按排名批量删除,适合排行榜维护、数据清理等场景,使用时需注意排名方向和索引范围。
ZREMRANGEBYSCORE
1. 基本功能
移除有序集合中分数在指定范围内的所有成员(包含 min 和 max 边界值)。
2. 命令格式
ZREMRANGEBYSCORE key min max
3. 核心特性
- 时间复杂度:
O(log(N) + M),N是集合大小,M是被移除的成员数量。 - 分数范围:
- 默认包含边界值(如
ZREMRANGEBYSCORE key 100 200移除分数 100 ≤ score ≤ 200 的成员)。 - 支持开区间(使用
(符号,如(100表示 >100,200表示 ≤200)。 - 支持特殊值
-inf(负无穷)和+inf(正无穷)。
- 默认包含边界值(如
- 返回值:被移除的成员数量(若
key不存在或范围无效,返回0)。
4. 使用示例
# 创建测试数据
127.0.0.1:6379> ZADD scores 100 "Alice" 200 "Bob" 300 "Charlie" 400 "David" 500 "Eve"
# 移除分数 200 ≤ score ≤ 400 的成员
127.0.0.1:6379> ZREMRANGEBYSCORE scores 200 400
(integer) 3 # 移除了 Bob(200), Charlie(300), David(400)
# 检查剩余成员
127.0.0.1:6379> ZRANGE scores 0 -1 WITHSCORES
1) "Alice"
2) "100"
3) "Eve"
4) "500"
# 使用开区间(移除分数 >100 且 ≤300 的成员)
127.0.0.1:6379> ZREMRANGEBYSCORE scores (100 300
(integer) 1 # 仅移除了 Bob(200)
# 使用 -inf 和 +inf(移除所有成员)
127.0.0.1:6379> ZREMRANGEBYSCORE scores -inf +inf
(integer) 2 # 移除了剩余 Alice(100), Eve(500)
5. 应用场景
- 数据清理(如删除过期低分数据)。
- 动态排行榜维护(定期移除低活跃用户)。
- 范围任务处理(批量移除特定优先级的任务)。
6. 注意事项
- 与
ZREMRANGEBYRANK区别:ZREMRANGEBYSCORE按分数范围移除。ZREMRANGEBYRANK按排名范围移除(如ZREMRANGEBYRANK scores 0 2)。
- 键不存在时:返回
0,无错误。 - 错误类型:若键存在但非有序集合,返回
WRONGTYPE错误。
7. 替代方案(复杂条件删除)
# 使用 ZRANGEBYSCORE + ZREM 组合(适用于非连续范围)
127.0.0.1:6379> ZRANGEBYSCORE scores 100 300 | xargs redis-cli ZREM scores
总结:ZREMRANGEBYSCORE 适用于按分数批量删除,支持灵活的范围设定,适合数据过滤和动态集合维护场景。
ZINCRBY 指令详解
1. 基本功能
对有序集合中的指定成员进行分数增减操作。若成员不存在,则自动创建并设置初始分数为 0.0 后增减;若键不存在,则创建新有序集合并添加该成员。
2. 命令格式
ZINCRBY key increment member
3. 核心特性
- 时间复杂度:
O(log(N)),N为有序集合的成员数量。 - 分数规则:
increment可为正数(增加分数)或负数(减少分数)。- 支持双精度浮点数(如
3.14或-1.5)。
- 键和成员处理:
- 若
key不存在,自动创建新有序集合。 - 若
member不存在,视为初始分数0.0并增减。 - 若
key存在但非有序集合,返回错误。
- 若
4. 使用示例
# 初始数据
127.0.0.1:6379> ZADD scores 100 "Alice" 200 "Bob"
# 增加 Alice 的分数 (+50)
127.0.0.1:6379> ZINCRBY scores 50 "Alice"
"150" # 新分数
# 减少 Bob 的分数 (-30)
127.0.0.1:6379> ZINCRBY scores -30 "Bob"
"170" # 新分数
# 不存在的成员(自动创建,初始分数 0 + 10)
127.0.0.1:6379> ZINCRBY scores 10 "Charlie"
"10"
# 错误示例(键存在但非有序集合)
127.0.0.1:6379> SET wrong_type "value"
OK
127.0.0.1:6379> ZINCRBY wrong_type 5 "test"
(error) WRONGTYPE Operation against a key holding the wrong kind of value
5. 应用场景
- 实时排行榜更新(如游戏积分变动)。
- 优先级任务调整(动态修改任务优先级)。
- 计数器系统(支持增减的浮点计数器)。
6. 注意事项
- 返回值:始终返回成员增减后的新分数(字符串形式的浮点数)。
- 精度问题:浮点数运算可能存在微小误差,需业务层处理。
- 性能:高频操作时建议使用管道(pipeline)优化。
7. 对比其他命令
- 与
ZADD区别:ZINCRBY专用于增量修改,而ZADD直接覆盖分数。 - 与
HINCRBY区别:HINCRBY用于哈希结构且仅支持整数增减。
总结:ZINCRBY 是动态维护有序集合分数的核心命令,适用于需频繁增减分数的场景,如排行榜、优先级队列等。
等等,我知道你想问什么,ZADD 好像已经有了 ZINCRBY 的功能呀,那为什么还要保留这个命令呢?感兴趣可以展开看看:
- 语义清晰性
ZINCRBY明确表达了”增量”操作的意图,代码可读性更强ZADD ... INCR的语法相对隐晦,不够直观
- 使用便捷性
ZINCRBY语法更简洁:ZINCRBY key increment memberZADD实现相同功能需要:ZADD key INCR increment member
- 历史原因
ZINCRBY是早期版本就存在的命令(1.2.0引入)ZADD的INCR选项是后来添加的(2.6.9版本)
- 性能考虑
- 虽然差异很小,但专用命令(
ZINCRBY)可能比通用命令(ZADD带选项)有轻微的性能优势 ZINCRBY的实现路径更直接,不需要解析额外选项
- 错误预防
ZINCRBY明确限制为单成员操作,避免误用ZADD支持多成员操作,配合INCR时容易误传多个成员导致错误
实际建议:
- 当明确要做增量操作时,优先使用
ZINCRBY,代码更清晰 - 需要批量操作或复杂条件时,使用
ZADD的选项功能 - 维护旧代码时,保持原有命令使用的一致性
Redis 的设计哲学之一是保持命令的专一性和明确性,即使某些功能有重叠,也会保留更专门的命令来满足不同场景的需求。
集合间操作指令
ZINTERSTORE 指令深度解析
1. 核心功能
计算多个有序集合的交集,并将结果存储到指定的目标键中。仅保留同时存在于所有输入集合的成员,其分数通过可配置的聚合规则计算。
2. 完整命令格式
ZINTERSTORE destination numkeys key [key ...]
[WEIGHTS weight [weight ...]]
[AGGREGATE SUM|MIN|MAX]
3. 参数详解
3.1 必选参数
- destination:存储结果的键名。若已存在会被覆盖(无论原类型)。
- numkeys:指定参与计算的有序集合数量(必须与实际输入的key数量一致)。
- key [key …]:参与交集的集合键名(至少1个)。
3.2 可选参数
WEIGHTS
- 作用:为每个输入集合的分数设置权重乘法因子。
- 规则:
- 权重数量必须与
numkeys一致,未指定时默认为1。 - 支持浮点数(如
2.5),可正可负。
- 权重数量必须与
- 示例:
# 集合1分数×10,集合2分数×0.5
ZINTERSTORE result 2 set1 set2 WEIGHTS 10 0.5
AGGREGATE
- 作用:控制交集成员的分数计算方式。
- 选项:
- SUM(默认):各集合分数×权重后相加。
- MIN:取各集合分数×权重后的最小值。
- MAX:取各集合分数×权重后的最大值。
- 示例:
# 取分数的最小值
ZINTERSTORE result 2 set1 set2 AGGREGATE MIN
4. 执行逻辑
- 交集判定:仅保留所有输入集合中均存在的成员。
- 分数计算:
- 对每个成员,按
WEIGHTS对原始分数加权。 - 按
AGGREGATE规则聚合加权后的分数。
- 对每个成员,按
- 存储结果:覆盖
destination键,类型转为有序集合。
5. 时间复杂度
- O(NK) + O(Mlog(M)):
N:最小输入集合的成员数。K:输入集合数量。M:结果集的成员数。
下面是对于这个时间复杂度的说明,感兴趣可以展开看看:
1. 时间复杂度组成O(N*K) + O(M*log(M)) 由两部分构成:
O(N*K):计算交集的核心成本N:最小输入集合的成员数量(需遍历该集合的所有成员)K:参与交集的集合数量(每个成员需检查是否存在于其他K-1个集合)- 例如:3个集合分别有100、200、300个成员,则
N=100,K=3。
O(M*log(M)):存储结果集的成本M:结果集的成员数量(需对M个成员按分数排序后存储)- 使用跳表(Skip List)结构存储,插入单个成员的时间为
O(log(M))
2. 为什么是这种复杂度?
2.1 交集计算逻辑
- 选择基准集合:取成员数最少的集合(
N最小)作为遍历基准 - 成员存在性检查:对基准集合的每个成员,检查是否在其他
K-1个集合中存在:- 每个检查操作时间复杂度为
O(log(S))(S为被检查集合的大小),但因有序集合使用哈希表+跳表,实际平均为O(1) - 总检查成本为
O(N*K)(简化模型)
- 每个检查操作时间复杂度为
2.2 结果集存储
- 交集成员需按分数重新排序后存入目标键:
- 插入
M个成员到跳表的总成本为O(M*log(M))
- 插入
3. 影响因素
- 数据分布:
- 若输入集合的成员高度重合(
M接近N),O(M*log(M))会显著增加 - 若成员几乎无交集(
M≈0),则主要成本为O(N*K)
- 若输入集合的成员高度重合(
- 权重与聚合操作:
WEIGHTS和AGGREGATE的计算成本已包含在O(N*K)中(仅额外算术操作)
4. 实测验证
- 测试场景(参考实际案例):
- 交集两个集合(在线用户与付费用户),规模从1k到1M成员。
- 结果:当
N=1k时,单次操作约0.5ms;N=1M时约200ms,符合O(N*K)趋势
- 关键结论:
- 小数据集(
N<10k)实时计算可行;大数据集需权衡性能
- 小数据集(
5. 对比其他命令
- 与
ZUNIONSTORE区别:- 并集需遍历所有集合的成员,时间复杂度为
O(N)(N为所有集合成员总数)
- 并集需遍历所有集合的成员,时间复杂度为
- 与
ZINTER区别:ZINTER仅返回结果,不存储,省去O(M*log(M))成本
总结:ZINTERSTORE 的时间复杂度由交集计算和结果存储两部分决定,实际性能取决于最小集合大小和结果集规模。业务中建议对大数据集预分片或离线计算[5]。
6. 使用示例
6.1 基础交集
# 创建测试数据
ZADD set1 1 "A" 2 "B"
ZADD set2 2 "B" 3 "C"
# 计算交集(默认SUM聚合)
ZINTERSTORE result 2 set1 set2
# 结果: "B"的分数=2(set1)+2(set2)=4
6.2 加权与聚合
# 加权计算(set1分数×10,set2分数×0.1)
ZINTERSTORE result 2 set1 set2 WEIGHTS 10 0.1
# "B"的分数=2×10 + 2×0.1=20.2
# MIN聚合
ZINTERSTORE result 2 set1 set2 AGGREGATE MIN
# "B"的分数=min(2, 2)=2
6.3 错误处理
# 键类型错误(非有序集合)
SET wrong_type "value"
ZINTERSTORE result 1 wrong_type
# 返回: (error) WRONGTYPE
7. 应用场景
- 共同好友统计:计算多个用户的共同关注列表。
- 商品筛选:找出同时满足多个条件的商品(如价格区间+评分区间)。
- 优先级任务合并:整合多来源的高优先级任务。
8. 注意事项
- 覆盖风险:目标键会被强制覆盖,包括类型转换。
- 权重顺序:
WEIGHTS顺序需与输入集合顺序严格对应。 - 空集合处理:若输入集合无交集,结果集为空(返回
0)。 - 性能影响:大集合交集的CPU和内存消耗较高,建议在低峰期执行。
ZUNIONSTORE
该指令用法和 ZINERSTORE 相同,故不再赘述,下面是使用示例:
1. 基础并集计算
# 创建两个有序集合
ZADD set1 1 "A" 2 "B"
ZADD set2 2 "B" 3 "C"
# 计算并集(默认SUM聚合)
ZUNIONSTORE result 2 set1 set2
# 结果: "A"(1), "B"(4=2+2), "C"(3)
2. 使用权重(WEIGHTS)
# 集合1分数×10,集合2分数×0.5
ZUNIONSTORE result 2 set1 set2 WEIGHTS 10 0.5
# 结果: "A"(10=1×10), "B"(21=2×10 + 2×0.5), "C"(1.5=3×0.5)
3. 聚合规则(AGGREGATE)
# MIN聚合(取最小分数)
ZUNIONSTORE result 2 set1 set2 AGGREGATE MIN
# 结果: "A"(1), "B"(2), "C"(3)
# MAX聚合(取最大分数)
ZUNIONSTORE result 2 set1 set2 AGGREGATE MAX
# 结果: "A"(1), "B"(2), "C"(3)
4. 多集合并集
# 三个集合的并集
ZADD set3 4 "B" 5 "D"
ZUNIONSTORE result 3 set1 set2 set3
# 结果: "A"(1), "B"(8=2+2+4), "C"(3), "D"(5)
5. 异常处理
# 键不存在时
ZUNIONSTORE result 1 non_existing_key
# 结果: (integer) 0
# 键类型错误(非有序集合)
SET wrong_type "value"
ZUNIONSTORE result 1 wrong_type
# 结果: (error) WRONGTYPE
Zset 相关指令汇总表
| 命令 | 时间复杂度 | 说明 |
|---|---|---|
| 基本操作 | ||
ZADD key score member [score member ...] |
O(k * log(n)) | k是添加/更新的成员个数,n是集合当前元素总数 |
ZCARD key |
O(1) | 获取有序集合的基数(成员数量) |
ZSCORE key member |
O(1) | 获取指定成员的分数值 |
| 排名查询 | ||
ZRANK key member |
O(log(n)) | 获取成员升序排名(从0开始计) |
ZREVRANK key member |
O(log(n)) | 获取成员降序排名 |
| 增删操作 | ||
ZREM key member [member ...] |
O(k * log(n)) | k是删除的成员个数 |
ZINCRBY key increment member |
O(log(n)) | 对成员分数进行增量操作(原子性) |
| 范围查询 | ||
ZRANGE key start end [WITHSCORES] |
O(log(n) + k) | k是返回的成员个数(原图公式顺序有误,已修正) |
ZREVRANGE key start end [WITHSCORES] |
O(log(n) + k) | 降序版的ZRANGE |
ZRANGEBYSCORE key min max [WITHSCORES] |
O(log(n) + k) | 按分数区间升序查询 |
ZREVRANGEBYSCORE key max min [WITHSCORES] |
O(log(n) + k) | 按分数区间降序查询 |
| 统计操作 | ||
ZCOUNT key min max |
O(log(n)) | 统计分数区间内的成员数量 |
| 批量删除 | ||
ZREMRANGEBYRANK key start end |
O(log(n) + k) | 按排名区间删除成员 |
ZREMRANGEBYSCORE key min max |
O(log(n) + k) | 按分数区间删除成员 |
| 集合运算 | ||
ZINTERSTORE dest numkeys key [key ...] |
O(nk) + O(mlog(m)) | n是最小输入集合大小,k是集合数,m是结果集大小 |
ZUNIONSTORE dest numkeys key [key ...] |
O(n) + O(m*log(m)) | n是输入集合总大小,m是结果集大小 |
内部编码
以下是整理后的Markdown格式内容:
有序集合类型的内部编码有两种:
1. ziplist(压缩列表)
- 使用条件:
- 元素个数 <
zset-max-ziplist-entries(默认128) - 每个元素值大小 <
zset-max-ziplist-value(默认64字节)
- 元素个数 <
- 优势:有效减少内存使用
- 示例:
127.0.0.1:6379> zadd zsetkey 50 e1 60 e2 30 e3
(integer) 3
127.0.0.1:6379> object encoding zsetkey
"ziplist"
2. skiplist(跳表)
- 使用条件:当不满足ziplist条件时自动切换
- 原因:元素过多或过大时,ziplist操作效率会下降
- 示例1(元素数量超过限制):
127.0.0.1:6379> zadd zsetkey 50 e1 60 e2 30 e3 ... e129
(integer) 129
127.0.0.1:6379> object encoding zsetkey
"skiplist"
- 示例2(元素大小超过限制):
127.0.0.1:6379> zadd zsetkey 50 "超长字符串...(超过64字节)..."
(integer) 1
127.0.0.1:6379> object encoding zsetkey
"skiplist"
配置参数
| 参数名 | 默认值 | 说明 |
|---|---|---|
zset-max-ziplist-entries |
128 | 使用ziplist的最大元素个数 |
zset-max-ziplist-value |
64 | 使用ziplist的单个元素最大字节数 |
应用场景
1. 实时排行榜系统
游戏积分排行榜
- 使用
ZADD添加玩家分数 ZREVRANGE获取前N名玩家ZINCRBY实时更新玩家分数
ZADD game_leaderboard 1500 "player1" 2000 "player2" 1800 "player3"
ZREVRANGE game_leaderboard 0 9 WITHSCORES # 获取TOP10
ZINCRBY game_leaderboard 50 "player1" # 玩家1增加50分
微博热搜榜
- 使用
ZINCRBY增加话题热度 ZREVRANGE获取实时热搜
ZINCRBY weibo_trending 1000000 "#世界杯决赛#"
ZINCRBY weibo_trending 500000 "#元旦假期安排#"
ZREVRANGE weibo_trending 0 4 WITHSCORES # 获取TOP5热搜
2. 延迟队列系统
订单超时处理
- 使用分数存储执行时间戳
- 定时任务获取到期订单
ZADD delay_queue 1672531200 "order_123" # 2023-01-01 00:00:00执行
ZRANGEBYSCORE delay_queue 0 1672531200 # 获取到期订单
3. 滑动窗口限流
API访问频率控制
- 使用时间戳作为分数
- 统计窗口内请求数
ZADD api_limit 1672500000 "req_1"
ZADD api_limit 1672500001 "req_2"
ZREMRANGEBYSCORE api_limit 0 1672500000 # 清理1秒前的请求
ZCARD api_limit # 获取当前秒内请求数
4. 带权重的投票系统
热门内容评选
- 不同用户投票权重不同
- 实时计算内容得分
ZADD content_votes 5 "article_123" # 初始5票
ZINCRBY content_votes 2 "article_123" # VIP用户投票权重为2
ZREVRANGE content_votes 0 9 WITHSCORES # 获取TOP10内容
5. 时间线排序
朋友圈动态
- 使用发布时间戳作为分数
- 获取最新动态
ZADD friend_timeline 1672500000 "post_123" # 发布动态
ZREVRANGE friend_timeline 0 19 # 获取最新20条动态
6. 多维度排序
商品综合排序
- 使用加权分数计算综合评分
- 销量、好评率、价格等多维度
ZADD product_rank 85 "product_123" # 初始综合分85
ZINCRBY product_rank 5 "product_123" # 销量增加提升排名
7. 实时竞赛系统
在线编程比赛
- 实时更新选手得分
- 获取实时排名
ZADD coding_contest 3 "user_123" # 3道题正确
ZINCRBY coding_contest 1 "user_123" # 又解决1题
ZREVRANK coding_contest "user_123" # 获取用户排名
8. 热点数据缓存
新闻点击排行
- 记录点击事件
- 实时统计热点
ZADD news_clicks 1500 "news_123" # 初始点击量
ZINCRBY news_clicks 1 "news_123" # 用户点击+1
ZREVRANGE news_clicks 0 9 # 获取TOP10热点新闻
ZSet的这些特性使其成为处理需要实时排序和加权计算场景的理想选择,性能优异且实现简洁。