redis----zset详解
Redis 的 Zset(Sorted Set,有序集合)是一种兼具唯一性和有序性的复合数据结构,在实际开发中常用于排行榜、延时任务、范围统计等场景。
核心特性
- 元素唯一,分数排序:每个元素(
member
)唯一,但分数(score
)可重复。Zset 会根据分数对元素进行升序排序,分数相同则按元素字典序排序。 - 动态排序:元素的分数可随时更新,更新后 Zset 会自动重新排序,无需手动维护。
- 高效性能:基于跳表(Skip List)和哈希表实现,插入、删除、范围查询的时间复杂度均为 O (log N),在大数据量下仍能保持高效。
常用命令
元素操作
ZADD
:添加或更新元素及分数ZADD key [NX|XX] [CH] [INCR] score member [score member ...]
NX
:仅当元素不存在时添加XX
:仅当元素存在时更新分数INCR
:将分数视为增量(如积分累加)
示例:
redis
ZADD rank 95 "Alice" 88 "Bob" 92 "Charlie" # 批量添加 ZADD rank XX 98 "Alice" # 更新Alice的分数 ZADD rank INCR 5 "Bob" # Bob的分数加5
ZSCORE
:获取元素的分数ZSCORE key member
示例:
ZSCORE rank "Alice"
→ 返回"98"
有序查询
ZRANGE
:按分数升序查询指定范围元素(0 为第一个,-1 为最后一个)ZRANGE key start end [WITHSCORES]
示例:
ZRANGE rank 0 -1 WITHSCORES
→ 返回带分数的元素列表ZREVRANGE
:按分数降序查询(适合排行榜 “从高到低”)ZREVRANGE key start end [WITHSCORES]
示例:
ZREVRANGE rank 0 1 WITHSCORES
→ 返回 Top 2 元素
范围统计与删除
ZRANGEBYSCORE
:按分数区间查询元素ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]
示例:
ZRANGEBYSCORE rank 90 100
→ 查询分数 90-100 的元素ZREM
:删除指定元素ZREM key member [member ...]
示例:
ZREM rank "Bob"
→ 删除 Bob,返回删除数量
典型应用场景
- 排行榜系统:如游戏积分榜、电商销量榜,用
ZREVRANGE
快速获取 Top N,ZSCORE
实时查询用户排名。 - 延时任务队列:将任务 ID 作为元素,执行时间戳作为分数,定期用
ZRANGEBYSCORE
获取 “已到执行时间” 的任务。 - 范围统计:如统计 “分数 80-90 分的学生”“粉丝数 1 万以上的博主”,通过
ZRANGEBYSCORE
高效筛选。
注意事项
- 分数精度:分数为 64 位浮点型,避免过度依赖高精度(如小数位数过多可能导致排序误差)。
- 大数据量优化:若 Zset 元素超百万级,避免使用
ZRANGE 0 -1
全量查询,建议分页查询(如ZRANGE 0 99
)。 - 内存占用:Zset 比普通 Set 内存占用稍高,若对内存敏感,需结合业务合理使用。
内部编码
Redis 中 Zset 的内部编码并非固定,会根据数据规模自动切换,核心目标是平衡「内存效率」和「操作性能」,主要包含两种编码形式:
一、ziplist(压缩列表)编码
这是 Zset 存储小规模数据时的默认编码,触发条件需同时满足以下 2 个配置阈值(可在 redis.conf 调整):
- 元素总数量 ≤
zset-max-ziplist-entries
(默认值:128) - 每个元素的「member(成员)」字符串长度 ≤
zset-max-ziplist-value
(默认值:64 字节)
1. 存储结构
ziplist 是连续的内存块,元素按「score(分数)升序」依次存储,格式为:
[zlbytes(总长度)][zltail(尾偏移)][zllen(元素数)][score1][member1][score2][member2]...[zlend(结束标识)]
- score:以「小端字节序的浮点型」存储(节省空间);
- member:以原始字符串形式存储,无额外指针开销。
2. 核心特点
- 优点:内存占用极低(连续内存 + 紧凑存储),遍历效率高(无需跳转指针);
- 缺点:插入 / 删除元素时需移动后续内存块,时间复杂度为 O (n),数据量大时性能骤降。
二、skiplist(跳表)+ dict(哈希表)编码
当 Zset 数据规模超过 ziplist 的阈值(元素数超 128 或 member 长度超 64 字节)时,Redis 会自动转为该编码,这是 Zset 处理「大规模数据」的核心结构。
1. 结构组成(双结构协同)
两种结构共享同一份数据(通过指针关联,无冗余存储),各自负责不同场景的高效操作:
(1)skiplist(跳表)—— 维护有序性与范围操作
- 本质:多层级链表结构,每个节点包含多个「层级指针」(类似 “高速公路 + 普通公路” 的分层索引);
- 作用:按 score 升序存储所有元素,支持 O(log n) 复杂度的插入、删除,以及范围查询(如
ZRANGE
、ZREVRANGE
、ZRANGEBYSCORE
等); - 优势:相比平衡二叉树(如红黑树),跳表实现更简单,且范围查询效率更高。
(2)dict(哈希表)—— 快速定位元素分数
- 本质:经典的键值对结构,「键 = member」,「值 = 对应的 score」;
- 作用:支持 O(1) 复杂度的「根据 member 查 score」操作(如
ZSCORE
命令); - 优势:避开跳表的 O (log n) 查找开销,直接通过哈希映射快速定位。
三、编码转换规则
- 单向转换:仅支持「ziplist → skiplist+dict」,一旦触发转换,后续即使删除元素使数据规模缩小,也不会再转回 ziplist;
- 触发时机:添加元素时实时检查阈值,一旦超过立即转换(无需手动干预);
- 配置调整:若业务中 Zset 多为小规模数据,可适当调大
zset-max-ziplist-entries
或zset-max-ziplist-value
,进一步节省内存;若多为大规模数据,可调小阈值,提前切换到高性能编码。
四、查看当前编码
通过 OBJECT ENCODING
命令可直接查看某个 Zset 的内部编码,示例:
127.0.0.1:6379> ZADD myzset 1 "a" 2 "b" # 小规模数据,用 ziplist
(integer) 2
127.0.0.1:6379> OBJECT ENCODING myzset
"ziplist" # 输出 ziplist127.0.0.1:6379> ZADD myzset 3 "long-long-member-more-than-64-bytes-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
(integer) 1
127.0.0.1:6379> OBJECT ENCODING myzset
"zset" # 输出 zset,代表 skiplist+dict 编码