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 → 1b → 2c → 3
  • 同时用跳表按分数 1 → 2 → 3 排列节点,方便范围查询,比如 ZRANGEBYSCORE

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

源码路径:/src/t_zset.c

关键结构:zskiplistzskiplistNode

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 实现高效的查找和排序。

相关推荐
jakeswang29 分钟前
应用缓存不止是Redis!——亿级流量系统架构设计系列
redis·分布式·后端·缓存
秋难降1 小时前
零基础学SQL(八)——事务
数据库·sql·mysql
Starry_hello world1 小时前
MySql 表的约束
数据库·笔记·mysql·有问必答
RestCloud2 小时前
ETLCloud中的数据转化规则是什么意思?怎么执行
数据库·数据仓库·etl
一个天蝎座 白勺 程序猿2 小时前
Apache IoTDB(4):深度解析时序数据库 IoTDB 在Kubernetes 集群中的部署与实践指南
数据库·深度学习·kubernetes·apache·时序数据库·iotdb
.Shu.2 小时前
Redis zset 渐进式rehash 实现原理、触发条件、执行流程以及数据一致性保障机制【分步源码解析】
数据库·redis·缓存
君不见,青丝成雪2 小时前
大数据技术栈 —— Redis与Kafka
数据库·redis·kafka
悟能不能悟2 小时前
排查Redis数据倾斜引发的性能瓶颈
java·数据库·redis
切糕师学AI2 小时前
.net core web程序如何设置redis预热?
redis·.netcore
DemonAvenger3 小时前
事务管理:ACID特性与隔离级别详解
数据库·mysql·性能优化