当前位置: 首页 > news >正文

实战探讨:为什么 Redis Zset 选择跳表?

在了解了跳表的原理和实现后,一个常见的问题(尤其是在面试中)随之而来:为什么像 Redis 的有序集合 (Zset) 这样的高性能组件会选择使用跳表,而不是大家熟知的平衡树(如红黑树)呢?

对于这个问题,Redis 的作者 Salvatore Sanfilippo (@antirez) 曾给出过解释,主要可以归纳为以下几点:

  1. 内存效率与灵活性 (Memory Efficiency & Flexibility):

    • 跳表并非特别消耗内存。其内存占用可以通过调整节点提升的概率 p​ 来控制。通过选择合适的 p​ 值,可以使得跳表的平均内存占用低于某些平衡树。
  2. 高效的范围查询与缓存局部性 (Efficient Range Queries & Cache Locality):

    • Zset 经常需要执行 ZRANGE​ (按排名范围查询) 或 ZREVRANGE​ (按排名反向范围查询) 操作,这本质上是在有序结构上进行一段连续元素的遍历。
    • 跳表的最底层 (Level 0) 是一个有序链表。执行范围查询时,只需定位到范围的起始点,然后沿着 Level 0 的链表顺序遍历即可。这种顺序访问模式具有良好的缓存局部性 (cache locality),与平衡树的中序遍历相比,至少同样好,甚至可能更好。
  3. 实现的简单性与易扩展性 (Implementation Simplicity & Extensibility):

    • 跳表的实现、调试相比平衡树(尤其是红黑树)要简单得多。平衡树复杂的旋转和重新平衡逻辑很容易出错。
    • 跳表的简单性也带来了更好的可扩展性。@antirez 提到,得益于跳表的简洁,他很容易地集成了一个社区贡献的补丁,通过对跳表进行少量修改,就在 O(log N) 时间内实现了 ZRANK​ (获取成员排名) 的功能。

进一步解读与补充:

  • 内存占用对比:

    • 平衡树(如红黑树)每个节点通常需要存储 2 个指向子节点的指针,以及可能的父指针和颜色信息。
    • 跳表每个节点包含的指针数目是可变的,取决于它提升的层数。平均来说,每个节点包含的指针数量为 1 / (1 - p)​(其中 p​ 是节点提升一级概率)。在 Redis 的实现中,p​ 通常取 0.25 (1/4),这意味着平均每个节点大约有 1 / (1 - 0.25) = 1.33​ 个 right​ 指针(再加上 down​ 指针和可能的 backward​ 指针,但核心指向下一节点的指针数平均较少)。这使得跳表在内存使用上具有一定的灵活性和潜在优势。
  • 范围查询的易实现性:

    • 如 @antirez 所说,跳表执行范围查询非常自然。找到范围起点后,沿着最底层的链表顺序遍历即可,逻辑简单清晰。
    • 平衡树执行范围查询,需要先找到范围起点,然后执行中序遍历来依次访问后续节点,直到超出范围终点。虽然中序遍历本身不复杂,但在某些实现中,高效地进行部分中序遍历可能需要额外的辅助结构或递归,相对跳表的直接链表遍历要稍微复杂一些。此外,跳表 Level 0 的节点在内存中可能是物理上更连续的(如果内存分配器配合),有利于缓存;而树的中序遍历则可能在内存地址上跳跃。
  • 实现与维护成本:

    • 这一点对于像 Redis 这样需要高性能、高稳定性且持续迭代的项目至关重要。更简单的实现意味着更少的 Bug、更快的开发迭代速度和更低的维护成本。平衡树,特别是红黑树,其插入和删除操作涉及多种情况的判断、旋转和重新染色,逻辑复杂,容易出错。

总结来说,Redis Zset 选择跳表是基于其在内存占用、范围查询性能与实现简洁性之间取得的良好平衡。 对于 Zset 这种既需要快速单点查找/更新,又需要高效范围遍历的场景,跳表提供了一个非常实用且工程上更优的解决方案。

http://www.xdnf.cn/news/263341.html

相关文章:

  • xLua笔记
  • 55.[前端开发-前端工程化]Day02-包管理工具npm等
  • Oracle 11g通过dg4odbc配置dblink连接神通数据库
  • Oracle RAC ‘Metrics Global Cache Blocks Lost‘告警解决处理
  • 小程序滚动条隐藏(uniapp版本)
  • 【Java学习】通配符?
  • Java多语言DApp质押挖矿盗U源码(前端UniApp纯源码+后端Java)
  • 使用 Selenium 爬取动态网页数据 —— 实战与坑点详解
  • 基于LangChain 实现 Advanced RAG-后检索优化(下)-上下文压缩与过滤
  • 将Airtable导入NocoDB
  • 多协议 Tracker 系统架构与传感融合实战 第六章 多传感器时钟同步与数据对齐
  • SETNX的存在问题和redisson进行改进的原理
  • 【RAG】向量?知识库的底层原理:向量数据库の技术鉴赏 | HNSW(导航小世界)、LSH、K-means
  • 【Hive入门】Hive与Spark SQL深度集成:执行引擎性能全面对比与调优分析
  • C语言蓝桥杯真题代码
  • Go反射-通过反射调用结构体的方法(带入参)
  • 解决奥壹oelove婚恋原生小程序上架问题,彻底解决解对问题增强版旗舰版通用
  • 计算机网络八股文--day4 --传输层TCP与UDP
  • WebAPI项目从Newtonsoft.Json迁移到System.Text.Json踩坑备忘
  • 【项目实践】boost 搜索引擎
  • 基于 JSP 和 Servlet 的数字信息分析小应用
  • 【Linux】驱动开发方法
  • ES6/ES11知识点 续一
  • 人工智能发展史 — 物理学诺奖之 Hopfield 联想和记忆神经网络模型
  • 19:常见的Halcon数据格式
  • 优化01-统计信息
  • 深入解析 SqlSugar 与泛型封装:实现通用数据访问层
  • 图论之幻想迷宫
  • 使用Rust + WebAssembly提升前端渲染性能:从原理到落地
  • 网络安全:sql注入练习靶场——sqli_labs安装保姆级教程