跳到主要内容
极客日志极客日志
首页博客AI提示词GitHub精选代理工具
搜索
|注册
博客列表
Javajava算法

Redis Zset 底层实现:跳跃表与字典

综述由AI生成Redis Zset 采用双编码策略平衡内存与性能。数据量小时使用 ziplist 或 listpack 节省内存;数据量大时自动切换为跳表加字典结构。跳表负责维护有序性和范围查询效率,字典提供 O(1) 的成员分值查找。这种混合设计实现了空间与时间的最佳权衡,支持高效的添加、删除、排序及排名操作。

神经兮兮发布于 2026/2/8更新于 2026/5/167.8K 浏览
Redis Zset 底层实现:跳跃表与字典
核心概括

Redis 的 Zset 同时具备两个核心特性:

  1. 有序性:元素按分值(score) 从小到大排列。
  2. 唯一性:集合中的成员(member) 是唯一的,但分值可以相同(分值相同时,按成员字典序排列)。

为了实现这种高效的、兼具'集合'和'有序'特性的数据结构,Redis 采用了两种底层数据结构相结合的方案:

  • ziplist(压缩列表)或 listpack(紧凑列表):用于元素数量少、元素体积小的场景,以节省内存。
  • skiplist(跳跃表) + dict(哈希表):用于通用场景,以提供高效的查询和范围操作。

这种根据条件动态切换底层结构的设计,体现了 Redis 在性能与内存之间做出的精妙权衡。

一、两种编码方式

在 Redis 内部,Zset 有两种编码方式,通过配置项 zset-max-ziplist-entries 和 zset-max-ziplist-value 来控制。

1. ziplist / listpack 编码

在 Redis 早期版本使用 ziplist,新版本(7.0+)逐渐用 listpack 替代 ziplist。我们以 listpack 为例讲解。

适用条件(同时满足):

  • 有序集合保存的 元素数量 小于 zset-max-ziplist-entries (默认 128)。
  • 每个 成员(member)的字符串长度 小于 zset-max-ziplist-value (默认 64 字节)。

内存布局:
listpack 是一个紧凑的、连续内存块,它按 [member1, score1, member2, score2, ...] 的顺序,成对存储成员和分值。

  • 按分值排序:所有元素在 listpack 内部就是严格按照分值升序排列的。
  • 查找方式:由于是紧凑数组,查找需要 线性遍历。但因为元素少,且内存连续,缓存友好,效率仍然可以接受。
  • 插入/删除:需要移动后续元素,时间复杂度 O(N)。同样因为元素少,成本可控。

目的:在元素少且小的场景下,这种结构避免了额外的指针开销,极大地节约了内存。

2. skiplist 编码

当不满足上述任一条件时,Zset 会自动转换为 skiplist 编码。这才是 Zset 的'完全体'和核心实现。

它实际上是一个 复合结构,包含一个 跳跃表(skiplist) 和一个 字典(dict)。

typedef struct zset {
    dict *dict;       // 哈希字典
    zskiplist *zsl;   // 跳跃表
} zset;
为什么需要两种结构?
  • 跳跃表(zskiplist):核心作用在于维护元素的有序性,支持高效的范围查询(如 ZRANGE, ZRANK)和插入/删除操作(平均 O(logN))。
  • 字典(dict):核心作用在于提供 O(1) 时间复杂度的成员查询(如 ZSCORE 命令,直接根据 member 获取其 score)。

如果只用跳跃表,查询成员分值需要 O(logN)。
如果只用字典,无法进行高效的范围操作。

因此,这种 '空间换时间' 的设计,让 Zset 既能快速进行单点查询,又能高效进行范围操作,是工程上的经典取舍。

二、核心数据结构剖析
1. 跳跃表(Skip List)详解

跳跃表是一种多层级的有序链表,是平衡树的一种概率替代方案,实现更简单,在并发环境下也更有优势。

结构:

typedef struct zskiplist {
    struct zskiplistNode *header, *tail; // 头尾指针
    unsigned long length;                // 节点总数
    int level;                           // 当前最大层数
} zskiplist;

