Redis 有序集合解析
一、Redis 有序集合是什么?
Redis 有序集合(ZSet)是一种兼具集合和有序特性的高级数据结构,它由两个核心部分组成:
成员(Member):
- 集合中的元素内容,可以是字符串、数字或其他Redis支持的数据类型
- 与普通Set集合一样,成员具有唯一性,确保集合中不会存在重复的成员
- 示例:在用户积分排行榜中,成员可以是用户ID
分数(Score):
- 一个64位的双精度浮点数(double类型)
- 用于对成员进行排序,范围在
-(2^53)
到+(2^53)
之间 - 允许多个成员拥有相同的分数(此时按字典序排序)
- 但同一个成员不能有多个不同的分数(会覆盖原有分数)
- 示例:在排行榜中,分数可以表示用户积分值
二、Redis 有序集合(ZSet)核心特性
1. 自动去重机制
- 与 Redis 普通集合(Set)相同,ZSet 通过哈希表实现成员唯一性校验
- 重复处理策略:
- 默认行为( Merge 模式):当添加重复成员时,会更新该成员的分数
- 使用 NX 选项:仅添加新成员,忽略已存在成员的添加请求
- 使用 XX 选项:仅更新已存在成员的分数
- 示例:电商商品热度排行榜中,同一商品多次被点击时,系统会自动合并统计
2. 基于分数的有序存储
- 排序机制:
- 底层采用跳跃表(Skip List)和哈希表组合结构
- 每个成员关联一个64位双精度浮点数作为分数(score)
- 默认按分数升序排列(小→大)
- 排序控制:
- ZRANGE:获取升序排列结果
- ZREVRANGE:获取降序排列结果
- 应用场景:游戏排行榜中,可使用ZREVRANGE获取前100名玩家
3. 高效的范围查询实现
- 查询类型:
- 分数范围查询(ZRANGEBYSCORE):如查询成绩在80-90分的学生
- 排名范围查询(ZRANGE):如查询排名10-20的用户
- 字典序范围查询(ZRANGEBYLEX):适用于所有成员分数相同的情况
- 性能优化:
- 跳跃表结构保证范围查询效率
- 典型应用:实时统计在线用户数(通过ZCOUNT查询特定分数段的用户)
4. 动态分数更新能力
- 更新方式:
- ZADD:完全替换成员分数
- ZINCRBY:对成员分数进行增量调整(支持正负值)
- 自动维护:
- 分数变更后,Redis自动调整成员位置
- 示例场景:
- 社交媒体的点赞功能(每次点赞+1分)
- 电商平台的商品实时销量统计
- 游戏玩家经验值动态更新
5. 扩展特性
- 多维度排序:通过组合分数实现(如将时间戳作为小数部分)
- 原子操作:所有ZSet命令都是原子性的,适合高并发场景
- 存储限制:理论上最多可存储2^32-1个成员,实际受内存限制
三、Redis 有序集合常用命令
1. 基础操作:添加/删除/判断成员
(1) ZADD:添加成员到有序集合
命令格式:
ZADD key [NX|XX] [CH] [INCR] score member [score member ...]
选项说明:
NX
:仅当成员不存在时才添加(Not Exists)XX
:仅当成员已存在时才更新分数(Exists)CH
:返回"新增的成员数"和"分数被更新的成员数"(默认只返回新增数)INCR
:将分数作为增量,对成员的分数进行累加(类似ZINCRBY)
实战示例:
# 向zset_rank中添加3个成员,分数分别为90、85、95
127.0.0.1:6379> ZADD zset_rank 90 Alice 85 Bob 95 Charlie
(integer) 3 # 新增3个成员,返回3# 尝试添加已存在的成员Alice,分数改为92(默认覆盖)
127.0.0.1:6379> ZADD zset_rank 92 Alice
(integer) 0 # 未新增成员,返回0# 使用XX选项,仅更新已存在的成员Bob的分数
127.0.0.1:6379> ZADD zset_rank XX 88 Bob
(integer) 0 # 分数更新,但未新增成员,返回0# 使用INCR选项,将Charlie的分数增加5(95+5=100)
127.0.0.1:6379> ZADD zset_rank INCR 5 Charlie
"100" # 返回更新后的分数
(2) ZREM:从有序集合中删除成员
命令格式:
ZREM key member [member ...]
功能:删除指定的一个或多个成员,返回成功删除的成员数(不存在的成员会被忽略)。
实战示例:
# 删除zset_rank中的Bob
127.0.0.1:6379> ZREM zset_rank Bob
(integer) 1 # 成功删除1个成员# 尝试删除不存在的成员Dave
127.0.0.1:6379> ZREM zset_rank Dave
(integer) 0 # 未删除任何成员
(3) ZSCORE:获取成员的分数
命令格式:
ZSCORE key member
功能:返回指定成员的分数,若成员不存在则返回nil。
实战示例:
# 获取Alice的分数
127.0.0.1:6379> ZSCORE zset_rank Alice
"92" # 返回分数(字符串格式)# 获取不存在的Bob的分数
127.0.0.1:6379> ZSCORE zset_rank Bob
(nil)
(4) ZCARD:获取有序集合的成员总数
命令格式:
ZCARD key
功能:返回ZSet的成员数量,若key不存在则返回0。
实战示例:
# 获取zset_rank的成员数
127.0.0.1:6379> ZCARD zset_rank
(integer) 2 # 当前有Alice和Charlie两个成员
2. 范围查询:按分数/排名获取成员
(1) ZRANGE:按排名升序获取成员(从低到高)
命令格式:
ZRANGE key start stop [WITHSCORES]
参数说明:
start/stop
:排名的起始和结束位置(排名从0开始,即分数最低的成员排名为0)WITHSCORES
:可选,返回结果中包含成员的分数
实战示例:
# 先添加几个成员,方便演示
127.0.0.1:6379> ZADD zset_score 80 Tom 85 Jerry 90 Mike 95 Lucy
(integer) 4# 获取排名0-2的成员(前3名,分数从低到高)
127.0.0.1:6379> ZRANGE zset_score 0 2
1) "Tom" # 排名0(80)
2) "Jerry" # 排名1(85)
3) "Mike" # 排名2(90)# 获取排名1-3的成员,并包含分数
127.0.0.1:6379> ZRANGE zset_score 1 3 WITHSCORES
1) "Jerry"
2) "85"
3) "Mike"
4) "90"
5) "Lucy"
6) "95"
(2) ZREVRANGE:按排名降序获取成员(从高到低)
命令格式:
ZREVRANGE key start stop [WITHSCORES]
功能:与ZRANGE相反,按排名降序返回成员(排名0为分数最高的成员)。
实战示例:
# 获取排名0-2的成员(分数从高到低的前3名)
127.0.0.1:6379> ZREVRANGE zset_score 0 2
1) "Lucy" # 排名0(95)
2) "Mike" # 排名1(90)
3) "Jerry" # 排名2(85)
(3) ZRANGEBYSCORE:按分数范围升序获取成员
命令格式:
ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]
参数说明:
min/max
:分数的最小值和最大值(支持(
表示"小于",如(85
表示分数小于85)LIMIT offset count
:可选,对结果分页(offset为起始偏移量,count为获取数量)
实战示例:
# 获取分数85-90的成员(包含85和90)
127.0.0.1:6379> ZRANGEBYSCORE zset_score 85 90
1) "Jerry" # 85
2) "Mike" # 90# 获取分数大于85且小于等于95的成员(用(85表示排除85)
127.0.0.1:6379> ZRANGEBYSCORE zset_score (85 95
1) "Mike" # 90
2) "Lucy" # 95# 获取分数80-95的成员,从第2个开始取2个(分页)
127.0.0.1:6379> ZRANGEBYSCORE zset_score 80 95 LIMIT 1 2
1) "Jerry" # 第2个成员
2) "Mike" # 第3个成员
(4) ZREVRANGEBYSCORE:按分数范围降序获取成员
命令格式:
ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count]
功能:与ZRANGEBYSCORE相反,按分数范围降序返回成员(注意max在前,min在后)。
实战示例:
# 获取分数85-95的成员,按分数降序排列
127.0.0.1:6379> ZREVRANGEBYSCORE zset_score 95 85
1) "Lucy" # 95
2) "Mike" # 90
3) "Jerry" # 85
3. 进阶操作:排名查询、分数增减、交集并集
(1) ZRANK/ZREVRANK:获取成员的排名
ZRANK key member
:返回成员的升序排名(从0开始,分数最低为0)ZREVRANK key member
:返回成员的降序排名(从0开始,分数最高为0)
实战示例:
# 获取Lucy的升序排名(分数95,最高,升序排名为3)
127.0.0.1:6379> ZRANK zset_score Lucy
(integer) 3# 获取Lucy的降序排名(分数最高,降序排名为0)
127.0.0.1:6379> ZREVRANK zset_score Lucy
(integer) 0
(2) ZINCRBY:为成员的分数增加增量
命令格式:
ZINCRBY key increment member
功能:将指定成员的分数增加increment(可正可负,负数表示减少),返回更新后的分数。
实战示例:
# 给Tom的分数增加5(80+5=85)
127.0.0.1:6379> ZINCRBY zset_score 5 Tom
"85"# 给Mike的分数减少10(90-10=80)
127.0.0.1:6379> ZINCRBY zset_score -10 Mike
"80"
(3) ZINTERSTORE/ZUNIONSTORE:交集/并集操作
ZINTERSTORE destkey numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX]
:计算多个ZSet的交集,结果存入destkeyZUNIONSTORE destkey numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX]
:计算多个ZSet的并集,结果存入destkey
参数说明:
numkeys
:参与运算的ZSet数量WEIGHTS
:可选,为每个ZSet设置权重,成员的分数会乘以对应权重AGGREGATE
:可选,指定分数聚合方式(SUM求和、MIN取最小、MAX取最大,默认SUM)
实战示例:
# 新建两个ZSet:zset1和zset2
127.0.0.1:6379> ZADD zset1 10 a 20 b 30 c
(integer) 3
127.0.0.1:6379> ZADD zset2 20 a 30 b 40 d
(integer) 3# 计算zset1和zset2的交集(成员a、b),存入zset_inter
127.0.0.1:6379> ZINTERSTORE zset_inter 2 zset1 zset2
(integer) 2 # 交集有2个成员# 查看交集结果(默认SUM,a的分数10+20=30,b的分数20+30=50)
127.0.0.1:6379> ZRANGE zset_inter 0 -1 WITHSCORES
1) "a"
2) "30"
3) "b"
4) "50"# 计算zset1和zset2的并集,存入zset_union,聚合方式取MAX
127.0.0.1:6379> ZUNIONSTORE zset_union 2 zset1 zset2 AGGREGATE MAX
(integer) 4 # 并集有4个成员(a、b、c、d)# 查看并集结果(a取20,b取30,c取30,d取40)
127.0.0.1:6379> ZRANGE zset_union 0 -1 WITHSCORES
1) "a"
2) "20"
3) "b"
4) "30"
5) "c"
6) "30"
7) "d"
8) "40"
四、Redis 有序集合的底层实现(为什么这么快?)
1. 两种底层结构
(1)压缩列表(ziplist):小数据量场景
适用条件: 当 ZSet 同时满足以下两个条件时,Redis 会使用压缩列表作为底层实现:
- 成员数量小于
zset-max-ziplist-entries
(默认128) - 每个成员的长度小于
zset-max-ziplist-value
(默认64字节)
配置参数(可通过redis.conf调整):
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
优势特点:
- 内存利用率极高:采用连续内存存储,无指针开销
- 局部性原理:数据紧凑排列,CPU缓存命中率高
- 小数据量下操作效率满足需求
具体结构: 压缩列表由一系列节点(entry)组成,每个节点存储一个分数或成员,严格按照分数升序排列。对于ZSet,排列模式为:分数1→成员1→分数2→成员2→...→分数N→成员N。
示例结构: 对于命令 ZADD zset_small 80 Tom 85 Jerry
,ziplist存储如下:
[zlbytes][zltail][zllen][entry(80)][entry(Tom)][entry(85)][entry(Jerry)][zlend]
字段说明:
zlbytes
:4字节,记录整个ziplist占用的内存字节数zltail
:4字节,记录最后一个节点的偏移量zllen
:2字节,记录节点数量(本例为4:2分数+2成员)entry
:变长节点,包含:prevlen
:前一个节点的长度(变长编码)encoding
:当前节点数据的编码方式data
:实际存储的数据(分数或成员)
zlend
:1字节,固定值0xFF,标记ziplist结束
操作特性:
- 插入/删除需要内存重分配和移动数据
- 查询需要线性遍历(但小数据量下影响不大)
- 最大支持2^32-1字节(约4GB)的数据
(2)跳跃表(skiplist)+ 哈希表(dict):大数据量场景
当ZSet不满足ziplist条件(成员数≥128或任一成员长度≥64字节)时,Redis会自动切换为跳跃表+哈希表的组合结构。
① 跳跃表(Skiplist)实现
数据结构: 跳跃表是一种概率平衡的多层链表结构,由William Pugh于1990年提出。Redis中的实现特点:
- 最大层数32(实际层数根据元素数量动态调整)
- 层数生成算法:随机生成,每增加一层的概率为25%
- 节点包含:
- 成员对象(robj*)
- 分数(double)
- 后退指针(backward)
- 层数组(level[])
示例结构: 对于包含3个节点(Tom:80、Jerry:85、Lucy:95)的跳跃表:
Level 2: header → Tom → Lucy
Level 1: header → Tom → Jerry → Lucy
Level 0: header → Tom → Jerry → Lucy
核心优势:
- 查询复杂度:平均O(logN),最坏O(N)
- 范围查询:ZRANGE/ZREVRANGE等操作高效
- 插入/删除:只需调整局部指针,无需全局重构
- 实现简单:相比红黑树等平衡树结构更易维护
具体实现细节:
- 每次插入新节点时随机确定层数
- 搜索从最高层开始,逐步向下缩小范围
- 维护跨度(span)信息支持排名操作
② 哈希表(Dict)实现
数据结构: 使用Redis标准的字典实现:
- 键:成员对象
- 值:分数
- 哈希算法:MurmurHash2
- 冲突解决:链地址法
核心作用:
- 支持O(1)时间的ZSCORE操作
- 确保成员唯一性
- 快速判断成员是否存在
协同工作机制:
- 插入操作:
- 先在dict中检查成员是否存在
- 不存在则在dict添加记录
- 同时在skiplist中插入节点
- 删除操作:
- 从dict中删除记录
- 从skiplist中删除节点
- 查询操作:
- 按成员查分数:直接访问dict
- 按分数范围查询:使用skiplist
2. 底层结构切换逻辑
自动切换机制
ziplist → skiplist+dict触发条件:
- 插入新成员后,成员总数超过zset-max-ziplist-entries
- 插入的成员长度超过zset-max-ziplist-value
- 执行合并操作(如ZUNIONSTORE)产生大集合
转换过程:
- 创建新的dict和skiplist
- 遍历ziplist中的所有元素
- 将每个元素依次插入到新结构中
- 释放原ziplist内存
- 更新ZSet的指针指向新结构
重要特性:
- Redis不会自动将skiplist+dict转换回ziplist
- 如需转换,必须手动操作:
- 删除原ZSet
- 重新创建ZSet并添加元素
- 确保元素满足ziplist条件
性能对比
操作类型 | ziplist复杂度 | skiplist+dict复杂度 |
---|---|---|
插入元素 | O(N) | O(logN) |
删除元素 | O(N) | O(logN) |
按成员查分数 | O(N) | O(1) |
按分数范围查询 | O(N) | O(logN + M) |
内存占用 | 极低 | 较高 |
注:N为元素总数,M为返回的元素数量
3. 实际应用优化建议
对于小型固定集合:
- 适当调小zset-max-ziplist-entries
- 确保成员长度控制在64字节内
对于大型动态集合:
- 监控内存使用情况
- 考虑分片存储超大ZSet
特殊场景处理:
- 频繁范围查询:保持skiplist结构
- 主要做存在性检查:可考虑额外使用Set
性能调优:
- 根据实际负载调整层数生成概率
- 对于热点ZSet可考虑持久化策略
五、Redis 有序集合的典型应用场景
1. 排行榜系统(如游戏排名、积分排名)
业务需求
需要展示用户的积分排名,支持以下功能:
- 查看前 10 名
- 查看用户自己的排名
- 查看用户附近的排名(如前后5名的用户)
- 实时更新排名(用户积分变化后立即反映在榜单上)
实现思路
数据结构设计:
- 以 "排行榜名称" 为 ZSet 的 key(如
game_rank
) - 以 "用户 ID" 为 member
- 以 "用户积分" 为 score
- 以 "排行榜名称" 为 ZSet 的 key(如
核心操作:
# 添加/更新用户积分 ZADD game_rank 100 user1 200 user2 150 user3# 获取积分前10的用户(降序排列) ZREVRANGE game_rank 0 9 WITHSCORES# 获取指定用户的排名(降序排名,0为第一名) ZREVRANK game_rank user_id# 获取用户附近的排名(如排名第5的用户,查看3-7名) ZREVRANGE game_rank 2 6 WITHSCORES# 获取用户积分 ZSCORE game_rank user_id
实际案例:
- 游戏玩家战力排行榜
- 电商平台会员积分榜
- 直播平台主播热度榜
2. 延时任务队列(如订单超时取消、消息延时发送)
业务需求
需要实现以下功能:
- 订单创建后30分钟未支付则自动取消
- 消息延迟1小时发送
- 优惠券到期提醒
- 定时批处理任务调度
实现思路
数据结构设计:
- 以 "延时任务队列名称" 为 ZSet 的 key(如
delay_queue
) - 以 "任务 ID" 为 member
- 以 "任务执行时间戳(当前时间戳 + 延时时间)" 为 score
- 以 "延时任务队列名称" 为 ZSet 的 key(如
核心流程:
# 添加延时任务(30分钟后执行) ZADD delay_queue 1672534200 task_id_123# 消费任务(每秒执行一次) while true:# 获取当前需要执行的任务tasks = ZRANGEBYSCORE delay_queue 0 当前时间戳for task in tasks:# 执行任务逻辑process_task(task)# 从队列中删除已完成任务ZREM delay_queue tasksleep(1)
优化方案:
- 使用多个消费者并行处理
- 添加失败重试机制
- 设置任务优先级(可用多个ZSet实现)
3. 范围查询场景(如用户等级筛选、商品价格区间筛选)
业务需求
需要实现以下功能:
- 筛选等级3-5级的用户
- 筛选100-200元的商品
- 分页展示查询结果
- 支持多条件组合查询
实现思路
数据结构设计:
- 以 "用户等级表" 或 "商品价格表" 为 ZSet 的 key(如
user_level
,product_price
) - 以 "用户ID/商品ID" 为 member
- 以 "用户等级/商品价格" 为 score
- 以 "用户等级表" 或 "商品价格表" 为 ZSet 的 key(如
核心操作:
# 筛选等级3-5级的用户 ZRANGEBYSCORE user_level 3 5# 筛选100-200元的商品并分页(每页20条) ZRANGEBYSCORE product_price 100 200 LIMIT 0 20# 获取符合条件的数据总数 ZCOUNT product_price 100 200
高级应用:
- 结合Lua脚本实现复杂查询
- 与Hash结构配合存储完整对象信息
- 使用多个ZSet实现多维度查询
4. 好友关系管理(如共同好友、好友活跃度排序)
业务需求
需要实现以下功能:
- 展示用户的共同好友
- 按好友活跃度排序
- 推荐可能认识的人
- 好友分组管理
实现思路
数据结构设计:
- 为每个用户创建一个ZSet存储好友列表
- key格式:
user_friends:user_id
- member:好友ID
- score:好友活跃度(如聊天次数、互动频率)
核心操作:
# 计算两个用户的共同好友 ZINTERSTORE common_friends 2 user_friends:user1 user_friends:user2# 按活跃度降序展示共同好友 ZREVRANGE common_friends 0 -1 WITHSCORES# 计算好友推荐(可能认识的人) ZUNIONSTORE recommend 3 user_friends:friend1 user_friends:friend2 user_friends:friend3 ZDIFFSTORE recommend recommend user_friends:current_user
扩展应用:
- 好友分组(不同ZSet存储不同分组)
- 好友亲密关系分析
- 社交网络中的二度人脉查询
六、Redis 有序集合的使用注意事项
1. 分数的精度问题
ZSet 的 score 是 64 位浮点数(double 类型),而 double 类型存在精度限制:对于整数,能精确表示的范围是-2^53 ~ 2^53;超过这个范围的整数,可能会出现精度丢失。
典型问题场景:
- 存储时间戳时,使用毫秒级时间戳(如 1710000000000)容易超出范围
- 存储金融金额时,直接使用浮点数可能导致精度丢失(如 0.1 + 0.2 ≠ 0.3)
建议解决方案:
若需存储整数型 score(如积分、时间戳),确保数值在-2^53 ~ 2^53范围内(约 ±9e15),避免精度丢失。
- 示例:使用秒级时间戳(1710000000)而不是毫秒级
- 示例:用户积分控制在1亿以内
若需更高精度(如金融场景),可将 score 乘以 10 的 N 次方转换为整数存储(如将金额保留 2 位小数,乘以 100 后存储为整数),使用时再除以 100 还原。
- 示例:存储金额123.45元,实际存储为12345
- 示例:存储汇率123.4567元,可乘以10000后存储为1234567
2. 大数据量下的范围查询性能
虽然 ZSet 的范围查询时间复杂度是 O(logN + K),但当 K(查询结果数量)过大时(如查询 10 万条数据),仍会占用大量 CPU 和网络资源,影响 Redis 性能。
性能影响示例:
- 查询10万条数据可能需要200-300ms
- 网络传输大量数据会占用带宽
- Redis单线程特性会阻塞其他命令执行
建议优化方案:
范围查询时尽量使用LIMIT参数分页,避免一次性获取大量数据
- 示例:
ZRANGEBYSCORE key min max LIMIT 0 20
- 示例:
ZREVRANGEBYSCORE key +inf -inf LIMIT 0 50
- 示例:
若需频繁查询大量数据,可考虑将数据同步到 MySQL 等关系型数据库,通过索引优化查询,减轻 Redis 压力。
- 实现方案:使用Redis作为实时缓存,MySQL作为持久化存储
- 同步方式:通过消息队列异步同步数据
3. 避免频繁修改大量成员的分数
当 ZSet 使用 skiplist+dict 结构时,修改成员分数会触发 skiplist 节点的重新排序(删除原节点→插入新节点),若频繁修改大量成员的分数(如每秒修改 1 万条),会导致 Redis 频繁进行 skiplist 调整,性能下降。
性能测试数据:
- 单机Redis每秒可处理约5万次简单命令
- 但频繁ZINCRBY可能导致性能下降至1万次/秒以下
优化建议:
若需批量更新分数,尽量合并操作
- 示例:使用
ZADD
批量添加,而非循环调用ZINCRBY
- 示例:
ZADD key 100 member1 200 member2 300 member3
- 示例:使用
若业务允许,可降低分数更新频率
- 示例:改为每5分钟更新一次,而非实时更新
- 示例:本地缓存分数变化,定时批量提交
4. 合理设置 ziplist 的阈值
Redis 默认的zset-max-ziplist-entries=128
和zset-max-ziplist-value=64
是基于通用场景的优化,但不同业务场景下可能需要调整:
配置参数说明:
zset-max-ziplist-entries
:ZSet最大元素数量,超过则转为skiplistzint-max-ziplist-value
:元素最大长度(字节),超过则转为skiplist
调整建议:
若ZSet的成员长度普遍较小(如用户ID为6位数字),可适当增大
zset-max-ziplist-entries
- 示例:设为256可节省约15%内存
- 适用场景:排行榜、计数器等小数据量场景
若ZSet的成员长度普遍较大(如包含长字符串描述),可适当减小
zset-max-ziplist-value
- 示例:设为32可避免大ziplist查询效率下降
- 适用场景:存储带描述的标签系统
注意事项:
- 修改需在redis.conf中调整
- 仅对新创建的ZSet生效
- 已存在的ZSet需手动重建(先删除再创建)
5. 避免使用ZSet存储超大集合
虽然ZSet支持存储百万级甚至千万级数据,但当数据量过大时(如超过1000万条),会占用大量内存,且查询、修改操作的耗时会明显增加。
性能数据参考:
- 100万成员约占用100MB内存
- 1000万成员约占用1GB内存
- 查询延迟可能从毫秒级上升到秒级
优化方案:
对超大集合进行分片存储
- 示例:按用户ID哈希值分片存储到多个ZSet
- 实现方式:
user_rank_{hash(user_id)%10}
定期清理过期或无用数据
- 示例:
ZREMRANGEBYSCORE key -inf (current_timestamp-86400)
- 示例:
ZREMRANGEBYRANK key 0 -1000
(保留前1000名)
- 示例:
考虑使用其他存储方案
- 示例:Redis Cluster分散存储压力
- 示例:将冷数据迁移到磁盘数据库
七、Redis 有序集合的扩展操作
1. ZREMRANGEBYRANK:按排名删除成员
命令格式:ZREMRANGEBYRANK key start stop
功能说明:该命令用于删除有序集合中指定排名范围内的所有成员。排名是基于分数升序排列的,其中0表示分数最低的成员(第一名的索引为0)。
参数说明:
start
:起始排名(包含)stop
:结束排名(包含)
应用场景:
- 清除排行榜末尾的若干成员
- 定期清理分数最低的过时数据
实战示例:
# 准备测试数据
127.0.0.1:6379> ZADD zset_score 60 "Tom" 70 "Jerry" 80 "Mike" 90 "Alice" 100 "Bob"
(integer) 5# 删除zset_score中排名0-1的成员(分数最低的2个成员)
127.0.0.1:6379> ZREMRANGEBYRANK zset_score 0 1
(integer) 2 # 成功删除"Tom"和"Jerry"两个成员# 验证结果
127.0.0.1:6379> ZRANGE zset_score 0 -1 WITHSCORES
1) "Mike"
2) "80"
3) "Alice"
4) "90"
5) "Bob"
6) "100"
2. ZREMRANGEBYSCORE:按分数删除成员
命令格式:ZREMRANGEBYSCORE key min max
功能说明:删除有序集合中分数在指定范围内的所有成员。可以使用-inf
和+inf
表示无限小和无限大。
参数说明:
min
:分数下限(包含)max
:分数上限(包含)
应用场景:
- 清理分数低于某个阈值的成员
- 删除特定分数段的数据
实战示例:
# 准备测试数据
127.0.0.1:6379> ZADD zset_score 60 "Tom" 70 "Jerry" 80 "Mike" 90 "Alice" 100 "Bob"
(integer) 5# 删除zset_score中分数小于85的成员
127.0.0.1:6379> ZREMRANGEBYSCORE zset_score -inf 85
(integer) 3 # 成功删除"Tom"(60)、"Jerry"(70)和"Mike"(80)三个成员# 验证结果
127.0.0.1:6379> ZRANGE zset_score 0 -1 WITHSCORES
1) "Alice"
2) "90"
3) "Bob"
4) "100"
3. ZCOUNT:统计分数范围内的成员数量
命令格式:ZCOUNT key min max
功能说明:统计有序集合中分数在指定范围内的成员数量,比先获取成员再计数更高效。
参数说明:
min
:分数下限(包含)max
:分数上限(包含)
应用场景:
- 统计满足特定条件的用户数量
- 分析数据分布情况
实战示例:
# 准备测试数据
127.0.0.1:6379> ZADD zset_score 60 "Tom" 70 "Jerry" 80 "Mike" 90 "Alice" 100 "Bob"
(integer) 5# 统计zset_score中分数85-95的成员数量
127.0.0.1:6379> ZCOUNT zset_score 85 95
(integer) 1 # 只有"Alice"(90)符合条件# 统计所有成员数量
127.0.0.1:6379> ZCOUNT zset_score -inf +inf
(integer) 5
4. 字典序相关操作
4.1 基本概念
当有序集合中所有成员的分数相同时,Redis会按照成员的字典序(lexicographical order,基于ASCII码值)进行排序。针对这种情况,Redis提供了专门的字典序操作命令。
注意事项:
- 这些命令仅在所有成员分数相同时有意义
- 字典序比较基于成员的二进制安全字符串
4.2 ZLEXCOUNT
命令格式:ZLEXCOUNT key min max
功能说明:统计字典序在指定范围内的成员数量。
参数说明:
min
和max
可以使用以下特殊语法:[
表示包含边界值(
表示不包含边界值-
表示负无穷+
表示正无穷
实战示例:
# 新建一个所有成员分数都为0的ZSet
127.0.0.1:6379> ZADD zset_lex 0 apple 0 banana 0 cherry 0 date
(integer) 4# 统计字典序在"apple"到"cherry"之间的成员数量(包含边界)
127.0.0.1:6379> ZLEXCOUNT zset_lex [apple [cherry
(integer) 3 # 包含apple、banana、cherry# 统计字典序在"apple"到"cherry"之间的成员数量(不包含左边界)
127.0.0.1:6379> ZLEXCOUNT zset_lex (apple [cherry
(integer) 2 # 只有banana、cherry
4.3 ZRANGEBYLEX
命令格式:ZRANGEBYLEX key min max [LIMIT offset count]
功能说明:按字典序获取指定范围内的成员,可以结合LIMIT参数进行分页查询。
参数说明:
- 边界值语法与ZLEXCOUNT相同
- LIMIT参数:
offset
:跳过的成员数量count
:返回的成员数量
实战示例:
# 按字典序获取所有成员
127.0.0.1:6379> ZRANGEBYLEX zset_lex - +
1) "apple"
2) "banana"
3) "cherry"
4) "date"# 按字典序获取前2个成员
127.0.0.1:6379> ZRANGEBYLEX zset_lex - + LIMIT 0 2
1) "apple"
2) "banana"# 获取字典序在"b"到"d"之间的成员(不包含"b"开头的)
127.0.0.1:6379> ZRANGEBYLEX zset_lex (b [d
1) "cherry"
2) "date"
4.4 应用场景
字典序操作特别适用于以下场景:
- 实现字母索引系统
- 构建有序的标签系统
- 实现简单的字典或单词列表
- 处理需要按名称排序的同类项集合