Redis 跳表(Skip List)原理实现

 更新时间:2025年04月07日 10:04:10   作者:xiaoyu❅  
跳表是zset有序集合的底层实现之一,本文主要介绍了Redis 跳表(Skip List)原理实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

引言:为什么 Redis 选择跳表?

在有序集合(ZSET)的实现中,Redis 开发者面临一个关键抉择:如何在高性能读写和代码简洁性之间找到平衡?传统平衡树(如红黑树)虽然能保证 O(logN) 时间复杂度,但实现复杂且难以支持范围查询。跳表(Skip List) 以媲美平衡树的性能、极简的实现(约 200 行代码)和天然支持范围查询的特性,成为 Redis ZSET 的核心数据结构。本文将深入剖析跳表的实现细节与 Redis 的工程优化。

一、跳表核心思想:概率化的多层索引

1.1 从链表到跳表的进化

  • 普通链表:插入/删除 O(1),但查询需要 O(N)
  • 跳表创新:通过随机化多层索引,实现对数级查询效率

1.2 跳表结构可视化

Level 3: Head -> 37 --------------------------> 99 -> NULL  
Level 2: Head -> 37 -------> 71 -------> 99 -> NULL  
Level 1: Head -> 37 -> 55 -> 71 -> 85 -> 99 -> NULL  
Level 0: Head -> 37 -> 55 -> 71 -> 85 -> 99 -> NULL  

关键特性

每个节点随机生成层数(Redis 最大层数 64)

高层索引跨越更多节点,加速搜索

底层链表存储完整数据

二、Redis 跳表实现深度解剖

2.1 数据结构定义(redis.h)

// 跳表节点
typedef struct zskiplistNode {
    sds ele;                          // 成员对象(SDS字符串)
    double score;                     // 排序分值
    struct zskiplistNode *backward;   // 后退指针(双向链表)
    struct zskiplistLevel {
        struct zskiplistNode *forward; // 前进指针
        unsigned long span;            // 跨度(用于排名计算)
    } level[];                        // 柔性数组,层级随机生成
} zskiplistNode;

// 跳表结构
typedef struct zskiplist {
    struct zskiplistNode *header, *tail;
    unsigned long length;             // 节点总数
    int level;                        // 当前最大层数
} zskiplist;

