Redis 有序集合解析

一、Redis 有序集合是什么?

Redis 有序集合(ZSet)是一种兼具集合和有序特性的高级数据结构,它由两个核心部分组成:

  1. 成员(Member)

    • 集合中的元素内容,可以是字符串、数字或其他Redis支持的数据类型
    • 与普通Set集合一样,成员具有唯一性,确保集合中不会存在重复的成员
    • 示例:在用户积分排行榜中,成员可以是用户ID
  2. 分数(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:添加成员到有序集合

命令格式

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

选项说明

  • NX:仅当成员不存在时才添加(Not Exists)
  • XX:仅当成员已存在时才更新分数(Exists)
  • CH:返回"新增的成员数"和"分数被更新的成员数"(默认只返回新增数)
  • INCR:将分数作为增量,对成员的分数进行累加(类似ZINCRBY)

实战示例

redis 复制代码
# 向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:从有序集合中删除成员

命令格式

redis 复制代码
ZREM key member [member ...]

功能:删除指定的一个或多个成员,返回成功删除的成员数(不存在的成员会被忽略)。

实战示例

redis 复制代码
# 删除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:获取成员的分数

命令格式

redis 复制代码
ZSCORE key member

功能:返回指定成员的分数,若成员不存在则返回nil。

实战示例

redis 复制代码
# 获取Alice的分数
127.0.0.1:6379> ZSCORE zset_rank Alice
"92"  # 返回分数(字符串格式)

# 获取不存在的Bob的分数
127.0.0.1:6379> ZSCORE zset_rank Bob
(nil)

(4) ZCARD:获取有序集合的成员总数

命令格式

redis 复制代码
ZCARD key

功能:返回ZSet的成员数量,若key不存在则返回0。

实战示例

redis 复制代码
# 获取zset_rank的成员数
127.0.0.1:6379> ZCARD zset_rank
(integer) 2  # 当前有Alice和Charlie两个成员

2. 范围查询:按分数/排名获取成员

(1) ZRANGE:按排名升序获取成员(从低到高)

命令格式

redis 复制代码
ZRANGE key start stop [WITHSCORES]

参数说明

  • start/stop:排名的起始和结束位置(排名从0开始,即分数最低的成员排名为0)
  • WITHSCORES:可选,返回结果中包含成员的分数

实战示例

redis 复制代码
# 先添加几个成员,方便演示
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:按排名降序获取成员(从高到低)

命令格式

redis 复制代码
ZREVRANGE key start stop [WITHSCORES]

功能:与ZRANGE相反,按排名降序返回成员(排名0为分数最高的成员)。

实战示例

redis 复制代码
# 获取排名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:按分数范围升序获取成员

命令格式

redis 复制代码
ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]

参数说明

  • min/max:分数的最小值和最大值(支持(表示"小于",如(85表示分数小于85)
  • LIMIT offset count:可选,对结果分页(offset为起始偏移量,count为获取数量)

实战示例

redis 复制代码
# 获取分数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:按分数范围降序获取成员

命令格式

redis 复制代码
ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count]

功能:与ZRANGEBYSCORE相反,按分数范围降序返回成员(注意max在前,min在后)。

实战示例

redis 复制代码
# 获取分数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)

实战示例

redis 复制代码
# 获取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:为成员的分数增加增量

命令格式

redis 复制代码
ZINCRBY key increment member

功能:将指定成员的分数增加increment(可正可负,负数表示减少),返回更新后的分数。

实战示例

redis 复制代码
# 给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的交集,结果存入destkey
  • ZUNIONSTORE destkey numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX]:计算多个ZSet的并集,结果存入destkey

参数说明

  • numkeys:参与运算的ZSet数量
  • WEIGHTS:可选,为每个ZSet设置权重,成员的分数会乘以对应权重
  • AGGREGATE:可选,指定分数聚合方式(SUM求和、MIN取最小、MAX取最大,默认SUM)

实战示例

redis 复制代码
# 新建两个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等操作高效
  • 插入/删除:只需调整局部指针,无需全局重构
  • 实现简单:相比红黑树等平衡树结构更易维护

具体实现细节

  1. 每次插入新节点时随机确定层数
  2. 搜索从最高层开始,逐步向下缩小范围
  3. 维护跨度(span)信息支持排名操作
