Redis 中的跳跃表(SkipList)是一种用于实现有序集合(Sorted Set)的数据结构,它提供了一个可以高效进行插入、删除、查找等操作的有序序列。跳跃表的设计基于概率平衡树的概念,但实现更为简洁且在实践中表现出优秀的性能。以下是 Redis 中跳跃表(SkipList)的主要组件及其源码解析要点:
跳跃表结构体定义
Redis 中跳跃表结构体主要包含两个部分:节点(zskiplistNode)和整体跳跃表(zskiplist)。
zskiplistNode 节点结构体
typedef struct zskiplistNode {
// 成员对象
robj *obj;
// 分值
double score;
// 层次数组,每一层包含一个前后指针
struct zskiplistLevel {
// 指向下一层节点的指针
struct zskiplistNode *forward;
//跨度,用于估算此层的节点数量
unsigned int span;
} level[];
} zskiplistNode;
每个节点除了包含它所储存的成员对象(键值对中的值)和对应的分值外,还有一个可变大小的层次数组 level[]
,每个层级都有一个指向同一层级下一个节点的指针。
zskiplist 跳跃表结构体
typedef struct zskiplist {
// 表头节点和表尾节点
struct zskiplistNode *header, *tail;
// 表中节点的数量
unsigned long length;
// 最大层级数
int level;
// 随机等级生成器的基数
unsigned long long max_level;
// 层级比例因子
double p;
} zskiplist;
跳跃表包含表头节点、表尾节点、节点总数、当前最大层级数、随机等级生成器基数和层级概率比例因子。层级数是由一个随机函数决定的,这样可以让查找路径呈现近似于log N的分布。
跳跃表特性与操作
-
插入节点:
- 插入新节点时,先生成一个随机层数,然后将节点按照分值顺序插入到各个层级中。
- 新节点会在每层找到合适的插入位置,并链接到合适的位置。
-
查找节点:
- 查找操作从最高层级开始,逐层向下比对分值,直到找到目标节点或到达最低层级。
-
删除节点:
- 删除节点时,需在所有层级中找到并断开相应的指针连接。
-
更新节点:
- 如果仅更改节点的分值导致排序变化,也需要更新相关指针关系。
-
渐进式复杂度:
- 跳跃表的查找、插入、删除操作平均时间复杂度为 O(log N),最坏情况下为 O(N)。
随机性与平衡性
Redis 跳跃表通过随机决定节点的高度来保持数据的均衡分布,高度较高的节点是稀疏的,而较低层级则较为稠密,这种设计使得大部分查找可以在较少的层级内完成,从而提高性能。
源码分析要点
在 Redis 源码中,有关跳跃表的实现包括了各种操作的实现函数,如 zslInsert()
(插入)、zslDelete()
(删除)、zslUpdateScore()
(更新分值)等,这些函数均体现了跳跃表的上述特性,并利用了跳跃表节点的层级结构来高效执行相应操作。此外,还有用于初始化、释放跳跃表资源、遍历跳跃表等功能的辅助函数。