Redis 中的跳表

16 阅读2分钟

Redis 中跳表的应用

Redis 主要在以下地方使用了跳表(Skip List)数据结构:

  1. 有序集合(Sorted Set)的实现

    • Redis 的 Sorted Set(ZSET)使用跳表作为其核心数据结构之一
    • 当有序集合元素较多或者元素的 score 为浮点数时,Redis 优先使用跳表实现
    • Sorted Set 同时使用哈希表和跳表:哈希表用于 O(1) 时间复杂度查找元素,跳表用于根据 score 范围查询和维护元素顺序

跳表的概念

跳表是一种基于有序链表的数据结构,通过建立多层索引来加快查找速度,本质上是一种空间换时间的优化结构。

关键特点:

  • 是链表的一种扩展
  • 每个节点可能在多个层(level)中出现
  • 层级越高,节点越稀疏,查找时可以快速跳过大量节点
  • 平均查找、插入和删除时间复杂度为 O(log n),接近二叉搜索树

跳表的实现原理

1. 基本结构

跳表由多层链表组成:

  • 最底层(Level 0)包含所有元素,完全有序
  • 上层是下层的"快速通道",节点越来越稀疏
  • 每个节点包含一个值和多个指向后继节点的指针数组

image.png

2. Redis 中的跳表实现

Redis 的跳表实现在 server.ht_zset.c 文件中,主要结构如下:

// 跳表节点
typedef struct zskiplistNode {
    sds ele;                   // 元素值
    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;

3. 主要操作实现

查找操作

  1. 从最高层开始查找
  2. 在每一层中,向右查找到最后一个小于目标值的节点
  3. 然后降到下一层继续查找
  4. 重复直到最底层,找到目标位置

插入操作

  1. 找到合适的插入位置
  2. 随机生成新节点的层数(Redis 使用幂次定律,越高层数概率越小)
  3. 修改相关节点的指针,插入新节点
  4. 必要时更新跳表最大层数

删除操作

  1. 找到要删除的节点
  2. 修改所有含有该节点的层中的指针,绕过该节点
  3. 释放节点内存

跳表的优势

  • 实现相对简单:比平衡树实现更简单
  • 内存占用可控:Redis 的跳表平均每个节点不超过 1.33 个指针
  • 范围查询高效:对于范围操作(如 ZRANGEBYSCORE)非常高效
  • 概率平衡:不需要复杂的平衡操作,通过随机层数实现平均 O(log n) 性能

Redis 选择跳表而非平衡树的主要原因是实现简单、易于维护,同时在性能和内存占用上有很好的平衡。