② 哈希表(Dict)实现

数据结构: 使用Redis标准的字典实现:

  • 键:成员对象
  • 值:分数
  • 哈希算法:MurmurHash2
  • 冲突解决:链地址法

核心作用

  • 支持O(1)时间的ZSCORE操作
  • 确保成员唯一性
  • 快速判断成员是否存在

协同工作机制

  • 插入操作:
    1. 先在dict中检查成员是否存在
    2. 不存在则在dict添加记录
    3. 同时在skiplist中插入节点
  • 删除操作:
    1. 从dict中删除记录
    2. 从skiplist中删除节点
  • 查询操作:
    • 按成员查分数:直接访问dict
    • 按分数范围查询:使用skiplist

2. 底层结构切换逻辑

自动切换机制

ziplist → skiplist+dict触发条件

  1. 插入新成员后,成员总数超过zset-max-ziplist-entries
  2. 插入的成员长度超过zset-max-ziplist-value
  3. 执行合并操作(如ZUNIONSTORE)产生大集合

转换过程

  1. 创建新的dict和skiplist
  2. 遍历ziplist中的所有元素
  3. 将每个元素依次插入到新结构中
  4. 释放原ziplist内存
  5. 更新ZSet的指针指向新结构

重要特性

  • Redis不会自动将skiplist+dict转换回ziplist
  • 如需转换,必须手动操作:
    1. 删除原ZSet
    2. 重新创建ZSet并添加元素
    3. 确保元素满足ziplist条件

性能对比

操作类型 ziplist复杂度 skiplist+dict复杂度
插入元素 O(N) O(logN)
删除元素 O(N) O(logN)
按成员查分数 O(N) O(1)
按分数范围查询 O(N) O(logN + M)
内存占用 极低 较高

注:N为元素总数,M为返回的元素数量

3. 实际应用优化建议

  1. 对于小型固定集合:

    • 适当调小zset-max-ziplist-entries
    • 确保成员长度控制在64字节内
  2. 对于大型动态集合:

    • 监控内存使用情况
    • 考虑分片存储超大ZSet
  3. 特殊场景处理:

    • 频繁范围查询:保持skiplist结构
    • 主要做存在性检查:可考虑额外使用Set
  4. 性能调优:

    • 根据实际负载调整层数生成概率
    • 对于热点ZSet可考虑持久化策略

五、Redis 有序集合的典型应用场景

1. 排行榜系统(如游戏排名、积分排名)

业务需求

需要展示用户的积分排名,支持以下功能:

  • 查看前 10 名
  • 查看用户自己的排名
  • 查看用户附近的排名(如前后5名的用户)
  • 实时更新排名(用户积分变化后立即反映在榜单上)