typedef struct zskiplistNode {
    sds ele;                             // 成员(SDS 字符串)
    double score;                        // 分值
    struct zskiplistNode *backward;      // 后向指针(L0 层,用于逆序)
    struct zskiplistLevel {
        struct zskiplistNode *forward;   // 该层的前进指针
        unsigned long span;              // 该层到下一个节点的跨度(用于排名)
    } level[];                           // 柔性数组,表示节点的层级
} zskiplistNode;

关键特性:

  • 多层链表:每个节点随机一个层高(1-32)。level 数组长度即为层高。
  • 有序性:节点首先按 score 排序,score 相同时,按 ele 的字典序排序。
  • 查找路径:从最高层(header 的最高层)开始,向右遍历找到最后一个 score 小于目标(或 score 相等但 ele 小于目标)的节点,然后下降一层继续。如此反复,直到第 0 层。这个过程时间复杂度平均 O(logN)。
  • 跨度(span):level[i].span 记录了从当前节点到 level[i].forward 节点之间,跨越了第 0 层的多少个节点。这是 ZRANK 命令(获取排名)能够 O(logN) 完成的关键。计算排名时,只需将搜索路径上所有符合方向的跨度累加即可。
  • 后向指针(backward):用于从尾到头遍历,支持 ZREVRANGE 等逆序操作。
2. 字典(Dict)

就是一个标准的 Redis 哈希表,其作用是:

  • 键(Key):存储成员(member)。
  • 值(Value):存储该成员对应的分值(score,一个 double 类型)。
  • 这个字典确保了对任意成员的 O(1) 时间复杂度 的访问。
三、操作流程示例

假设我们执行 ZADD myzset 10 "alice" 20 "bob" 30 "charlie"。

在 skiplist 编码下,内存结构示意图如下:

  • dict: { "alice": 10, "bob": 20, "charlie": 30 }
  • zskiplist: 节点按分数 10 -> 20 -> 30 有序链接,包含多层级指针和跨度信息。

关键操作如何工作:

  1. ZSCORE myzset "bob":直接在 dict 中查找键 "bob",立即返回其值 20。O(1)。
  2. ZRANGE myzset 1 2:从 zskiplist 的 header 出发,利用高层指针快速定位到起始节点("bob"),然后沿低层指针遍历输出。O(logN + M),M 为返回元素个数。
  3. ZRANK myzset "charlie":从 zskiplist 的 header 出发,搜索 "charlie" 的路径,同时累加沿途的 span 值。最终得到的累加和就是其排名(从 0 开始)。O(logN)。
  4. ZADD myzset 15 "david":
    • 先在 dict 中检查 "david" 是否存在(保证唯一性)。
    • 然后在 zskiplist 中执行标准的跳表插入操作(O(logN)),找到插入位置。
    • 最后,在 dict 中插入 "david" -> 15 的键值对。
    • 整个操作 O(logN)。
四、ZSET ZRANGE 操作序列图

当执行 ZRANGE myzset 1 3 命令时,Redis 内部不同组件之间的交互流程涉及字典的快速定位与跳跃表的顺序遍历配合。

五、总结与要点
  1. 双编码策略:Zset 是智能的,小数据用紧凑的 listpack 省内存,大数据用功能强大的'跳表 + 哈希'组合保性能。

  2. 核心复合结构:skiplist + dict 是 Zset 的灵魂。skiplist 负责排序和范围操作,dict 负责快速单点访问。二者通过共享成员和分值的指针来保证数据一致性,没有冗余存储。

  3. 跳跃表的角色:它不仅是一个有序链表,其跨度(span) 属性是实现 O(logN) 复杂度的排名查询的关键。

  4. 时间复杂度:

    • 添加/删除/按分值查询:平均 O(logN)。
    • 按成员查分值:O(1)。
    • 范围查询(如 ZRANGE):O(logN + M)。
    • 获取排名(ZRANK):O(logN)。
  5. 选择原因:相比于红黑树等严格平衡树,跳跃表实现简单,区间查询更直观,且在并发环境下更容易实现无锁优化。

面试回答

它的底层实现其实采用了两种数据结构相结合的**'混合'策略**,目的是在内存使用和性能之间取得一个平衡。具体来说,它同时使用了:

  1. 跳跃表(Skip List)
  2. 字典(或哈希表,Hash Table)

