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

相关推荐
Flynt39 分钟前
Room 3.0 包名重构 + KMP 迁移:我把项目升级踩了个遍
android·数据库·kotlin
晚安code1 小时前
缓存击穿、穿透、雪崩一次讲透:附 Redis hotkey 实战
redis
wear工程师1 小时前
Redis 分布式锁到底靠不靠谱:从 SETNX 到 Redlock,我踩过的坑和业内的争议
redis·面试
这个DBA有点耶17 小时前
NULL不是空——数据库里最反直觉的设计,90%新人踩过的坑
数据库·mysql·代码规范
这个DBA有点耶19 小时前
AI写的SQL跑崩了生产库,这锅谁背?
数据库·人工智能·程序员
镜舟科技19 小时前
Databricks 再提 LTAP,AI 时代的数据底座为何重回大一统叙事?
数据库·架构·agent
Databend20 小时前
从湖仓升级为 Agent 时代的数据控制面,Snowflake 和 Databricks 有哪些布局
大数据·数据库·agent
ClouGence1 天前
SQL Server CDC 能放到 Always On 备库读吗?一文讲透原理与实践
数据库·sql server
先吃饱再说2 天前
存储的进化:从 MySQL 到浏览器缓存,数据到底住在哪?
数据库
Nturmoils2 天前
字段太多看不全,ksql 的展开模式和输出控制怎么用
数据库·后端