实现思路

  1. 数据结构设计:

    • 以 "排行榜名称" 为 ZSet 的 key(如 game_rank
    • 以 "用户 ID" 为 member
    • 以 "用户积分" 为 score
  2. 核心操作:

    redis 复制代码
    # 添加/更新用户积分
    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
  3. 实际案例:

    • 游戏玩家战力排行榜
    • 电商平台会员积分榜
    • 直播平台主播热度榜

2. 延时任务队列(如订单超时取消、消息延时发送)

业务需求

需要实现以下功能:

  • 订单创建后30分钟未支付则自动取消
  • 消息延迟1小时发送
  • 优惠券到期提醒
  • 定时批处理任务调度

实现思路

  1. 数据结构设计:

    • 以 "延时任务队列名称" 为 ZSet 的 key(如 delay_queue
    • 以 "任务 ID" 为 member
    • 以 "任务执行时间戳(当前时间戳 + 延时时间)" 为 score
  2. 核心流程:

    redis 复制代码
    # 添加延时任务(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 task
       sleep(1)
  3. 优化方案:

    • 使用多个消费者并行处理
    • 添加失败重试机制
    • 设置任务优先级(可用多个ZSet实现)

3. 范围查询场景(如用户等级筛选、商品价格区间筛选)

业务需求

需要实现以下功能:

  • 筛选等级3-5级的用户
  • 筛选100-200元的商品
  • 分页展示查询结果
  • 支持多条件组合查询

实现思路

  1. 数据结构设计:

    • 以 "用户等级表" 或 "商品价格表" 为 ZSet 的 key(如 user_level, product_price
    • 以 "用户ID/商品ID" 为 member
    • 以 "用户等级/商品价格" 为 score
  2. 核心操作:

    redis 复制代码
    # 筛选等级3-5级的用户
    ZRANGEBYSCORE user_level 3 5
    
    # 筛选100-200元的商品并分页(每页20条)
    ZRANGEBYSCORE product_price 100 200 LIMIT 0 20
    
    # 获取符合条件的数据总数
    ZCOUNT product_price 100 200
  3. 高级应用:

    • 结合Lua脚本实现复杂查询
    • 与Hash结构配合存储完整对象信息
    • 使用多个ZSet实现多维度查询

4. 好友关系管理(如共同好友、好友活跃度排序)

业务需求

需要实现以下功能:

  • 展示用户的共同好友
  • 按好友活跃度排序
  • 推荐可能认识的人
  • 好友分组管理

实现思路

  1. 数据结构设计:

    • 为每个用户创建一个ZSet存储好友列表
    • key格式:user_friends:user_id
    • member:好友ID
    • score:好友活跃度(如聊天次数、互动频率)
  2. 核心操作:

    redis 复制代码
    # 计算两个用户的共同好友
    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
  3. 扩展应用:

    • 好友分组(不同ZSet存储不同分组)
    • 好友亲密关系分析
    • 社交网络中的二度人脉查询

六、Redis 有序集合的使用注意事项

1. 分数的精度问题

ZSet 的 score 是 64 位浮点数(double 类型),而 double 类型存在精度限制:对于整数,能精确表示的范围是-2^53 ~ 2^53;超过这个范围的整数,可能会出现精度丢失。

典型问题场景

  • 存储时间戳时,使用毫秒级时间戳(如 1710000000000)容易超出范围
  • 存储金融金额时,直接使用浮点数可能导致精度丢失(如 0.1 + 0.2 ≠ 0.3)

建议解决方案

  1. 若需存储整数型 score(如积分、时间戳),确保数值在-2^53 ~ 2^53范围内(约 ±9e15),避免精度丢失。

    • 示例:使用秒级时间戳(1710000000)而不是毫秒级
    • 示例:用户积分控制在1亿以内
  2. 若需更高精度(如金融场景),可将 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单线程特性会阻塞其他命令执行

建议优化方案

  1. 范围查询时尽量使用LIMIT参数分页,避免一次性获取大量数据

    • 示例:ZRANGEBYSCORE key min max LIMIT 0 20
    • 示例:ZREVRANGEBYSCORE key +inf -inf LIMIT 0 50
  2. 若需频繁查询大量数据,可考虑将数据同步到 MySQL 等关系型数据库,通过索引优化查询,减轻 Redis 压力。

    • 实现方案:使用Redis作为实时缓存,MySQL作为持久化存储
    • 同步方式:通过消息队列异步同步数据

3. 避免频繁修改大量成员的分数

当 ZSet 使用 skiplist+dict 结构时,修改成员分数会触发 skiplist 节点的重新排序(删除原节点→插入新节点),若频繁修改大量成员的分数(如每秒修改 1 万条),会导致 Redis 频繁进行 skiplist 调整,性能下降。

性能测试数据

  • 单机Redis每秒可处理约5万次简单命令
  • 但频繁ZINCRBY可能导致性能下降至1万次/秒以下

优化建议

  1. 若需批量更新分数,尽量合并操作

    • 示例:使用ZADD批量添加,而非循环调用ZINCRBY
    • 示例:ZADD key 100 member1 200 member2 300 member3
  2. 若业务允许,可降低分数更新频率

    • 示例:改为每5分钟更新一次,而非实时更新
    • 示例:本地缓存分数变化,定时批量提交

4. 合理设置 ziplist 的阈值

Redis 默认的zset-max-ziplist-entries=128zset-max-ziplist-value=64是基于通用场景的优化,但不同业务场景下可能需要调整:

配置参数说明

  • zset-max-ziplist-entries:ZSet最大元素数量,超过则转为skiplist
  • zint-max-ziplist-value:元素最大长度(字节),超过则转为skiplist

调整建议

  1. 若ZSet的成员长度普遍较小(如用户ID为6位数字),可适当增大zset-max-ziplist-entries

    • 示例:设为256可节省约15%内存
    • 适用场景:排行榜、计数器等小数据量场景
  2. 若ZSet的成员长度普遍较大(如包含长字符串描述),可适当减小zset-max-ziplist-value

    • 示例:设为32可避免大ziplist查询效率下降
    • 适用场景:存储带描述的标签系统

注意事项

  • 修改需在redis.conf中调整
  • 仅对新创建的ZSet生效
  • 已存在的ZSet需手动重建(先删除再创建)

5. 避免使用ZSet存储超大集合

虽然ZSet支持存储百万级甚至千万级数据,但当数据量过大时(如超过1000万条),会占用大量内存,且查询、修改操作的耗时会明显增加。

性能数据参考

  • 100万成员约占用100MB内存
  • 1000万成员约占用1GB内存
  • 查询延迟可能从毫秒级上升到秒级

优化方案

  1. 对超大集合进行分片存储

    • 示例:按用户ID哈希值分片存储到多个ZSet
    • 实现方式:user_rank_{hash(user_id)%10}
  2. 定期清理过期或无用数据

    • 示例:ZREMRANGEBYSCORE key -inf (current_timestamp-86400)
    • 示例:ZREMRANGEBYRANK key 0 -1000(保留前1000名)
  3. 考虑使用其他存储方案

    • 示例:Redis Cluster分散存储压力
    • 示例:将冷数据迁移到磁盘数据库

七、Redis 有序集合的扩展操作

1. ZREMRANGEBYRANK:按排名删除成员

命令格式ZREMRANGEBYRANK key start stop

功能说明:该命令用于删除有序集合中指定排名范围内的所有成员。排名是基于分数升序排列的,其中0表示分数最低的成员(第一名的索引为0)。

参数说明

  • start:起始排名(包含)
  • stop:结束排名(包含)

应用场景

  • 清除排行榜末尾的若干成员
  • 定期清理分数最低的过时数据

实战示例

redis 复制代码
# 准备测试数据
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:分数上限(包含)

应用场景

  • 清理分数低于某个阈值的成员
  • 删除特定分数段的数据

实战示例

redis 复制代码
# 准备测试数据
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:分数上限(包含)

应用场景

  • 统计满足特定条件的用户数量
  • 分析数据分布情况

实战示例

redis 复制代码
# 准备测试数据
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

功能说明:统计字典序在指定范围内的成员数量。

参数说明

  • minmax可以使用以下特殊语法:
    • [ 表示包含边界值
    • ( 表示不包含边界值
    • - 表示负无穷
    • + 表示正无穷

实战示例

redis 复制代码
# 新建一个所有成员分数都为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:返回的成员数量

实战示例

redis 复制代码
# 按字典序获取所有成员
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 应用场景

字典序操作特别适用于以下场景:

  1. 实现字母索引系统
  2. 构建有序的标签系统
  3. 实现简单的字典或单词列表
  4. 处理需要按名称排序的同类项集合
相关推荐
抹茶冰淇淋20 分钟前
降级系统后,2019年的Mac电脑重获新生
前端
路边草随风1 小时前
java实现发布spark yarn作业
java·spark·yarn
雪碧聊技术1 小时前
前端VUE3项目部署到linux服务器(CentOS 7)
前端·linux部署vue3项目
为爱停留1 小时前
Spring AI实现MCP(Model Context Protocol)详解与实践
java·人工智能·spring
汝生淮南吾在北4 小时前
SpringBoot+Vue饭店点餐管理系统
java·vue.js·spring boot·毕业设计·毕设
酒尘&7 小时前
JS数组不止Array!索引集合类全面解析
开发语言·前端·javascript·学习·js
冬夜戏雪8 小时前
【java学习日记】【2025.12.7】【7/60】
java·开发语言·学习
CC.GG8 小时前
【C++】二叉搜索树
java·c++·redis
学历真的很重要8 小时前
VsCode+Roo Code+Gemini 2.5 Pro+Gemini Balance AI辅助编程环境搭建(理论上通过多个Api Key负载均衡达到无限免费Gemini 2.5 Pro)
前端·人工智能·vscode·后端·语言模型·负载均衡·ai编程
用户47949283569159 小时前
"讲讲原型链" —— 面试官最爱问的 JavaScript 基础
前端·javascript·面试