知其然要知其所以然,探索每一个知识点背后的意义,你知道的越多,你不知道的越多,一起学习,一起进步,如果文章感觉对您有用的话,关注、收藏、点赞,有困惑的地方请评论,我们一起交流!
Redis 中的 ZSet(有序集合) 是一种结合了 Set 和 Sorted 特性的数据结构,既能保证元素的唯一性,又能按分数(Score)排序。其高效实现依赖于 跳跃表(Skip List) 和 哈希表(Hash Table) 的混合结构。以下是详细分析:
一、ZSet 的核心特性
- 唯一性:成员(Member)不可重复,类似 Set。
- 有序性:按分数(Score)排序,分数可重复。
- 操作效率 :
- 插入、删除、查找:平均 O(logN) 时间复杂度。
- 范围查询(如
ZRANGE
):O(logN + M)(M 为返回元素数量)。
二、底层实现:跳跃表 + 哈希表
ZSet 通过 两种数据结构 协同工作,兼顾高效查询和范围操作:
1. 跳跃表(Skip List)
-
作用:按分数排序存储成员,支持快速的范围查询。
-
结构特点 :
- 多层链表结构,底层包含所有元素,上层是索引层(概率性构建)。
- 每个节点包含:
- 成员(Member):存储实际数据(如用户 ID)。
- 分数(Score):用于排序的数值。
- 后退指针(Backward Pointer):指向底层前驱节点。
- 层(Level):每层有前进指针和跨度(Span)。
-
示例 (简化版跳跃表):
plaintext头节点 -> [L3: NULL] [L2: -> 节点B (Score=30)] -> [L2: NULL] [L1: -> 节点A (Score=10)] -> [L1: -> 节点B (Score=30)] -> [L1: -> 节点C (Score=50)]
2. 哈希表(Hash Table)
- 作用 :存储 成员到分数的映射,实现 O(1) 复杂度的分数查询。
- 结构特点 :
- Key 为成员(Member),Value 为分数(Score)。
- 解决冲突方式:链地址法(Redis 6.0 后使用渐进式 Rehash)。
三、ZSet 的操作逻辑
1. 插入元素(ZADD)
- 检查唯一性 :通过哈希表判断成员是否存在。
- 若存在,更新分数并调整跳跃表位置。
- 若不存在,执行插入。
- 插入跳跃表 :
- 从最高层开始查找,确定新节点的插入位置。
- 随机生成层高(1~32),创建新节点并链接到各层。
- 更新哈希表:添加/更新成员到分数的映射。
2. 查询分数(ZSCORE)
- 直接通过哈希表查询,时间复杂度 O(1)。
3. 范围查询(ZRANGE/ZREVRANGE)
- 从跳跃表的底层链表遍历:
- 升序:从头节点向右扫描。
- 降序:从尾节点向左扫描(利用后退指针)。
4. 删除元素(ZREM)
- 通过哈希表定位分数。
- 在跳跃表中搜索并删除节点,调整前后指针。
- 从哈希表删除成员。
四、为什么选择跳跃表而非平衡树?
维度 | 跳跃表(Skip List) | 平衡树(如 AVL、红黑树) |
---|---|---|
实现复杂度 | 简单,无需旋转操作 | 复杂,需处理平衡条件(旋转、变色) |
范围查询 | 天然有序链表,遍历高效 | 需中序遍历,实现稍复杂 |
并发友好性 | 局部修改,锁粒度小(适合未来扩展) | 全局调整,锁竞争激烈 |
内存占用 | 略高(多层指针) | 较低 |
Redis 选择跳跃表的原因:
- 代码更简单:减少 Bug 风险,易于维护。
- 范围查询高效 :适合
ZRANGE
、ZREVRANGEBYSCORE
等操作。 - 平衡性能与实现:虽理论复杂度相同,但实际性能接近平衡树。
五、内存优化:ziplist 编码
当 ZSet 满足以下条件时,Redis 会使用 压缩列表(ziplist) 存储以节省内存:
- 元素数量 ≤
zset-max-ziplist-entries
(默认 128)。 - 所有成员长度 ≤
zset-max-ziplist-value
(默认 64 字节)。
ziplist 结构 :
按 [member1, score1, member2, score2, ...]
顺序存储,查询时需遍历。
触发转换 :
当条件不满足时,自动转为 跳跃表 + 哈希表。
六、实战示例
1. 存储排行榜
bash
# 添加用户分数
ZADD leaderboard 100 "user1" 200 "user2" 150 "user3"
# 查询前3名(升序)
ZRANGE leaderboard 0 2 WITHSCORES
# 输出:
# 1) "user1"
# 2) "100"
# 3) "user3"
# 4) "150"
# 5) "user2"
# 6) "200"
2. 时间复杂度验证
ZADD
:O(logN)(跳跃表插入)。ZSCORE
:O(1)(哈希表查询)。ZRANGEBYSCORE
:O(logN + M)(M 为返回元素数)。
七、总结
- 底层结构:跳跃表(排序 + 范围操作) + 哈希表(快速分数查询)。
- 优势 :
- 高效的范围查询和单点操作。
- 自动平衡内存与性能(ziplist 编码优化)。
- 适用场景:排行榜、延迟队列、带权重的去重集合。
通过这种混合设计,Redis 的 ZSet 在保证功能强大的同时,维持了高性能和较低的内存开销。