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 在保证功能强大的同时,维持了高性能和较低的内存开销。

相关推荐
向哆哆5 分钟前
Netty在Java网络编程中的应用:实现高性能的异步通信
java·网络·php
程序员爱钓鱼14 分钟前
循环语句:for、range -《Go语言实战指南》
java·数据结构·算法
wowocpp33 分钟前
Java项目层级介绍 java 层级 层次
java
码上飞扬44 分钟前
Java大师成长计划之第20天:Spring Framework基础
java·开发语言
chenyuhao20241 小时前
链表面试题7之相交链表
数据结构·算法·链表·面试·c#
wowocpp1 小时前
centos 7 安装 java 运行环境
java·linux·centos
Pluchon1 小时前
硅基计划2.0 学习总结 壹 Java初阶
java·开发语言·学习·算法
wowocpp1 小时前
Java MVC
java·开发语言·mvc
带刺的坐椅2 小时前
jFinal 使用 SolonMCP 开发 MCP(拥抱新潮流)
java·ai·solon·jfinal·mcp
陌尘(MoCheeen)2 小时前
技术书籍推荐(002)
java·javascript·c++·python·go