redis中的zset怎么实现的?

知其然要知其所以然,探索每一个知识点背后的意义,你知道的越多,你不知道的越多,一起学习,一起进步,如果文章感觉对您有用的话,关注、收藏、点赞,有困惑的地方请评论,我们一起交流!


Redis 中的 ZSet(有序集合) 是一种结合了 SetSorted 特性的数据结构,既能保证元素的唯一性,又能按分数(Score)排序。其高效实现依赖于 跳跃表(Skip List)哈希表(Hash Table) 的混合结构。以下是详细分析:

一、ZSet 的核心特性

  1. 唯一性:成员(Member)不可重复,类似 Set。
  2. 有序性:按分数(Score)排序,分数可重复。
  3. 操作效率
    • 插入、删除、查找:平均 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. 检查唯一性 :通过哈希表判断成员是否存在。
    • 若存在,更新分数并调整跳跃表位置。
    • 若不存在,执行插入。
  2. 插入跳跃表
    • 从最高层开始查找,确定新节点的插入位置。
    • 随机生成层高(1~32),创建新节点并链接到各层。
  3. 更新哈希表:添加/更新成员到分数的映射。

2. 查询分数(ZSCORE)

  • 直接通过哈希表查询,时间复杂度 O(1)

3. 范围查询(ZRANGE/ZREVRANGE)

  • 从跳跃表的底层链表遍历:
    • 升序:从头节点向右扫描。
    • 降序:从尾节点向左扫描(利用后退指针)。

4. 删除元素(ZREM)

  1. 通过哈希表定位分数。
  2. 在跳跃表中搜索并删除节点,调整前后指针。
  3. 从哈希表删除成员。

四、为什么选择跳跃表而非平衡树?

维度 跳跃表(Skip List) 平衡树(如 AVL、红黑树)
实现复杂度 简单,无需旋转操作 复杂,需处理平衡条件(旋转、变色)
范围查询 天然有序链表,遍历高效 需中序遍历,实现稍复杂
并发友好性 局部修改,锁粒度小(适合未来扩展) 全局调整,锁竞争激烈
内存占用 略高(多层指针) 较低

Redis 选择跳跃表的原因:

  1. 代码更简单:减少 Bug 风险,易于维护。
  2. 范围查询高效 :适合 ZRANGEZREVRANGEBYSCORE 等操作。
  3. 平衡性能与实现:虽理论复杂度相同,但实际性能接近平衡树。

五、内存优化:ziplist 编码

当 ZSet 满足以下条件时,Redis 会使用 压缩列表(ziplist) 存储以节省内存:

  1. 元素数量 ≤ zset-max-ziplist-entries(默认 128)。
  2. 所有成员长度 ≤ 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 在保证功能强大的同时,维持了高性能和较低的内存开销。

相关推荐
一只小松许️15 分钟前
Rust泛型与特性
java·开发语言·rust
_一条咸鱼_5 小时前
AI 大模型的 MCP 原理
人工智能·深度学习·面试
_一条咸鱼_5 小时前
AI 大模型 Function Calling 原理
人工智能·深度学习·面试
angushine5 小时前
Gateway获取下游最终响应码
java·开发语言·gateway
爱的叹息5 小时前
关于 JDK 中的 jce.jar 的详解,以及与之功能类似的主流加解密工具的详细对比分析
java·python·jar
小陈同学呦5 小时前
聊聊双列瀑布流
前端·javascript·面试
一一Null5 小时前
Token安全存储的几种方式
android·java·安全·android studio
来自星星的坤6 小时前
SpringBoot 与 Vue3 实现前后端互联全解析
后端·ajax·前端框架·vue·springboot
AUGENSTERN_dc6 小时前
RaabitMQ 快速入门
java·后端·rabbitmq