Redis 中跳表的应用
Redis 主要在以下地方使用了跳表(Skip List)数据结构:
-
有序集合(Sorted Set)的实现:
- Redis 的 Sorted Set(ZSET)使用跳表作为其核心数据结构之一
- 当有序集合元素较多或者元素的 score 为浮点数时,Redis 优先使用跳表实现
- Sorted Set 同时使用哈希表和跳表:哈希表用于 O(1) 时间复杂度查找元素,跳表用于根据 score 范围查询和维护元素顺序
跳表的概念
跳表是一种基于有序链表的数据结构,通过建立多层索引来加快查找速度,本质上是一种空间换时间的优化结构。
关键特点:
- 是链表的一种扩展
- 每个节点可能在多个层(level)中出现
- 层级越高,节点越稀疏,查找时可以快速跳过大量节点
- 平均查找、插入和删除时间复杂度为 O(log n),接近二叉搜索树
跳表的实现原理
1. 基本结构
跳表由多层链表组成:
- 最底层(Level 0)包含所有元素,完全有序
- 上层是下层的"快速通道",节点越来越稀疏
- 每个节点包含一个值和多个指向后继节点的指针数组
2. Redis 中的跳表实现
Redis 的跳表实现在 server.h
和 t_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. 主要操作实现
查找操作:
- 从最高层开始查找
- 在每一层中,向右查找到最后一个小于目标值的节点
- 然后降到下一层继续查找
- 重复直到最底层,找到目标位置
插入操作:
- 找到合适的插入位置
- 随机生成新节点的层数(Redis 使用幂次定律,越高层数概率越小)
- 修改相关节点的指针,插入新节点
- 必要时更新跳表最大层数
删除操作:
- 找到要删除的节点
- 修改所有含有该节点的层中的指针,绕过该节点
- 释放节点内存
跳表的优势
- 实现相对简单:比平衡树实现更简单
- 内存占用可控:Redis 的跳表平均每个节点不超过 1.33 个指针
- 范围查询高效:对于范围操作(如 ZRANGEBYSCORE)非常高效
- 概率平衡:不需要复杂的平衡操作,通过随机层数实现平均 O(log n) 性能
Redis 选择跳表而非平衡树的主要原因是实现简单、易于维护,同时在性能和内存占用上有很好的平衡。