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

相关推荐
左直拳25 分钟前
mysql community 8.0.23升级到8.0.42再到8.4.5
数据库·mysql·升级·8.0.42·8.4.5
laimaxgg27 分钟前
MySQL表的约束
数据库·mysql
我不是秋秋1 小时前
MongoDB 操作全解析:从部署到安全控制的详细指南(含 emoji 趣味总结)
数据库·mongodb
消失在人海中2 小时前
使用exdp 备份数据库
数据库·oracle
nomi-糯米2 小时前
Mybatis-plus代码生成器的创建使用与详细解释
数据库·mysql·mybatis
涛思数据(TDengine)2 小时前
时序数据库 TDengine × Perspective:你需要的可视化“加速器”
数据库·时序数据库·tdengine
傻小胖2 小时前
MongoDB的下载安装与启动
数据库·mongodb
深鱼~3 小时前
【Redis】缓存|缓存的更新策略|内存淘汰策略|缓存预热、缓存穿透、缓存雪崩和缓存击穿
数据库·redis·缓存
nicepainkiller3 小时前
redis高阶2 高性能
数据库·redis·缓存