Redis的ZSet对象底层原理——跳表

我们来聊聊「跳表(Skip List)」,这是一个既经典又优雅的数据结构,尤其在 Redis 中非常重要,比如 ZSet(有序集合)底层就用到了跳表。


🌟 跳表(Skip List)简介

跳表是一种有序链表的升级版,通过引入多级"索引层"来加快查找效率。

✅ 主要特点:

特性 说明
有序结构 跳表维护的是一个按 key 排序的链表
多级索引 类似高速公路入口,有"快捷路径"
查找时间复杂度 O(log n),跟平衡树差不多
实现简单 不像红黑树那样复杂
支持范围查找 很适合 Redis 中的范围查询场景

📐 跳表结构长啥样?

假设我们有以下数据:[1, 3, 5, 7, 9]

我们构建跳表的过程如下(用 → 表示链表):

复制代码
Level 2:      1  →         →       → 9
Level 1:      1  →    5    →       → 9
Level 0:      1  → 3 → 5 → 7 → 9

你可以把它想象成"高架桥+地面道路"的组合:

  • 第 0 层是原始链表,所有节点都在这里
  • 每上一层都是稀疏的抽样索引,跳过一些中间节点,加快查找速度
  • 查找从最上层开始,逐层向下,"跳跃式前进"

🔍 查找过程举例:查找 7

  1. 从最上层的 1 开始 → 跳到 9(9 > 7,不行)
  2. 回退到下一层 → 1 → 5 → 9(9 > 7,继续往下)
  3. 到最底层 → 5 → 7 ✅ 找到!

→ 相比普通链表要顺着一格一格走,跳表"跳着走",很快!


📈 跳表的时间复杂度

操作 时间复杂度
查找 O(log n)
插入 O(log n)
删除 O(log n)
空间复杂度 O(n)

相比红黑树、AVL 树,它的代码实现更简单、更易于维护。


💡 跳表的应用场景

场景 用途
Redis ZSet 支持快速插入、范围查询(如 zrangebyscore)
LSM-Tree(如 RocksDB) 早期实现用过跳表
时间序列数据 有序插入、范围检索
分布式系统 一致性哈希环也可用跳表改进检索速度

🔧 跳表 vs 红黑树

项目 跳表 红黑树
插入复杂度 O(log n) O(log n),但旋转逻辑复杂
实现难度 简单 复杂(需维持平衡)
查询性能 相似 相似
空间开销 稍高(多指针) 较少

🧪 小技巧:跳表的层数怎么选?

跳表是随机化结构,每次插入一个节点时,以一定概率(如 1/2)决定它是否晋升到上一层。

最常见的方式是:

c 复制代码
while (random() < p && level < MAX_LEVEL) {
    level++;
}

所以跳表是一种「概率平衡」的数据结构。


📦 总结

优点 缺点
实现简单、性能稳定 占用内存较多
支持快速范围查找 查询时间有轻微波动
非常适合缓存、有序集合 不如 B 树适合磁盘存储

在 Redis 中,跳表(Skip List) 主要用于 Sorted Set(有序集合,zset) 的底层实现。


✅ 使用跳表的场景:zset(有序集合)

Redis 的 zset 类型是一个既可以快速查找元素、又能按照分数排序的数据结构。为了实现这个特性,它使用了 两种结构 来共同实现:

结构 作用
哈希表(dict) 用于通过成员名快速定位其分数,O(1) 时间复杂度
跳表(skiplist) 用于按照分数进行有序排列,支持范围查找、按排名查找等,平均 O(log n) 复杂度

🧠 为什么 Redis 用跳表而不是红黑树?

虽然红黑树、AVL 树等自平衡二叉树也能满足排序要求,但 Redis 选择跳表是因为:

  1. 实现简单、易于维护
  2. 插入和删除操作更平滑,少量随机性避免最坏情况
  3. 天然支持范围查询和按 rank 查找
  4. 跳表是线性结构,遍历更快,利于内存预取(cache friendly)

🧪 举个例子:

bash 复制代码
127.0.0.1:6379> ZADD myzset 1 a 2 b 3 c
127.0.0.1:6379> ZRANGE myzset 0 -1 WITHSCORES
1) "a"
2) "1"
3) "b"
4) "2"
5) "c"
6) "3"

在内部:

  • Redis 用 dict 维护 a → 1,b → 2,c → 3
  • 同时用跳表按分数 1 → 2 → 3 排列节点,方便范围查询,比如 ZRANGEBYSCORE

🔧 Redis 跳表结构核心代码(在源码中)

源码路径:/src/t_zset.c

关键结构:zskiplist、zskiplistNode

c 复制代码
typedef struct zskiplistNode {
    robj *obj;         // 成员对象
    double score;      // 分数
    struct zskiplistNode *backward;
    struct zskiplistLevel {
        struct zskiplistNode *forward;
        unsigned int span;
    } level[];
} zskiplistNode;

✅ 总结

Redis 中只有 zset 使用跳表,结合 hash dict 实现高效的查找和排序。

相关推荐
龙腾AI白云2 小时前
仿真到现实(Sim-to-Real)迁移:跨越仿真与现实鸿沟
数据库·机器学习·plotly·知识图谱·tornado
freejackman4 小时前
【实战二】字段级血缘追踪:拆解 AST 迷局,精准穿透函数链与子查询
大数据·数据库·sql
.痞老板.5 小时前
mysql初阶--增删改查操作
数据库·mysql
FungLeo6 小时前
成为全栈·基础补充·缓存的层次与失效:浏览器、CDN、服务端、数据库
缓存·cdn·数据一致性·http缓存·成为全栈·flutter缓存
我不会起名字32210 小时前
Redis 从 0 到 1(三十三):机器断电 Redis 起不来——AOF/RDB 损坏的 4 步抢救流程
redis·数据恢复·aof·rdb·排查
lambo mercy10 小时前
AI智能租房助手
数据库
浮生若城12 小时前
MySQL:库与表的操作
数据库·mysql
薛定谔的悦12 小时前
光储充一体机从主控到执行单元的双层架构
数据库·mongodb·架构
数字新视界12 小时前
动环监控信创适配在智能机房管理中的应用价值与未来展望
大数据·服务器·数据库·动环监控系统·动力与环境监控系统
code2cat12 小时前
【随笔】KV缓存量化:省下的显存,要付出多少数值误差
人工智能·缓存·大语言模型