这两种结构会共享集合中元素(成员)和分数(分值)的数据,但通过指针引用,所以不会造成双倍的内存开销。

为什么需要两种结构呢?
这主要是为了应对 Zset 不同的操作场景,让它们都能非常高效:

  • 跳跃表 的核心作用是维持元素的有序性。它的结构像多层链表,上层是'快速通道',下层是'精确通道'。这使得范围型操作,以及插入、删除的平均时间复杂度都能在 O(log N) 级别,效率很高。
  • 字典 的核心作用是提供 '成员 -> 分数' 的快速映射。当我们执行像去获取某个成员的分数或者更新一个已有成员这类操作时,如果能直接通过成员名(key)在哈希表里 O(1) 时间复杂度查到它的分数,那就太快了。如果只用跳跃表,这个查询就需要 O(log N)。

所以,简单来讲,可以把 Zset 想象成 一个用哈希表做'索引',用跳跃表做'排序清单'的组合体。哈希表保证了点查的极致速度,跳跃表保证了范围操作的效率。

不过,在 Redis 的后续版本中(我记得是 7.0 之后),为了进一步节省内存,还有另一种编码方式,叫 Ziplist(紧凑列表)。

  • 当 Zset 同时满足两个条件时:1. 元素数量较少(默认 ≤ 128 个);2. 每个成员字符串长度较短(默认 ≤ 64 字节),Redis 会使用一块连续内存的 ziplist 来存储。在这个链表里,成员和分数是挨个交替存放的,它因为少了跳跃表和哈希表的结构开销,所以非常节省内存。
  • 一旦不满足上述任一条件,它就会自动转换成标准的'跳跃表 + 字典'结构。这个转换对使用者是无感的。

总结一下,我的理解是:
Zset 的底层会根据数据规模灵活选择:

  • 对于小型集合,使用内存紧凑的 ziplist。
  • 对于通用集合,使用 '跳跃表 + 字典' 的双结构,同时保证高效的范围操作和单点查询。这也是一个在工程上非常经典的,'用空间换时间'和'空间时间平衡' 的设计思路。

目录

  1. 核心概括
  2. 一、两种编码方式
  3. 1. ziplist / listpack 编码
  4. 2. skiplist 编码
  5. 为什么需要两种结构?
  6. 二、核心数据结构剖析
  7. 1. 跳跃表(Skip List)详解
  8. 2. 字典(Dict)
  9. 三、操作流程示例
  10. 四、ZSET ZRANGE 操作序列图
  11. 五、总结与要点
  12. 面试回答
  • 💰 8折买阿里云服务器限时8折了解详情
  • GPT-5.5 超高智商模型1元抵1刀ChatGPT中转购买
  • 代充Chatgpt Plus/pro 帐号了解详情
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • 编写提示词(Prompt)的实用技巧
  • Java 工程项目管理系统功能模块详解
  • VSCode Copilot 接入 GLM-4.6 及多模型适配方案
  • C++ 模板详解:进阶篇
  • 2026 届学位论文 AIGC 检测率要求汇总与应对策略
  • FPGA 加速 YOLOv5:从模型量化到硬件部署全流程
  • LeetCode 二分查找算法入门与实战
  • 零改造迁移实录:2000+存储过程从 SQL Server 迁移至 KingbaseES V9R4C12
  • Diffusion Transformer(DiT):将 U-Net 换成 ViT,应用于视频生成与机器人动作预测
  • 深入理解 Transformer 架构:从注意力机制到位置编码
  • DeepMind 提出欧几里得 Transformer:2.5 天完成 1 年分子动力学模拟
  • Vue3 与 TypeScript 核心面试题实战解析
  • 从三年前端到 CS 硕士:我在韩国亚大的留学复盘与回归
  • 医疗人工智能大模型的关键能力:中期训练 (Mid-training)
  • AI 辅助识别与解码加密字符串的技术实践
  • SketchUp STL 插件:3D 建模与打印无缝转换指南
  • Git Bash 安装与基础使用指南
  • C++ 模板进阶:非类型参数与特化机制
  • 技术学习资源整理:电子书、软件与 AI 工具合集
  • 基于 MCP 协议的智能体落地示例:天气预报工具实现

相关免费在线工具

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online