设计亮点

  • span 字段:记录节点在某一层的跨度,支持 O(1) 时间复杂度计算元素排名(ZRANK
  • backward 指针:构成双向链表,支持逆序遍历
  • 柔性数组(level[]):内存紧凑,避免指针冗余

2.2 关键操作源码解析

2.2.1 节点层数生成算法

// redis.h 源码节选
int zslRandomLevel(void) {
    int level = 1;
    // 0xFFFF 对应 1/4 概率提升层级(基于位运算优化)
    while ((random()&0xFFFF) < (ZSKIPLIST_P * 0xFFFF))
        level += 1;
    return (level < ZSKIPLIST_MAXLEVEL) ? level : ZSKIPLIST_MAXLEVEL;
}

数学原理

  • 使用 幂次定律(Power Law),高层节点指数级减少
  • 每个节点有 50% 概率进入 L1,25% 进入 L2,12.5% 进入 L3...
  • Redis 实际使用 1/4 的概率因子(优化内存与性能平衡)

2.2.2 插入节点流程(zslInsert)

  • 搜索路径记录:从最高层开始,记录每层的前驱节点和跨度
  • 生成随机层数:调用 zslRandomLevel
  • 创建新节点:分配层级并连接前后指针
  • 更新跨度:调整相邻节点的 span 值
  • 维护后退指针:设置新节点的 backward 指针

2.2.3 范围查询(ZRANGEBYSCORE)

  • 从高层索引快速定位起点
  • 利用底层链表遍历范围
  • 复杂度:O(logN + M)(M 为返回元素数量)

三、性能分析与优化策略

3.1 时间复杂度对比

操作跳表(平均)跳表(最坏)平衡树
插入O(logN)O(N)O(logN)
删除O(logN)O(N)O(logN)
查找O(logN)O(N)O(logN)
范围查询O(logN + M)O(N)O(logN + M)

:跳表最坏情况(所有节点高度相同)概率极低(例如 1亿节点出现概率为 1/(2^50))

3.2 内存占用分析

  • 理论空间复杂度:O(NlogN)
  • Redis 优化实践:通过 1/4 概率因子,实际空间占用约为 O(1.33N)(实测 100 万节点内存约 64MB)

3.3 调优参数

  • ZSKIPLIST_MAXLEVEL:控制最大层数(默认 64,可调整内存与性能平衡)
  • ZSKIPLIST_P:调整层数生成概率(默认 0.25)

四、跳表在 Redis 中的应用场景

4.1 有序集合(ZSET)

元素数量 > 128 或 元素长度 > 64 字节 时,ZSET 内部使用跳表

支持操作

  • ZADD/ZREM:插入删除
  • ZRANK/ZSCORE:排名查询
  • ZRANGE:范围查询

4.2 集群元数据管理

用于维护槽位(slot)与节点的映射关系

五、跳表 vs 平衡树:工程角度的选择

维度跳表红黑树
实现复杂度约 200 行代码约 500 行代码
范围查询天然支持(链表特性)需要额外遍历
并发控制更易实现无锁优化需要复杂锁机制
调试难度可视化调试友好树旋转逻辑难追踪

Redis 作者 Antirez 的评价:“跳表在理论上不如平衡树优雅,但实际工程中更简单、更快,尤其适合需要范围查询的场景。”

总结:

跳表的精妙之处在于 用概率换结构,通过随机化层级分布避免复杂的再平衡操作。这种“以空间换时间” + “以概率换简单性”的设计哲学,在分布式系统开发中具有重要借鉴意义。理解跳表不仅有助于掌握 Redis 源码,更能启发我们思考如何在高性能与可维护性之间找到平衡。

到此这篇关于Redis 跳表(Skip List)原理实现的文章就介绍到这了,更多相关Redis 跳表内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • spring boot集成redis基础入门实例详解

    spring boot集成redis基础入门实例详解

    redis在spring boot项目开发中是常用的缓存套件,常见使用的是spring-boot-starter-data-redis,这篇文章主要介绍了spring boot集成redis基础入门,本文结合实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-10-10
  • redis加锁的几种方式汇总

    redis加锁的几种方式汇总

    这篇文章主要介绍了redis加锁的几种方式汇总,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-03-03
  • 在redis中存储ndarray的示例代码

    在redis中存储ndarray的示例代码

    在Redis中存储NumPy数组(ndarray)通常需要将数组转换为二进制格式,然后将其存储为字符串,这篇文章给大家介绍了在redis中存储ndarray的示例代码,感兴趣的朋友一起看看吧
    2024-02-02
  • 详解Redis瘦身指南

    详解Redis瘦身指南

    Redis应该是开发者最常用的缓存服务器了,它丰富的数据结构,快速高效的内存操作能帮助开发者迅速完成复杂功能的设计,作为一个内存型数据库,Redis经常会遇到内存问题,今天我们来谈一下Redis常见的内存满的问题,介绍一下给 Redis “瘦身”的通用方式。
    2021-05-05
  • Centos7.3安装Redis4.0.6详细图文教程

    Centos7.3安装Redis4.0.6详细图文教程

    这篇文章主要介绍了Centos7.3安装Redis4.0.6详细教程图解,本文图文并茂给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下
    2018-10-10
  • reids自定义RedisTemplate以及乱码问题解决

    reids自定义RedisTemplate以及乱码问题解决

    本文主要介绍了reids自定义RedisTemplate以及乱码问题解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2024-04-04
  • Redis持久化方式之RDB和AOF的原理及优缺点

    Redis持久化方式之RDB和AOF的原理及优缺点

    在Redis中,数据可以分为两类,即内存数据和磁盘数据,Redis 提供了两种不同的持久化方式,其中 RDB 是快照备份机制,AOF 则是追加写操作机制,本文将详细给大家介绍Redis 持久化方式RDB和AOF的原理及优缺点,感兴趣的同学可以跟着小编一起来学习
    2023-06-06
  • RabbitMQ+redis+Redisson分布式锁+seata实现订单服务的流程分析

    RabbitMQ+redis+Redisson分布式锁+seata实现订单服务的流程分析

    订单服务涉及许多方面,分布式事务,分布式锁,例如订单超时未支付要取消订单,订单如何防止重复提交,如何防止超卖、这里都会使用到,这篇文章主要介绍了RabbitMQ+redis+Redisson分布式锁+seata实现订单服务的流程分析,需要的朋友可以参考下
    2024-07-07
  • 一篇文章带你弄清楚Redis的精髓

    一篇文章带你弄清楚Redis的精髓

    Redis是一个开源的、支持网络、基于内存的键值对存储系统,它可以用作数据库、缓存和消息中间件。它支持多种数据类型,包括字符串、散列、列表、集合、位图等,拥有极快的读写速度,并且支持丰富的特性,如事务、持久化、复制、脚本、发布/订阅等。
    2023-02-02
  • redis启动,停止,及端口占用处理方法

    redis启动,停止,及端口占用处理方法

    今天小编就为大家分享一篇redis启动,停止,及端口占用处理方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2018-05-05

最新评论